diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f59e9d11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +dist +*dropin.cache +*egg-info +*_temp diff --git a/NEWS.rst b/NEWS.rst index db9b575d..0d6758a9 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,19 @@ +Nevow 1.0 (2018-02-01) +====================== + +Features +-------- + +This release is compatible with python3, and actually incompatible with +python2. + +- _flat.flatten no longer worries about encoding strings; this has + to be done by the write function passed. +- htmlfile and htmlstr are now aliases for xmlfile and xmlstr and + consequently require well-formed XML. This change results from the + microdom being effecively dropped from twisted.web for python3 + + Nevow 0.14.3 (2017-07-26) ========================= diff --git a/benchmarks/json_string_tokenizer.py b/benchmarks/json_string_tokenizer.py index 65f7059c..0b0b9000 100644 --- a/benchmarks/json_string_tokenizer.py +++ b/benchmarks/json_string_tokenizer.py @@ -22,7 +22,7 @@ def postOptions(self): self['scale'] = int(self['scale']) -BASE = u'Hello, world. "Quotes".' +BASE = 'Hello, world. "Quotes".' def benchmark(iterations, scale): """ Deserialize a string C{iterations} times. Make the string longer based @@ -32,10 +32,10 @@ def benchmark(iterations, scale): """ s = serialize(BASE * scale) before = time() - for i in xrange(iterations): + for i in range(iterations): parse(s) after = time() - print (after - before) / iterations, 'per call' + print((after - before) / iterations, 'per call') diff --git a/doc/strings_and_bytes.rst b/doc/strings_and_bytes.rst new file mode 100644 index 00000000..e5865a5b --- /dev/null +++ b/doc/strings_and_bytes.rst @@ -0,0 +1,62 @@ +===================================== +Strings vs. Bytes in Nevow on python3 +===================================== + +Twisted.web decided to have represent several items in their Request +class – which is used quite frequently in nevow and exposed to user code +– as bytes. Also, at some point nevow has to produce bytes, as that is +what needs to go down the line. + +In between, however, I'd like to keep out bytes as much as possible and +let people work with strings as far as possible. This document attempts +to delineate the string/bytes perimeter. + +Given that, unfortunately, that perimeter is long and twisted, the plan +is to accept bytes and strings in several places (in particular, always +for URIs), where bytes, for these purposes, are supposed to be in a +(perhaps at some poin configurable) default encoding, which for now is +utf-8 independent of the enviroment. + +Use utils.toBytes or utils.toString to turn function arguments into +strings or bytes as requrired. + + +Bytes within Twisted we're concerned with +========================================= + +The most important items that are byte strings within request, include: + +* uri +* keys and values in args (this hurts a lot) +* header keys (but header values are decoded) +* prePath -- this is where segments come from; segments, however, are + nevow interface and hence strings. +* the arguments to write (in nevow, we accept strings, too) + + +At least cred.checkers.InMemoryUsernamePasswordDatabaseDontUse +explicitly ASCII-encodes their usernames right now. Since that's +what's used in the unit tests, I'm following ASCII-encoded usernames +in guard. This seems insane. Anyone actually working with guard +should look into this. + + +Bytes usage within nevow itself +=============================== + +While flatteners still return strings, what is passed on to +twisted.web requests' write methods must, of course, be bytes. +nevow.appserver.Requests make that translation using utils.toBytes; user +code requiring non-UTF-8 encodings needs to translate to bytes itself at +this point. + +Since renderHTTP can (and is, indeed, encouraged to) write strings and +the translation is done within nevow.Request.write (or similar), +Page.renderSynchronously and Page.renderString return strings rather +than bytes. + +This is particularly relevant for unit tests: what is in the +FakeRequest's accumulator is bytes. + + +.. vim:tw=72 diff --git a/examples/advanced_manualform/advanced_manualform.py b/examples/advanced_manualform/advanced_manualform.py index 3a472174..a1d84688 100644 --- a/examples/advanced_manualform/advanced_manualform.py +++ b/examples/advanced_manualform/advanced_manualform.py @@ -61,7 +61,7 @@ def redirectAfterPost(aspects): magicCookie = str(now()) refpath = refpath.replace('_nevow_carryover_', magicCookie) _CARRYOVER[magicCookie] = C = tpc.Componentized() - for k, v in aspects.iteritems(): + for k, v in aspects.items(): C.setComponent(k, v) request.redirect(str(refpath)) from nevow import static @@ -99,8 +99,8 @@ class Page(ManualFormMixin, rend.Page): def form_post_btn1(self, what=None): # 'what' is a keyword argument, and must be the same name that you # give to the widget. - print "btn1:", what + print("btn1:", what) def form_post_btn2(self, what=None): # see above for 'what'. - print "btn2:", what + print("btn2:", what) diff --git a/examples/athenademo/benchmark.py b/examples/athenademo/benchmark.py index 34d7f9d8..710df9f6 100644 --- a/examples/athenademo/benchmark.py +++ b/examples/athenademo/benchmark.py @@ -1,5 +1,5 @@ -from __future__ import division + from twisted.python import filepath @@ -31,7 +31,7 @@ def render_body(self, ctx, data): yield top class InitializationBenchmark(athena.LiveFragment): - jsClass = u'Nevow.Benchmarks.InitializationBenchmark' + jsClass = 'Nevow.Benchmarks.InitializationBenchmark' docFactory = loaders.stan( tags.div(render=tags.directive('liveFragment'))[ diff --git a/examples/athenademo/calculator.py b/examples/athenademo/calculator.py index 21d1fdbe..996caf35 100644 --- a/examples/athenademo/calculator.py +++ b/examples/athenademo/calculator.py @@ -28,8 +28,8 @@ class Calculator(object): entered into the calculator. For example, if the buttons '3', '5', and '+' have been pressed (in that order), C{expression} will be C{'35+'}. """ - defaultExpression = u'0' - errorExpression = u'E' + defaultExpression = '0' + errorExpression = 'E' def __init__(self): self.expression = self.defaultExpression @@ -57,7 +57,7 @@ def buttonClicked(self, symbol): # Evaluate the expression if symbol == '=': try: - self.expression = unicode(eval(self.expression)) + self.expression = str(eval(self.expression)) except ZeroDivisionError: self.expression = self.errorExpression return self.expression @@ -86,7 +86,7 @@ class CalculatorElement(LiveElement): """ docFactory = xmlfile(sibling('calculator.html').path, 'CalculatorPattern') - jsClass = u"CalculatorDemo.Calculator" + jsClass = "CalculatorDemo.Calculator" validSymbols = '0123456789/*-=+.C' @@ -123,7 +123,7 @@ def __init__(self, *a, **kw): # Update the mapping of known JavaScript modules so that the # client-side code for this example can be found and served to the # browser. - self.jsModules.mapping[u'CalculatorDemo'] = sibling( + self.jsModules.mapping['CalculatorDemo'] = sibling( 'calculator.js').path diff --git a/examples/athenademo/typeahead.py b/examples/athenademo/typeahead.py index e738cf18..9e8c523b 100644 --- a/examples/athenademo/typeahead.py +++ b/examples/athenademo/typeahead.py @@ -3,10 +3,10 @@ from formless import annotate, webform from twisted.python import util -animals = {u'elf' : u'Pointy ears. Bad attitude regarding trees.', - u'chipmunk': u'Cute. Fuzzy. Sings horribly.', - u'chupacabra': u'It sucks goats.', - u'ninja': u'Stealthy and invisible, and technically an animal.', +animals = {'elf' : 'Pointy ears. Bad attitude regarding trees.', + 'chipmunk': 'Cute. Fuzzy. Sings horribly.', + 'chupacabra': 'It sucks goats.', + 'ninja': 'Stealthy and invisible, and technically an animal.', } @@ -26,8 +26,8 @@ class TypeAheadFieldFragment(athena.LiveFragment): ]) def loadDescription(self, typed): - if typed == u'': - return None, u'--' + if typed == '': + return None, '--' matches = [] for key in animals: if key.startswith(typed): @@ -35,9 +35,9 @@ def loadDescription(self, typed): if len(matches) == 1: return matches[0], animals[matches[0]] elif len(matches) > 1: - return None, u"(Multiple found)" + return None, "(Multiple found)" else: - return None, u'--' + return None, '--' athena.expose(loadDescription) class DataEntry(rend.Page): @@ -75,7 +75,7 @@ def animals(self, animal, description): return url.here def data_animals(self, ctx, data): - return animals.keys() + return list(animals.keys()) def child_typeahead(self, ctx): return TypeAheadPage(None, None) diff --git a/examples/athenademo/widgets.py b/examples/athenademo/widgets.py index 8669f948..02a74188 100644 --- a/examples/athenademo/widgets.py +++ b/examples/athenademo/widgets.py @@ -7,7 +7,7 @@ from nevow import athena, loaders, static class Clock(athena.LiveFragment): - jsClass = u"WidgetDemo.Clock" + jsClass = "WidgetDemo.Clock" docFactory = loaders.xmlstr('''\