diff --git a/docs/source/conf.py b/docs/source/conf.py index 159b168..983a943 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -61,28 +61,3 @@ ], "genindex": ["links.html", "sourcelink.html", "searchbox.html"], } - -# Avoid issues like below when running under python 3.x: -# -# Expected: -# [u'hello', u'world'] -# -# Got: -# ['hello', 'world'] - -import re -import sys -import doctest - -OrigOutputChecker = doctest.OutputChecker - - -class Py23OutputChecker(OrigOutputChecker): - def check_output(self, want, got, optionflags): - if sys.version_info[0] > 2: - want = re.sub("u'(.*?)'", "'\\1'", want) - want = re.sub('u"(.*?)"', '"\\1"', want) - return OrigOutputChecker.check_output(self, want, got, optionflags) - - -doctest.OutputChecker = Py23OutputChecker diff --git a/docs/source/intro.rst b/docs/source/intro.rst index 7d41c52..c295b15 100644 --- a/docs/source/intro.rst +++ b/docs/source/intro.rst @@ -65,7 +65,7 @@ blank, using default values, or with values taken from your objects. form = SignInForm.from_flat(request.POST) if form.validate(): - logging.info(u"sign-in: %s" % form['username'].value) + logging.info("sign-in: %s" % form['username'].value) redirect('/app/') else: render('login.html', form=form) @@ -81,6 +81,6 @@ used as-is in output templates for form layout, redisplay and error reporting. >>> isinstance(as_regular_python_data, dict) True >>> as_regular_python_data['username'] - u'jek' + 'jek' >>> form2 = SignInForm(as_regular_python_data) >>> assert form['username'].value == form2['username'].value diff --git a/docs/source/markup.rst b/docs/source/markup.rst index d1e0a3b..fc0fa57 100644 --- a/docs/source/markup.rst +++ b/docs/source/markup.rst @@ -178,7 +178,7 @@ participate in ``tabindex=`` generation. >>> print(html.textarea(auto_tabindex=True)) >>> html.set(auto_tabindex=True) - u'' + '' >>> print(html.textarea()) diff --git a/docs/source/schema/lists.rst b/docs/source/schema/lists.rst index d96fc7e..bcee0d8 100644 --- a/docs/source/schema/lists.rst +++ b/docs/source/schema/lists.rst @@ -46,15 +46,15 @@ Example: >>> from flatland import List, String >>> Names = List.named('names').of(String.named('name')) - >>> names = Names([u'a', u'b']) + >>> names = Names(['a', 'b']) >>> names.value - [u'a', u'b'] + ['a', 'b'] >>> names.flatten() - [(u'names_0_name', u'a'), (u'names_1_name', u'b')] + [('names_0_name', 'a'), ('names_1_name', 'b')] >>> names[1].value - u'b' + 'b' >>> names.find_one('1').value - u'b' + 'b' Validation ---------- diff --git a/docs/source/schema/schema.rst b/docs/source/schema/schema.rst index 3d92694..d46e5d9 100644 --- a/docs/source/schema/schema.rst +++ b/docs/source/schema/schema.rst @@ -15,7 +15,7 @@ types: .. testcode:: fso from flatland import Dict, String - SearchSchema = Dict.named('search').of(String.named(u'keywords')) + SearchSchema = Dict.named('search').of(String.named('keywords')) .. TODO:: FIXME UPDATE: @@ -29,9 +29,9 @@ types: .. doctest:: fso :options: +ELLIPSIS - >>> form = SearchSchema({u'keywords': u'foo bar baz'}) + >>> form = SearchSchema({'keywords': 'foo bar baz'}) >>> form.value - {u'keywords': u'foo bar baz'} + {'keywords': 'foo bar baz'} .. TODO:: FIXME UPDATE: @@ -42,10 +42,10 @@ types: >>> from flatland import List >>> ComposedSchema = Dict.of(SearchSchema, - ... List.named(u'many_searches').of(SearchSchema)) + ... List.named('many_searches').of(SearchSchema)) >>> form = ComposedSchema() >>> sorted(form.value.keys()) - [u'many_searches', u'search'] + ['many_searches', 'search'] .. TODO:: FIXME UPDATE: diff --git a/docs/source/schema/traversal.rst b/docs/source/schema/traversal.rst index 4ddd52f..684ebc4 100644 --- a/docs/source/schema/traversal.rst +++ b/docs/source/schema/traversal.rst @@ -25,7 +25,7 @@ has a bit of variety in its structure. 'location': {'x': 10, 'y': 20}, } - ann1 = Annotation(sample_data, name=u'ann1') + ann1 = Annotation(sample_data, name='ann1') Going Raw @@ -38,7 +38,7 @@ application. An element's :attr:`~base.Element.value` is a full & recursive .. doctest:: >>> ann1['title'] # ann1 is a flatland structure - + >>> isinstance(ann1.value, dict) # but its .value is not True >>> ann1.value == sample_data @@ -59,12 +59,12 @@ For example, ``Form`` and ``Dict`` can be indexed and used like ``dict``: .. doctest:: >>> ann1['title'].value - u'Interesting Spot' + 'Interesting Spot' >>> ann1['location']['x'].value 10 >>> sorted(ann1['location'].items()) - [(u'x', ), (u'y', )] - >>> u'title' in ann1 + [('x', ), ('y', )] + >>> 'title' in ann1 True And ``List`` and similar types can be used like lists: @@ -108,9 +108,9 @@ to related elements: :attr:`~base.Element.root`, >>> list(ann1['title'].children) # title is a String and has no children [] >>> sorted(el.name for el in ann1.all_children if el.name) - [u'flags', u'location', u'title', u'x', u'y'] + ['flags', 'location', 'title', 'x', 'y'] >>> [el.name for el in ann1['location']['x'].parents] - [u'location', u'ann1'] + ['location', 'ann1'] Each of these properties (excepting ``root``) returns an iterator of elements. @@ -128,28 +128,28 @@ use when authoring flexible & reusable validators. .. doctest:: >>> ann1.find('title') # find 'ann1's child named 'title' - [] + [] Paths are evaluated relative to the element: .. doctest:: >>> ann1['location'].find('x') - [] + [] Referencing parents is possible with ``..``: .. doctest:: >>> ann1['location']['x'].find('../../title') - [] + [] Absolute paths begin with a ``/``. .. doctest:: >>> ann1['location']['x'].find('/title') - [] + [] Members of sequences can be selected like any other child (their index number is their name), or you can use Python-like slicing: @@ -181,7 +181,7 @@ needed to illustrate this: >>> p = Points([[dict(x=1, y=1), dict(x=2, y=2)], ... [dict(x=3, y=3)]]) >>> p.find('[:][:]/x') - [, , ] + [, , ] The equivalent straight Python to select the same set of elements is quite a bit more wordy. diff --git a/src/flatland/_compat.py b/src/flatland/_compat.py deleted file mode 100644 index 89fc060..0000000 --- a/src/flatland/_compat.py +++ /dev/null @@ -1,98 +0,0 @@ -import sys - -__all__ = [ - "PY2", - "builtins", - "bytestring_type", - "getattr_py2", - "hasattr_py2", - "identifier_transform", - "iterkeys", - "iteritems", - "itervalues", - "long_type", - "setattr_py2", - "string_types", - "text_type", - "with_metaclass", - "xrange", -] - - -PY2 = sys.version_info[0] == 2 - -if PY2: - import __builtin__ as builtins - - text_type = unicode - bytestring_type = str - long_type = long - - def identifier_transform(identifier): - if isinstance(identifier, unicode): - return identifier.encode("ascii") - else: - return identifier - - # for simple, purposeful conversions ala ``unicode(2)`` that - # should be allowed by the text suite's unicode coercion detector - def text_transform(object): - if isinstance(object, unicode): - return object - elif hasattr(object, "__unicode__"): - return object.__unicode__() - else: - return str(object).decode("ascii", "strict") - - def hasattr_py2(obj, attr): - attr = identifier_transform(attr) - return hasattr(obj, attr) - - def getattr_py2(obj, attr, *default): - # quietly downgrade u'attribute' to b'attribute' - attr = identifier_transform(attr) - if default: - return getattr(obj, attr, default[0]) - else: - return getattr(obj, attr) - - def setattr_py2(obj, attr, value): - attr = identifier_transform(attr) - setattr(obj, attr, value) - - iterkeys = lambda d: d.iterkeys() - itervalues = lambda d: d.itervalues() - iteritems = lambda d: d.iteritems() - xrange = xrange -else: - import builtins - - text_type = str - bytestring_type = bytes - long_type = int - identifier_transform = lambda i: i - text_transform = str - getattr_py2 = getattr - hasattr_py2 = hasattr - setattr_py2 = setattr - iterkeys = lambda d: iter(d.keys()) - itervalues = lambda d: iter(d.values()) - iteritems = lambda d: iter(d.items()) - xrange = range - -string_types = (bytestring_type, text_type) - - -def with_metaclass(meta, *bases): - # From flask, MIT License - # https://github.com/mitsuhiko/flask/blob/master/flask/_compat.py - class metaclass(meta): - __call__ = type.__call__ - __init__ = type.__init__ - - def __new__(cls, name, this_bases, d): - if this_bases is None: - return type.__new__(cls, name, (), d) - return meta(name, bases, d) - - return metaclass("temporary_class", None, {}) diff --git a/src/flatland/out/generic.py b/src/flatland/out/generic.py index e6b4e40..bd64a99 100644 --- a/src/flatland/out/generic.py +++ b/src/flatland/out/generic.py @@ -1,6 +1,5 @@ import re -from flatland._compat import PY2, bytestring_type, iteritems, text_type from flatland.out.util import parse_trool from flatland.schema import Array, Boolean from flatland.util import Maybe, to_pairs @@ -48,9 +47,7 @@ def __getitem__(self, key): def __setitem__(self, key, value): if key not in self: - raise KeyError( - f"{key!r} not permitted in this {self.__class__.__name__}" - ) + raise KeyError(f"{key!r} not permitted in this {self.__class__.__name__}") self._frames[-1][key] = value def __contains__(self, key): @@ -65,16 +62,14 @@ def update(self, *iterable, **kwargs): source = to_pairs(iterable[0]) for key, value in source: self[key] = value - for key, value in iteritems(kwargs): - if PY2: - key = key.decode("ascii") + for key, value in kwargs.items(): self[key] = value def __repr__(self): return f"{self.__class__.__name__}({self._frames[-1]!r})" -class Markup(text_type): +class Markup(str): """A string of HTML markup that should not be escaped in output.""" __slots__ = () @@ -162,7 +157,7 @@ def transform_value(tagname, attributes, contents, context, bind): current = attributes.get("value") if current is not None: value = current - elif isinstance(contents, text_type): + elif isinstance(contents, str): value = contents.strip() elif contents is None: value = "" @@ -231,7 +226,7 @@ def transform_tabindex(tagname, attributes, contents, context, bind): current = attributes.get("tabindex") if forced or current is None and tagname in _auto_tags["tabindex"]: - attributes["tabindex"] = text_type(str(tabindex)) + attributes["tabindex"] = str(tabindex) if tabindex > 0: context["tabindex"] = tabindex + 1 return contents @@ -310,9 +305,9 @@ def _sanitize_domid_suffix(string): def _unpack(html_string): """Extract HTML from a __html__() interface.""" unpacked = html_string.__html__() - if unpacked.__class__ is text_type: + if unpacked.__class__ is str: return unpacked - return text_type(unpacked) + return str(unpacked) def _markup_escape(string): diff --git a/src/flatland/out/genshi.py b/src/flatland/out/genshi.py index 852aee5..7847df7 100644 --- a/src/flatland/out/genshi.py +++ b/src/flatland/out/genshi.py @@ -12,7 +12,6 @@ from genshi.template.directives import Directive from genshi.template.interpolation import interpolate -from flatland._compat import bytestring_type, iteritems, text_type from flatland.out.generic import _unpack, transform, Context __all__ = ("setup",) @@ -125,7 +124,7 @@ def process(self, stream, directives, ctxt, vars): def inject(self, mapping, ctxt, vars): """Inject the translated key and interpolated value into *mapping*.""" raw = self.raw_value - if raw.__class__ is text_type: + if raw.__class__ is str: final_value = raw else: parts = [] @@ -134,7 +133,7 @@ def inject(self, mapping, ctxt, vars): parts.append(value) else: value = _eval_expr(value, ctxt, vars) - parts.append(text_type(value)) + parts.append(str(value)) final_value = "".join(parts) mapping[_to_context.get(self._name, self._name)] = final_value @@ -280,7 +279,7 @@ def _rewrite_stream(stream, directives, ctxt, vars, bind): existing_attributes = {} for qname, value in attrs: if qname.namespace is None: - if not isinstance(value, text_type): + if not isinstance(value, str): value = _simplify_stream(value, ctxt, vars) attrs |= ((qname, value),) existing_attributes[qname.localname] = qname @@ -297,10 +296,10 @@ def _rewrite_stream(stream, directives, ctxt, vars, bind): if new_contents is None: new_contents = () - elif isinstance(new_contents, text_type): + elif isinstance(new_contents, str): new_contents = [(TEXT, new_contents, (None, -1, -1))] - pairs = sorted(iteritems(mutable_attrs), key=_attribute_sort_key) + pairs = sorted(mutable_attrs.items(), key=_attribute_sort_key) for attribute_name, value in pairs: if attribute_name in existing_attributes: qname = existing_attributes.pop(attribute_name) @@ -377,15 +376,15 @@ def _simplify_stream(stream, ctxt, vars): while hasattr(value, "__next__") or hasattr(value, "next"): value = list(value) value = _simplify_stream(value, ctxt, vars) - if not isinstance(value, text_type): + if not isinstance(value, str): stream[idx : idx + 1] = value return stream else: stream[idx] = (TEXT, value, pos) - elif isinstance(value, bytestring_type): + elif isinstance(value, bytes): value = value.decode("utf8", "replace") - elif not isinstance(value, text_type): - value = text_type(value) + elif not isinstance(value, str): + value = str(value) parts.append(value) else: return stream diff --git a/src/flatland/out/markup.py b/src/flatland/out/markup.py index 398770a..ac10dd4 100644 --- a/src/flatland/out/markup.py +++ b/src/flatland/out/markup.py @@ -1,6 +1,5 @@ from collections import defaultdict -from flatland._compat import PY2, bytestring_type, iteritems, text_type from flatland.out.generic import Context, transform, _unpack from flatland.out.util import parse_trool @@ -67,8 +66,6 @@ def set(self, **settings): """ for key, value in settings.items(): - if PY2: - key = key.decode("ascii") if key not in self: raise TypeError("%r is not a valid argument." % key) if key.startswith("auto_"): @@ -194,8 +191,8 @@ def tag(self, tagname, bind=None, **attributes): example, ``tag('input')`` is equivalent to ``input()``. """ - if isinstance(tagname, bytestring_type): # pragma: nocover - tagname = text_type(tagname) + if isinstance(tagname, bytes): # pragma: nocover + tagname = str(tagname) tagname = tagname.lower() if bind is None and not attributes: return self._tag(tagname) @@ -269,7 +266,7 @@ def _open(self, bind, kwargs): if self._context["ordered_attributes"]: pairs = sorted(attributes.items(), key=_attribute_sort_key) else: - pairs = iteritems(attributes) + pairs = attributes.items() guts = " ".join(f'{k}="{_attribute_escape(v)}"' for k, v in pairs) if guts: return "<" + tagname + " " + guts @@ -317,8 +314,6 @@ def _attribute_escape(string): def _transform_keys(d): rekeyed = {} for key, value in d.items(): - if PY2: - key = key.decode("ascii") key = key.rstrip("_") rekeyed[key] = value return rekeyed diff --git a/src/flatland/out/util.py b/src/flatland/out/util.py index e48f07b..4c1c18e 100644 --- a/src/flatland/out/util.py +++ b/src/flatland/out/util.py @@ -1,4 +1,3 @@ -from flatland._compat import bytestring_type from flatland.util import Maybe YES = ("1", "true", "True", "t", "on", "yes") @@ -9,7 +8,7 @@ def parse_trool(value): if value is True or value is False or value is Maybe: return value - if isinstance(value, bytestring_type): + if isinstance(value, bytes): value = value.decode("utf8", "replace") value = value.lower() if value in YES: diff --git a/src/flatland/schema/base.py b/src/flatland/schema/base.py index 1ec2bb4..6ce7822 100644 --- a/src/flatland/schema/base.py +++ b/src/flatland/schema/base.py @@ -2,7 +2,6 @@ import itertools import operator -from flatland._compat import PY2, bytestring_type, iteritems, text_type from flatland.schema.paths import pathexpr from flatland.schema.properties import Properties from flatland.signals import validator_validated @@ -168,8 +167,8 @@ def named(cls, name): :returns: a new class """ - if not isinstance(name, (text_type, NoneType)): - name = text_type(name) + if not isinstance(name, (str, NoneType)): + name = str(name) cls.name = name return cls @@ -192,7 +191,7 @@ def using(cls, **overrides): if not isinstance(overrides["properties"], Properties): overrides["properties"] = Properties(overrides["properties"]) - for attribute, value in iteritems(overrides): + for attribute, value in overrides.items(): # TODO: must make better if callable(value): value = staticmethod(value) @@ -396,17 +395,17 @@ def fq_name(self): :attr:`Element.root` (``/``) down to the element. >>> from flatland import Dict, Integer - >>> Point = Dict.named(u'point').of(Integer.named(u'x'), - ... Integer.named(u'y')) + >>> Point = Dict.named('point').of(Integer.named('x'), + ... Integer.named('y')) >>> p = Point(dict(x=10, y=20)) >>> p.name - u'point' + 'point' >>> p.fq_name() - u'/' + '/' >>> p['x'].name - u'x' + 'x' >>> p['x'].fq_name() - u'/x' + '/x' The index used in a path may not be the :attr:`.name` of the element. For example, sequence members are referenced by their @@ -414,15 +413,15 @@ def fq_name(self): >>> from flatland import List, String >>> Addresses = List.named('addresses').of(String.named('address')) - >>> form = Addresses([u'uptown', u'downtown']) + >>> form = Addresses(['uptown', 'downtown']) >>> form.name - u'addresses' + 'addresses' >>> form.fq_name() - u'/' + '/' >>> form[0].name - u'address' + 'address' >>> form[0].fq_name() - u'/0' + '/0' """ if self.parent is None: @@ -489,9 +488,9 @@ class Profile(Schema): >>> cities = form.find('/contact/addresses[:]/city') >>> [el.value for el in cities] - [u'Kingsport', u'Dunwich'] + ['Kingsport', 'Dunwich'] >>> form.find('/contact/name', single=True) - + """ expr = pathexpr(path) @@ -501,10 +500,8 @@ class Profile(Schema): elif not results: return None elif len(results) > 1 and strict: - display_path = repr(path).lstrip("u") raise LookupError( - "Path %s matched multiple elements; single " - "result expected." % display_path + "Path %r matched multiple elements; single result expected." % path ) else: return results[0] @@ -544,11 +541,11 @@ def flattened_name(self, sep="_"): >>> form = flatland.List('addresses', ... flatland.String('address')) >>> element = form() - >>> element.set([u'uptown', u'downtown']) + >>> element.set(['uptown', 'downtown']) >>> element[0].value - u'uptown' + 'uptown' >>> element['0'].flattened_name() - u'addresses_0_address' + 'addresses_0_address' """ return sep.join(parent.name for parent in self.path if parent.name is not None) @@ -572,25 +569,25 @@ def flatten(self, sep="_", value=operator.attrgetter("u")): >>> from flatland import Schema, Dict, String >>> class Nested(Schema): - ... contact = Dict.of(String.named(u'name'), - ... Dict.named(u'address'). - ... of(String.named(u'email'))) + ... contact = Dict.of(String.named('name'), + ... Dict.named('address'). + ... of(String.named('email'))) ... >>> element = Nested() >>> element.flatten() - [(u'contact_name', u''), (u'contact_address_email', u'')] + [('contact_name', ''), ('contact_address_email', '')] The value of each pair can be customized with the *value* callable:: >>> element.flatten(value=operator.attrgetter('u')) - [(u'contact_name', u''), (u'contact_address_email', u'')] + [('contact_name', ''), ('contact_address_email', '')] >>> element.flatten(value=lambda el: el.value) - [(u'contact_name', None), (u'contact_address_email', None)] + [('contact_name', None), ('contact_address_email', None)] Solo elements will return a sequence containing a single pair:: >>> element['name'].flatten() - [(u'contact_name', u'')] + [('contact_name', '')] """ if self.flattenable: @@ -617,32 +614,32 @@ def set(self, obj): native value of None will be represented as u'' in :attr:`u`. If adaptation fails, :attr:`value` will be ``None`` and :attr:`u` will - contain ``str(value)`` (or unicode), or ``u''`` for None. + contain ``str(value)``, or ``''`` for None. >>> from flatland import Integer >>> el = Integer() >>> el.u, el.value - (u'', None) + ('', None) >>> el.set('123') True >>> el.u, el.value - (u'123', 123) + ('123', 123) >>> el.set(456) True >>> el.u, el.value - (u'456', 456) + ('456', 456) >>> el.set('abc') False >>> el.u, el.value - (u'abc', None) + ('abc', None) >>> el.set(None) True >>> el.u, el.value - (u'', None) + ('', None) """ raise NotImplementedError() diff --git a/src/flatland/schema/compound.py b/src/flatland/schema/compound.py index 006ec41..4656a98 100644 --- a/src/flatland/schema/compound.py +++ b/src/flatland/schema/compound.py @@ -1,12 +1,6 @@ from functools import wraps, reduce import operator -from flatland._compat import ( - PY2, - identifier_transform, - string_types, - with_metaclass, -) from flatland.exc import AdaptationError from flatland.signals import element_set from flatland.util import ( @@ -77,10 +71,7 @@ def __call__(cls, value=Unspecified, **kw): def _wrap_compound_init(fn): """Decorate __compound_init__ with a status setter & classmethod.""" if isinstance(fn, classmethod): - if PY2: - fn = fn.__get__(str).__func__ # type doesn't matter here - else: - fn = fn.__func__ + fn = fn.__func__ @wraps(fn) def __compound_init__(cls): @@ -91,7 +82,7 @@ def __compound_init__(cls): return classmethod(__compound_init__) -class Compound(with_metaclass(_MetaCompound, Mapping, Scalar)): +class Compound(Mapping, Scalar, metaclass=_MetaCompound): """A mapping container that acts like a scalar value. Compound fields are dictionary-like fields that can assemble a @@ -256,9 +247,7 @@ def explode(self, value): value = Date.adapt(self, value) for attrib, child_schema in zip(self.used, self.field_schema): - self[child_schema.name].set( - getattr(value, identifier_transform(attrib)) - ) + self[child_schema.name].set(getattr(value, attrib)) except (AdaptationError, TypeError): for child_schema in self.field_schema: self[child_schema.name].set(None) @@ -274,12 +263,12 @@ class JoinedString(Array, String): >>> from flatland import JoinedString >>> el = JoinedString(['x', 'y', 'z']) >>> el.value - u'x,y,z' + 'x,y,z' >>> el2 = JoinedString('foo,bar') >>> el2[1].value - u'bar' + 'bar' >>> el2.value - u'foo,bar' + 'foo,bar' Only the joined representation is considered when flattening or restoring with :meth:`set_flat`. JoinedStrings run validation after their children. @@ -303,7 +292,7 @@ class JoinedString(Array, String): #: ... separator_regex=re.compile('\s*,\s*')) #: ... #: >>> schema('a , b,c,d').value - #: u'a, b, c, d' + #: 'a, b, c, d' separator_regex = None #: The default child type is :class:`~flatland.schema.scalars.String`, @@ -318,7 +307,7 @@ def set(self, value): self.raw = value if isinstance(value, (list, tuple)): values = value - elif not isinstance(value, string_types): + elif not isinstance(value, (str, bytes)): values = list(value) elif self.separator_regex: # a text regexp separator diff --git a/src/flatland/schema/containers.py b/src/flatland/schema/containers.py index b502e82..d5649a8 100644 --- a/src/flatland/schema/containers.py +++ b/src/flatland/schema/containers.py @@ -1,16 +1,6 @@ from collections import defaultdict import re -from flatland._compat import ( - PY2, - identifier_transform, - iteritems, - iterkeys, - itervalues, - bytestring_type, - text_type, - xrange, -) from flatland.util import ( Unspecified, assignable_class_property, @@ -19,7 +9,6 @@ keyslice_pairs, re_uescape, to_pairs, - decode_repr, ) from flatland.signals import element_set from .base import Element, Unevaluated, Slot, validate_element @@ -178,7 +167,7 @@ def of(cls, *schema): >>> el = Names(['Bob', 'Biff']) >>> el - [, ] + [, ] If more than one `~flatland.schema.base.Element` is specified in *\*schema*, an anonymous :class:`Dict` is created to hold them. @@ -192,9 +181,9 @@ def of(cls, *schema): >>> el = Points([dict(x=1, y=2)]) >>> point = el[0] >>> point['x'] - + >>> point['y'] - + """ for field in schema: @@ -379,7 +368,7 @@ def value(self): @property def u(self): return "[%s]" % ", ".join( - element.u if isinstance(element, Container) else decode_repr(element.u) + element.u if isinstance(element, Container) else repr(element.u) for element in self.children ) @@ -451,11 +440,9 @@ def _as_element(self, value): def _new_slot(self, value=Unspecified): """Wrap *value* in a Slot named as the element's index in the list.""" - # avoid direct text_type() here so that test suite unicode coercion - # detector isn't triggered new_idx = len(self) name = str(new_idx) - if not isinstance(name, text_type): + if not isinstance(name, str): name = name.decode("ascii") return self.slot_type(name=name, parent=self, element=self._as_element(value)) @@ -529,11 +516,7 @@ def reverse(self): def _renumber(self): for idx, slot in enumerate(self._slots): - # don't trigger naive unicode coercion (for test suite) - name = str(idx) - if not isinstance(name, text_type): - name = name.decode("ascii") - slot.name = name + slot.name = str(idx) @property def children(self): @@ -590,7 +573,7 @@ def _set_flat(self, pairs, sep): # schema-configured maximum. flat + python indexes match. else: max_index = min(max(indexes) + 1, self.maximum_set_flat_members) - for index in xrange(0, max_index): + for index in range(0, max_index): slot = self._new_slot() list.append(self, slot) flat = indexes.get(index, None) @@ -617,7 +600,7 @@ def set_default(self): del self[:] if isinstance(default, int): - for _ in xrange(0, default): + for _ in range(0, default): slot = self._new_slot() list.append(self, slot) slot.element.set_default() @@ -796,7 +779,7 @@ def update(self, *dictish, **kwargs): elif dictish: for key, value in to_pairs(dictish[0]): self[key] = value - for key, value in iteritems(kwargs): + for key, value in kwargs.items(): self[key] = value def setdefault(self, key, default=None): @@ -816,7 +799,7 @@ def get(self, key, default=None): @property def children(self): # order not guaranteed - return itervalues(self) + return self.values() def set(self, value): """.. TODO:: doc set()""" @@ -835,7 +818,7 @@ def set(self, value): ) converted &= self[key].set(value) seen.add(key) - required = set(iterkeys(self)) + required = set(self.keys()) if seen != required: missing = required - seen raise TypeError( @@ -891,15 +874,15 @@ def _index(self, name): def u(self): """A string repr of the element.""" pairs = ( - (key, value.u if isinstance(value, Container) else decode_repr(value.u)) - for key, value in iteritems(self) + (key, value.u if isinstance(value, Container) else repr(value.u)) + for key, value in self.items() ) - return "{%s}" % ", ".join(f"{decode_repr(k)}: {v}" for k, v in pairs) + return "{%s}" % ", ".join(f"{repr(k)}: {v}" for k, v in pairs) @property def value(self): """The element as a regular Python dictionary.""" - return {key: value.value for key, value in iteritems(self)} + return {key: value.value for key, value in self.items()} @property def is_empty(self): @@ -1002,9 +985,7 @@ def set(self, value, policy=None): if policy is None: policy = self.policy if policy not in ("strict", "subset", "duck", None): - raise RuntimeError( - f"Unknown {self.__class__.__name__} policy {policy!r}" - ) + raise RuntimeError(f"Unknown {self.__class__.__name__} policy {policy!r}") if policy == "strict": missing, extra = _evaluate_dict_strict_policy(self, pairs) @@ -1039,8 +1020,6 @@ def set(self, value, policy=None): fields = self.field_schema_mapping converted = True for key, value in pairs: - if PY2 and isinstance(key, bytestring_type): - key = key.decode("ascii", "replace") if key not in fields: continue if dict.__contains__(self, key): @@ -1097,18 +1076,18 @@ def __init__(self, login=None, password=None): >>> form = UserForm() >>> form.set_by_object(user) >>> form['login'].value - u'biff' - >>> form['password'] = u'new-password' + 'biff' + >>> form['password'] = 'new-password' >>> form.update_object(user, omit=['verify_password']) >>> user.password - u'new-password' + 'new-password' >>> user_keywords = form.slice(omit=['verify_password'], key=str) >>> sorted(user_keywords.keys()) ['login', 'password'] >>> new_user = User(**user_keywords) """ - fields = set(iterkeys(self)) + fields = set(self.keys()) attributes = fields.copy() if rename: rename = list(to_pairs(rename)) @@ -1127,9 +1106,7 @@ def __init__(self, login=None, password=None): final = {key: value for key, value in sliced if key in fields} self.set(final) - def update_object( - self, obj, include=None, omit=None, rename=None, key=identifier_transform - ): + def update_object(self, obj, include=None, omit=None, rename=None, key=lambda x: x): """Update an object's attributes using the element's values. Produces a :meth:`slice` using *include*, *omit*, *rename* and @@ -1140,12 +1117,12 @@ def update_object( """ data = self.slice(include=include, omit=omit, rename=rename, key=key) - for attribute, value in iteritems(data): + for attribute, value in data.items(): setattr(obj, attribute, value) def slice(self, include=None, omit=None, rename=None, key=None): """Return a ``dict`` containing a subset of the element's values.""" - pairs = ((key, element.value) for key, element in sorted(iteritems(self))) + pairs = ((key, element.value) for key, element in sorted(self.items())) sliced = keyslice_pairs( pairs, include=include, omit=omit, rename=rename, key=key ) @@ -1250,7 +1227,9 @@ def pop(self, key): def setdefault(self, key, default=None): if not self.may_contain(key): raise TypeError( - "Key {!r} not allowed in {} {!r}".format(key, type(self).__name__, self.name) + "Key {!r} not allowed in {} {!r}".format( + key, type(self).__name__, self.name + ) ) if key in self: @@ -1264,7 +1243,7 @@ def setdefault(self, key, default=None): @property def is_empty(self): - for _ in iterkeys(self): + for _ in self: return False return True @@ -1289,8 +1268,6 @@ def set_default(self): def _textset(iterable): values = set() for value in iterable: - if PY2 and isinstance(value, bytestring_type): - value = value.decode("ascii", "replace") values.add(value) return values diff --git a/src/flatland/schema/declarative.py b/src/flatland/schema/declarative.py index c62a8d5..7204aa4 100644 --- a/src/flatland/schema/declarative.py +++ b/src/flatland/schema/declarative.py @@ -1,6 +1,5 @@ """Class attribute-style declarative schema construction.""" -from flatland._compat import PY2, with_metaclass from .base import Element from .containers import Dict, SparseDict @@ -38,13 +37,9 @@ def __new__(self, class_name, bases, members): # add / replace fields declared as attributes on this class declared_fields = [] for name, value in list(members.items()): - if PY2: - text_name = name.decode("ascii") - else: - text_name = name if isinstance(value, type) and issubclass(value, Element): - if text_name != value.name: - value = value.named(text_name) + if name != value.name: + value = value.named(name) declared_fields.append(value) del members[name] fields.add_and_overwrite(declared_fields) @@ -81,7 +76,7 @@ def add_and_overwrite(self, iterable): self.elements.append(field) -class Schema(with_metaclass(_MetaSchema, Dict)): +class Schema(Dict, metaclass=_MetaSchema): """A declarative collection of named elements. Schemas behave like |Dict|, but are defined with Python class syntax: @@ -106,7 +101,7 @@ class Schema(with_metaclass(_MetaSchema, Dict)): ... >>> helloworld = HelloSchema() >>> sorted(helloworld.keys()) - [u'hello', u'world'] + ['hello', 'world'] Schemas may embed other container fields and other schemas: @@ -138,14 +133,14 @@ class attributes. They are removed from the class dictionary and placed >>> hasattr(HelloSchema, 'hello') False >>> sorted([field.name for field in HelloSchema.field_schema]) - [u'hello', u'world'] + ['hello', 'world'] The order of ``field_schema`` is undefined. """ -class SparseSchema(with_metaclass(_MetaSchema, SparseDict)): +class SparseSchema(SparseDict, metaclass=_MetaSchema): """A sparse variant of `~flatland.schema.declarative.Schema`. Exactly as ``Schema``, but based upon @@ -154,5 +149,5 @@ class SparseSchema(with_metaclass(_MetaSchema, SparseDict)): """ -class Form(with_metaclass(_MetaSchema, Dict)): +class Form(Dict, metaclass=_MetaSchema): """An alias for Schema, for older flatland version compatibility.""" diff --git a/src/flatland/schema/paths.py b/src/flatland/schema/paths.py index b5cab97..6878570 100644 --- a/src/flatland/schema/paths.py +++ b/src/flatland/schema/paths.py @@ -1,7 +1,6 @@ import re -from flatland._compat import PY2, bytestring_type, text_type, xrange -from flatland.util import decode_repr, symbol +from flatland.util import symbol __all__ = ["pathexpr"] @@ -41,9 +40,7 @@ def pathexpr(expr): if isinstance(expr, PathExpression): return expr - elif PY2 and isinstance(expr, bytestring_type): - expr = text_type(expr) - elif isinstance(expr, text_type): + elif isinstance(expr, str): pass elif hasattr(expr, "__iter__"): expr = "/".join(expr) @@ -68,7 +65,7 @@ def __call__(self, element, strict=False): contexts = [(self.ops, element)] for _ops, el in contexts: - for idx in xrange(len(_ops)): + for idx in range(len(_ops)): op, data = _ops[idx] if op is TOP: el = el.root @@ -84,13 +81,13 @@ def __call__(self, element, strict=False): if strict: if el.name: type_ = "{} element {}".format( - el.__class__.__name__, decode_repr(el.name) + el.__class__.__name__, repr(el.name) ) else: type_ = "Unnamed element %s" % (el.__class__.__name__) raise LookupError( "{} has no child {} in expression {}".format( - type_, decode_repr(data), decode_repr(self.expr) + type_, repr(data), repr(self.expr) ) ) break @@ -103,8 +100,6 @@ def __call__(self, element, strict=False): return found def __str__(self): - if PY2: - return self.expr.encode("utf8") return self.expr def __unicode__(self): diff --git a/src/flatland/schema/properties.py b/src/flatland/schema/properties.py index 66af70a..c9f1589 100644 --- a/src/flatland/schema/properties.py +++ b/src/flatland/schema/properties.py @@ -1,6 +1,5 @@ from weakref import WeakKeyDictionary -from flatland._compat import iteritems from flatland.util import symbol Deleted = symbol("deleted") @@ -8,23 +7,14 @@ class DictLike: - def iteritems(self): # pragma: nocover + def items(self): # pragma: nocover raise NotImplementedError - def items(self): - return list(self.iteritems()) - - def iterkeys(self): - return (item[0] for item in self.iteritems()) - def keys(self): - return list(self.iterkeys()) - - def itervalues(self): - return (item[1] for item in self.iteritems()) + return (item[0] for item in self.items()) def values(self): - return list(self.itervalues()) + return (item[1] for item in self.items()) def get(self, key, default=None): try: @@ -33,13 +23,13 @@ def get(self, key, default=None): return default def copy(self): - return dict(self.iteritems()) + return dict(self.items()) def popitem(self): raise NotImplementedError def __contains__(self, key): - return key in self.iterkeys() + return key in self.keys() def __bool__(self): return bool(self.copy()) @@ -85,7 +75,7 @@ def __delitem__(self, key): def clear(self): frame = self._base_frame - for key in self.iterkeys(): + for key in self.keys(): frame[key] = Deleted def pop(self, key, *default): @@ -105,10 +95,10 @@ def update(self, *iterable, **values): simplified = dict(*iterable, **values) self._base_frame.update(simplified) - def iteritems(self): + def items(self): seen = set() for frame in self._frames(): - for key, value in iteritems(frame): + for key, value in frame.items(): if key not in seen: seen.add(key) if value is not Deleted: @@ -195,13 +185,13 @@ def update(self, *iterable, **values): simplified = dict(*iterable, **values) self.local.update(simplified) - def iteritems(self): + def items(self): seen = set() - for key, value in iteritems(self.local): + for key, value in self.local.items(): seen.add(key) if value is not Deleted: yield key, value - for key, value in self.class_lookup.iteritems(): + for key, value in self.class_lookup.items(): if key not in seen: seen.add(key) if value is not Deleted: # pragma: nocover (coverage bug) diff --git a/src/flatland/schema/scalars.py b/src/flatland/schema/scalars.py index 5757cda..147b012 100644 --- a/src/flatland/schema/scalars.py +++ b/src/flatland/schema/scalars.py @@ -2,13 +2,6 @@ import decimal import re -from flatland._compat import ( - PY2, - long_type, - string_types, - text_type, - text_transform, -) from flatland.exc import AdaptationError from flatland.signals import element_set from flatland.util import ( @@ -71,7 +64,7 @@ def set(self, obj): of ``None`` will be represented as ``u''`` in ``.u``. If adaptation fails, ``.value`` will be ``None`` and ``.u`` will - contain ``str(obj)`` (or unicode), or ``u''`` for none. + contain ``str(obj)``, or ``''`` for none. """ self.raw = obj @@ -83,15 +76,15 @@ def set(self, obj): # but, still try to textify it if obj is None: self.u = "" - elif isinstance(obj, text_type): + elif isinstance(obj, str): self.u = obj else: try: - self.u = text_transform(obj) + self.u = str(obj) except TypeError: self.u = "" except UnicodeDecodeError: - self.u = text_type(obj, errors="replace") + self.u = str(obj, errors="replace") element_set.send(self, adapted=False) return False @@ -123,10 +116,10 @@ def serialize(self, obj): compatible type. This semi-abstract method is called by :meth:`set`. The base - implementation returns ``str(obj)`` (or unicode). + implementation returns ``str(obj)``. """ - return text_transform(obj) + return str(obj) def _index(self, name): raise IndexError(name) @@ -148,8 +141,6 @@ def __bool__(self): __nonzero__ = __bool__ def __str__(self): - if PY2: - return self.u.encode("utf8", "replace") return self.u def __unicode__(self): @@ -178,8 +169,8 @@ def adapt(self, value): """ if value is None: return None - if not isinstance(value, text_type): - value = text_transform(value) + if not isinstance(value, str): + value = str(value) if self.strip: return value.strip() @@ -197,8 +188,8 @@ def serialize(self, value): """ if value is None: return "" - if not isinstance(value, text_type): - value = text_transform(value) + if not isinstance(value, str): + value = str(value) if self.strip: return value.strip() @@ -238,7 +229,7 @@ def adapt(self, value): """ if value is None: return None - if isinstance(value, string_types): + if isinstance(value, (str, bytes)): value = value.strip() # decimal.Decimal doesn't like whitespace try: native = self.type_(value) @@ -253,9 +244,8 @@ def adapt(self, value): def serialize(self, value): """Generic numeric serialization. - :returns: Unicode text formatted with :attr:`format` or the - ``str()`` (or unicode) of *value* if *value* is not of - :attr:`type_` + :returns: Text formatted with :attr:`format` or the + ``str()`` of *value* if *value* is not of :attr:`type_` Converts *value* to a string using Python's string formatting function and the :attr:`format` as the template. The *value* is provided to @@ -264,7 +254,7 @@ def serialize(self, value): """ if type(value) is self.type_: return self.format % value - return text_transform(value) + return str(value) class Integer(Number): @@ -274,17 +264,17 @@ class Integer(Number): """``int``""" format = "%i" - """``u'%i'``""" + """``'%i'``""" class Long(Number): - """Element type for Python's long.""" + """Element type for Python's integer.""" - type_ = long_type - """``long``, or ``int`` on Python 3.""" + type_ = int + """``int``""" format = "%i" - """``u'%i'``""" + """``'%i'``""" class Float(Number): @@ -294,7 +284,7 @@ class Float(Number): """``float``""" format = "%f" - """``u'%f'``""" + """``'%f'``""" class Decimal(Number): @@ -304,28 +294,28 @@ class Decimal(Number): """``decimal.Decimal``""" format = "%f" - """``u'%f'``""" + """``'%f'``""" class Boolean(Scalar): """Element type for Python's ``bool``.""" true = "1" - """The text serialization for ``True``: ``u'1'``.""" + """The text serialization for ``True``: ``'1'``.""" true_synonyms = ("on", "true", "True", "1") """A sequence of acceptable string equivalents for True. - Defaults to ``(u'on', u'true', u'True', u'1')`` + Defaults to ``('on', 'true', 'True', '1')`` """ false = "" - """The text serialization for ``False``: ``u''``.""" + """The text serialization for ``False``: ``''``.""" false_synonyms = ("off", "false", "False", "0", "") """A sequence of acceptable string equivalents for False. - Defaults to ``(u'off', u'false', u'False', u'0', u'')`` + Defaults to ``('off', 'false', 'False', '0', '')`` """ def adapt(self, value): @@ -340,7 +330,7 @@ def adapt(self, value): For non-text values, equivalent to ``bool(value)``. """ - if not isinstance(value, text_type): + if not isinstance(value, str): return bool(value) elif value == self.true or value in self.true_synonyms: return True @@ -379,11 +369,11 @@ class Constrained(Scalar): ... >>> schema = Constrained.using(child_type=Integer, valid_value=is_valid) >>> element = schema() - >>> element.set(u'2') + >>> element.set('2') True >>> element.value 2 - >>> element.set(u'5') + >>> element.set('5') False >>> element.value is None True @@ -481,7 +471,7 @@ def adapt(self, value): return value elif isinstance(value, self.type_): return value - elif isinstance(value, string_types): + elif isinstance(value, (str, bytes)): if self.strip: value = value.strip() match = self.regex.match(value) @@ -499,14 +489,13 @@ def serialize(self, value): """Serializes value to string. If *value* is an instance of :attr:`type`, formats it as described in - the attribute documentation. Otherwise returns ``str(value)`` (or - unicode). + the attribute documentation. Otherwise returns ``str(value)``. """ if isinstance(value, self.type_): return self.format % as_mapping(value) else: - return text_transform(value) + return str(value) class DateTime(Temporal): diff --git a/src/flatland/schema/util.py b/src/flatland/schema/util.py index 75be7da..721477b 100644 --- a/src/flatland/schema/util.py +++ b/src/flatland/schema/util.py @@ -1,6 +1,6 @@ import itertools -from flatland._compat import builtins +import builtins def element_ancestry(element): diff --git a/src/flatland/util.py b/src/flatland/util.py index fad2bd1..5b9a2df 100644 --- a/src/flatland/util.py +++ b/src/flatland/util.py @@ -7,19 +7,6 @@ except ImportError: # pragma:nocover import dummy_threading as threading -from flatland._compat import PY2, text_type - - -def decode_repr(x): - """create a unicode string representation (as a unicode string) - for py2 and py3 that looks the same: u'example' - """ - r = repr(x) - if PY2: - return r.decode("raw_unicode_escape") - else: - return "u" + r - # derived from ASPN Cookbook (#36302) class lazy_property: @@ -183,18 +170,11 @@ def __init__(self, target): def __getitem__(self, item): try: - if PY2 and isinstance(item, text_type): - return getattr(self.target, item.encode("ascii")) return getattr(self.target, item) except (AttributeError, UnicodeError): raise KeyError(item) def __contains__(self, item): - if PY2 and isinstance(item, text_type): - try: - return hasattr(self.target, item.encode("ascii")) - except UnicodeError: - return False return hasattr(self.target, item) def __iter__(self): @@ -216,9 +196,7 @@ def re_ucompile(pattern, flags=0): return re.compile(pattern, flags | re.UNICODE) -_alphanum = set( - text_type(string.digits + string.ascii_lowercase + string.ascii_uppercase) -) +_alphanum = set(str(string.digits + string.ascii_lowercase + string.ascii_uppercase)) def re_uescape(pattern): @@ -240,12 +218,12 @@ def to_pairs(dictlike): "dictlike". """ - if hasattr(dictlike, "iteritems"): - return dictlike.iteritems() + if hasattr(dictlike, "items"): + return dictlike.items() elif hasattr(dictlike, "keys"): return ((key, dictlike[key]) for key in dictlike.keys()) elif hasattr(dictlike, "_asdict"): # namedtuple interface - return dictlike._asdict().iteritems() + return dictlike._asdict().items() else: return ((key, value) for key, value in dictlike) @@ -269,8 +247,8 @@ def keyslice_pairs(pairs, include=None, omit=None, rename=None, key=None): :param key: optional, a function of one argument that is used to extract a comparison key from the first item of each pair. Similar to the - ``key`` parameter to Python's ``sort`` and ``sorted``. Useful for - transforming unicode keys to bytestrings with ```key=str``, adding or + ``key`` parameter to Python's ``sort`` and ``sorted``. + Useful for transforming keys to other types ```key=type``, adding or removing prefixes en masse, etc. :returns: yields ``(key, value)`` pairs. diff --git a/src/flatland/validation/base.py b/src/flatland/validation/base.py index 58cdf7f..243d053 100644 --- a/src/flatland/validation/base.py +++ b/src/flatland/validation/base.py @@ -2,7 +2,6 @@ from operator import attrgetter -from flatland._compat import getattr_py2, hasattr_py2, iteritems, setattr_py2 from flatland.schema.util import find_i18n_function N_ = lambda translatable: translatable @@ -21,9 +20,9 @@ def __init__(self, **kw): """ cls = type(self) - for attr, value in iteritems(kw): - if hasattr_py2(cls, attr): - setattr_py2(self, attr, value) + for attr, value in kw.items(): + if hasattr(cls, attr): + setattr(self, attr, value) else: raise TypeError( "{} has no attribute {!r}, can not override.".format( @@ -108,7 +107,7 @@ def validate(self, element, state): assert el.errors == ['Oh noes!'] """ - message = message or getattr_py2(self, key) + message = message or getattr(self, key) if message: element.add_error(self.expand_message(element, state, message, **info)) return False @@ -143,7 +142,7 @@ def note_warning(self, element, state, key=None, message=None, **info): Always returns False. """ - message = message or getattr_py2(self, key) + message = message or getattr(self, key) if message: element.add_warning(self.expand_message(element, state, message, **info)) return False @@ -182,8 +181,8 @@ def find_transformer(self, type, element, state, message): 5. Otherwise return ``None``. """ - if hasattr_py2(state, type): - return getattr_py2(state, type) + if hasattr(state, type): + return getattr(state, type) if hasattr(state, "__getitem__"): try: return state[type] @@ -280,8 +279,6 @@ def __init__(self, *targets, **kw): raise TypeError("unexpected keyword argument") def __getitem__(self, item): - __hopeless_morass_of_unicode_hell__ = True - for target in self.targets: # try target[item] first if hasattr(target, "__getitem__"): @@ -292,7 +289,7 @@ def __getitem__(self, item): pass # then target.item try: - value = getattr_py2(target, item) + value = getattr(target, item) break except AttributeError: pass diff --git a/src/flatland/validation/network.py b/src/flatland/validation/network.py index 5811b3f..04964c1 100644 --- a/src/flatland/validation/network.py +++ b/src/flatland/validation/network.py @@ -1,14 +1,9 @@ """Network address and URL validation.""" -from flatland._compat import PY2 import re -if PY2: - import urlparse -else: - from urllib import parse as urlparse +from urllib import parse as urlparse -from flatland._compat import identifier_transform, text_transform from .base import N_, Validator @@ -173,11 +168,10 @@ def validate(self, element, state): except Exception: return self.note_error(element, state, "bad_format") - scheme_name = identifier_transform(url.scheme) - if scheme_name == "": + if url.scheme == "": return self.note_error(element, state, "blocked_scheme") elif self.allowed_schemes != ("*",): - if scheme_name not in self.allowed_schemes: + if url.scheme not in self.allowed_schemes: return self.note_error(element, state, "blocked_scheme") for part in _url_parts: @@ -271,7 +265,7 @@ def validate(self, element, state): try: value = getattr(parsed, part) if part == "port": - value = None if value is None else text_transform(value) + value = None if value is None else str(value) except ValueError: return self.note_error(element, state, "bad_format") required = self.required_parts.get(part) diff --git a/tests/__init__.py b/tests/__init__.py index ce973a4..8b13789 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1 @@ -# TODO: switch this on with an environ variable or something. -# and document. -# def setup_package(): -# import tests._util -# tests._util.enable_coercion_blocker() + diff --git a/tests/_util.py b/tests/_util.py deleted file mode 100644 index dcbae81..0000000 --- a/tests/_util.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Test suite helpers.""" - -import codecs -from contextlib import contextmanager -from functools import wraps -from inspect import stack -import sys - -from flatland._compat import long_type, text_type - -__all__ = [ - "asciistr", - "fails", - "requires_unicode_coercion", - "udict", - "unicode_coercion_available", -] - - -# acts like 'str', but safe to use when tests are running with -# sys.getdefaultencoding() == 'nocoercion'. -_ascii_codec = codecs.getencoder("ascii") -asciistr = lambda s: _ascii_codec(s)[0] -# acts like naive unicode() on simple types like int -textstr = lambda o: text_type(str(o)) - -_coercion_override = None - - -def fails(reason): - """Mark a test case as expected to fail for *reason*.""" - - def decorator(fn): - @wraps(fn) - def decorated(*args, **kw): - try: - fn(*args, **kw) - except (SystemExit, KeyboardInterrupt): - raise - except: - pass # ok - else: - raise AssertionError("Unexpected success.") - - return decorated - - return decorator - - -def udict(*dictionary, **kwargs): - "Return a dict with unicode keys. A stand-in for the dict constructor." - kwargs = {text_type(k): v for k, v in kwargs.items()} - if dictionary: - base = {text_type(k): v for k, v in dictionary[0].items()} - base.update(kwargs) - return base - else: - return kwargs - - -def unicode_coercion_available(): - return sys.getdefaultencoding() != "nocoercion" - - -def requires_unicode_coercion(fn): - @wraps(fn) - def decorated(*args, **kw): - if sys.getdefaultencoding() != "nocoercion": - return fn(*args, **kw) - try: - fn(*args, **kw) - except UnicodeError: - # failure is success! - return - else: - raise AssertionError("Test did not trigger expected UnicodeError.") - - return decorated - - -@contextmanager -def unicode_coercion_allowed(): - global _coercion_override - initial_value = _coercion_override - try: - _coercion_override = True - yield - finally: - _coercion_override = initial_value - - -def _allowed_coercion(input): - if _coercion_override: - return True - # TODO: this isn't hit anymore (buffer comes in). did it ever work? - if isinstance(input, (int, float, long_type, type(None))): - return True - - try: - caller = stack()[2] - if "__hopeless_morass_of_unicode_hell__" in caller[0].f_locals: - return True - - calling_path = caller[1] - if "/" in calling_path: - calling_file = calling_path.rsplit("/", 1)[1] - else: - calling_file = calling_path - - if calling_file in ("sre_parse.py", "decimal.py", "urlparse.py"): - return True - elif "/_pytest/" in calling_path: - return True - elif "genshi" in calling_path and "out/genshi" not in calling_path: - # OMG slow on genshi 0.5.2 - return True - # this does lots of expected '%s' formatting e.g. unicode(2) - elif "flatland/validation" in calling_path and caller[3] == "expand_message": - return True - return False - finally: - del caller - - -class NoCoercionCodec(codecs.Codec): - - def encode(self, input, errors="string"): - if _allowed_coercion(input): - return codecs.ascii_encode(input, errors) - raise UnicodeError("encoding coercion blocked") - - def decode(self, input, errors="strict"): - if _allowed_coercion(input): - return codecs.ascii_decode(input, errors) - raise UnicodeError("encoding coercion blocked") - - -class _IncrementalEncoder(codecs.IncrementalEncoder): - - def encode(self, input, final=False): - if _allowed_coercion(input): - return codecs.ascii_encode(input, self.errors)[0] - raise UnicodeError("encoding coercion blocked") - - -class _IncrementalDecoder(codecs.IncrementalDecoder): - - def decode(self, input, final=False): - if _allowed_coercion(input): - return codecs.ascii_decode(input, self.errors)[0] - raise UnicodeError("encoding coercion blocked") - - -class _StreamWriter(NoCoercionCodec, codecs.StreamWriter): - pass - - -class _StreamReader(NoCoercionCodec, codecs.StreamReader): - pass - - -def enable_coercion_blocker(): - registration = lambda name: codecs.CodecInfo( - name="nocoercion", - encode=NoCoercionCodec().encode, - decode=NoCoercionCodec().decode, - incrementalencoder=_IncrementalEncoder, - incrementaldecoder=_IncrementalDecoder, - streamwriter=_StreamWriter, - streamreader=_StreamReader, - ) - codecs.register(registration) - reload(sys) - sys.setdefaultencoding("nocoercion") diff --git a/tests/markup/_util.py b/tests/markup/_util.py index ff229f0..5291180 100644 --- a/tests/markup/_util.py +++ b/tests/markup/_util.py @@ -3,8 +3,6 @@ import pytest -from flatland._compat import bytestring_type - class Capabilities(dict): @@ -70,7 +68,7 @@ def __init__(self, language, schema, **kw): def __call__(self, fn): self.expected = inspect.cleandoc(fn.__doc__).strip() - if isinstance(self.expected, bytestring_type): + if isinstance(self.expected, bytes): self.expected = self.expected.decode("utf8") self.alternate_expectations = getattr(fn, "alternates", {}) return self diff --git a/tests/markup/test_transforms.py b/tests/markup/test_transforms.py index 3cd330d..7f493c4 100644 --- a/tests/markup/test_transforms.py +++ b/tests/markup/test_transforms.py @@ -2,8 +2,6 @@ from flatland.out import generic from flatland.out.generic import Context -from tests._util import unicode_coercion_allowed, textstr - Unspecified = object() Unique = object() schema = Integer.named("number") @@ -477,12 +475,9 @@ def test_value_option(): ) # This matches value = contents via a sentinel object - with unicode_coercion_allowed(): - given = {} - expected = {} - assert_transform( - generic.transform_value, "option", given, expected, bind=bind - ) + given = {} + expected = {} + assert_transform(generic.transform_value, "option", given, expected, bind=bind) contents = expected_contents = "123" given = { @@ -638,7 +633,7 @@ def test_tabindex_stop_numbers(): for stop_num in -1, -2: context = Context() context["tabindex"] = stop_num - expected = {"tabindex": textstr(stop_num)} + expected = {"tabindex": str(stop_num)} assert_bound_transform( generic.transform_tabindex, "input", given, expected, context=context ) diff --git a/tests/schema/test_arrays.py b/tests/schema/test_arrays.py index b19b3b0..ca9a92c 100644 --- a/tests/schema/test_arrays.py +++ b/tests/schema/test_arrays.py @@ -7,7 +7,6 @@ ) import pytest -from tests._util import udict def test_set_flat_pruned(): @@ -160,7 +159,7 @@ def test_mutation(): el.pop() assert el.value == ["f"] assert el[0].u == "f" - assert el.u == "[u'f']" + assert el.u == "['f']" del el[:] assert list(el) == [] diff --git a/tests/schema/test_base.py b/tests/schema/test_base.py index 44d172d..8cf4a55 100644 --- a/tests/schema/test_base.py +++ b/tests/schema/test_base.py @@ -5,10 +5,8 @@ SkipAllFalse, Unevaluated, ) -from flatland._compat import xrange import pytest -from tests._util import requires_unicode_coercion def test_cloning(): @@ -18,7 +16,6 @@ def test_cloning(): assert "test_base" in new_element.__module__ -@requires_unicode_coercion def test_naming(): for arg in ("unicode", "sysencoding", None): schema = Element.named(arg) @@ -40,11 +37,11 @@ def test_validators(): el = Element(validators=(123, 456)) assert el.validators == [123, 456] - el = Element(validators=xrange(3)) - assert el.validators == list(xrange(3)) + el = Element(validators=range(3)) + assert el.validators == list(range(3)) - schema = Element.using(validators=xrange(3)) - assert schema.validators == list(xrange(3)) + schema = Element.using(validators=range(3)) + assert schema.validators == list(range(3)) def test_dsl_validated_by(): diff --git a/tests/schema/test_constrained.py b/tests/schema/test_constrained.py index 949cf6c..74299e4 100644 --- a/tests/schema/test_constrained.py +++ b/tests/schema/test_constrained.py @@ -3,7 +3,6 @@ Enum, Integer, ) -from flatland._compat import text_transform def test_constrained_no_default_validity(): @@ -102,9 +101,9 @@ def test_typed_enum(): for good_val in good_values: el = schema() - assert el.set(text_transform(good_val)) + assert el.set(str(good_val)) assert el.value == good_val - assert el.u == text_transform(good_val) + assert el.u == str(good_val) assert not el.errors el = schema() diff --git a/tests/schema/test_declarative.py b/tests/schema/test_declarative.py index d59e076..6e85681 100644 --- a/tests/schema/test_declarative.py +++ b/tests/schema/test_declarative.py @@ -4,10 +4,7 @@ String, ) -from tests._util import requires_unicode_coercion - -@requires_unicode_coercion def test_from_object(): class Obj: @@ -66,7 +63,6 @@ class Outer(Schema): assert el.value == wanted -@requires_unicode_coercion def test_inheritance_straight(): class Base(Schema): @@ -85,7 +81,6 @@ class Sub(Base): assert set(Sub().keys()) == {"base_member", "added_member"} -@requires_unicode_coercion def test_inheritance_diamond(): class A(Schema): diff --git a/tests/schema/test_dicts.py b/tests/schema/test_dicts.py index 8d3f4ca..0ffb553 100644 --- a/tests/schema/test_dicts.py +++ b/tests/schema/test_dicts.py @@ -6,15 +6,9 @@ Unset, element_set, ) -from flatland._compat import PY2, iteritems from flatland.util import Unspecified, keyslice_pairs import pytest -from tests._util import ( - asciistr, - udict, - unicode_coercion_available, -) def test_dict(): @@ -77,41 +71,30 @@ def test_dict_update(): el = schema() def value_dict(element): - return {k: v.value for k, v in iteritems(element)} + return {k: v.value for k, v in element.items()} - try: - el.update(x=20, y=30) - except UnicodeError: - assert not unicode_coercion_available() - el.update(udict(x=20, y=30)) - assert udict(x=20, y=30) == el.value + el.update(x=20, y=30) + assert {"x": 20, "y": 30} == el.value el.update({"y": 40}) - assert udict(x=20, y=40) == el.value + assert {"x": 20, "y": 40} == el.value el.update() - assert udict(x=20, y=40) == el.value + assert {"x": 20, "y": 40} == el.value el.update((_, 100) for _ in "xy") - assert udict(x=100, y=100) == el.value - - try: - el.update([("x", 1)], y=2) - assert udict(x=1, y=2) == el.value - except UnicodeError: - assert not unicode_coercion_available() - - try: - el.update([("x", 10), ("y", 10)], x=20, y=20) - assert udict(x=20, y=20) == el.value - except UnicodeError: - assert not unicode_coercion_available() - - if unicode_coercion_available(): - with pytest.raises(TypeError): - el.update(z=1) - with pytest.raises(TypeError): - el.update(x=1, z=1) + assert {"x": 100, "y": 100} == el.value + + el.update([("x", 1)], y=2) + assert {"x": 1, "y": 2} == el.value + + el.update([("x", 10), ("y", 10)], x=20, y=20) + assert {"x": 20, "y": 20} == el.value + + with pytest.raises(TypeError): + el.update(z=1) + with pytest.raises(TypeError): + el.update(x=1, z=1) with pytest.raises(TypeError): el.update({"z": 1}) with pytest.raises(TypeError): @@ -198,7 +181,7 @@ def test_full_set(self): assert el.value == wanted el = self.new_element() - el.set(udict(x=101, y=102)) + el.set({"x": 101, "y": 102}) assert el.value == wanted el = self.new_element() @@ -372,7 +355,7 @@ def test_dict_as_unicode(): schema = Dict.of(Integer.named("x"), Integer.named("y")) el = schema({"x": 1, "y": 2}) - assert el.u in ("{u'x': u'1', u'y': u'2'}", "{u'y': u'2', u'x': u'1'}") + assert el.u in ("{'x': '1', 'y': '2'}", "{'y': '2', 'x': '1'}") def test_nested_dict_as_unicode(): @@ -380,7 +363,7 @@ def test_nested_dict_as_unicode(): el = schema.from_defaults() assert el.value == {"d": {"x": 10}} - assert el.u == "{u'd': {u'x': u'10'}}" + assert el.u == "{'d': {'x': '10'}}" def test_nested_unicode_dict_as_unicode(): @@ -389,7 +372,7 @@ def test_nested_unicode_dict_as_unicode(): ) el = schema.from_defaults() assert el.value == {"d": {"x": "\u2308\u2309"}} - assert el.u == "{u'd': {u'x': u'\u2308\u2309'}}" + assert el.u == "{'d': {'x': '\u2308\u2309'}}" def test_dict_find(): @@ -420,7 +403,7 @@ def __init__(self, **kw): def updated_(obj_factory, initial_value, wanted=None, **update_kw): el = schema(initial_value) obj = obj_factory() - keyfunc = lambda x: (asciistr(x) if PY2 else x) + keyfunc = lambda x: x update_kw.setdefault("key", keyfunc) el.update_object(obj, **update_kw) if wanted is None: @@ -449,7 +432,7 @@ def same_(source, kw): assert {type(_) for _ in sliced.keys()} == {type(_) for _ in wanted.keys()} same_({"x": "X", "y": "Y"}, {}) - same_({"x": "X", "y": "Y"}, dict(key=asciistr)) + same_({"x": "X", "y": "Y"}, dict(key=lambda s: s.encode("ascii"))) same_({"x": "X", "y": "Y"}, dict(include=["x"])) same_({"x": "X", "y": "Y"}, dict(omit=["x"])) same_({"x": "X", "y": "Y"}, dict(omit=["x"], rename={"y": "z"})) diff --git a/tests/schema/test_lists.py b/tests/schema/test_lists.py index b9cd738..d8f0b83 100644 --- a/tests/schema/test_lists.py +++ b/tests/schema/test_lists.py @@ -5,7 +5,6 @@ Unset, element_set, ) -from flatland._compat import xrange, text_type from flatland.schema.base import Unspecified @@ -272,7 +271,7 @@ def test_set(): assert el.value == [0, 1, 2] el = schema() - assert el.set(xrange(3)) + assert el.set(range(3)) assert el.value == [0, 1, 2] el = schema([0, 1, 2]) @@ -373,7 +372,7 @@ def test_mutation(): def order_ok(): slot_names = list(_.name for _ in el._slots) for idx, name in enumerate(slot_names): - assert name == text_type(str(idx)) + assert name == str(idx) assert not el order_ok() @@ -476,7 +475,7 @@ def test_slots(): def test_u(): schema = List.of(String) el = schema(["x", "x"]) - assert el.u == "[u'x', u'x']" + assert el.u == "['x', 'x']" def test_value(): diff --git a/tests/schema/test_paths.py b/tests/schema/test_paths.py index 38f959d..66fd9a6 100644 --- a/tests/schema/test_paths.py +++ b/tests/schema/test_paths.py @@ -201,15 +201,15 @@ def test_find_strict_messaging(): el = Mixed.from_defaults() message = _find_message(el, "bogus") - expected = "Unnamed element Mixed has no child u'bogus' " "in expression u'bogus'" + expected = "Unnamed element Mixed has no child 'bogus' " "in expression 'bogus'" assert expected in message message = _find_message(el, "d1/bogus") - expected = "Dict element u'd1' has no child u'bogus' " "in expression u'd1/bogus'" + expected = "Dict element 'd1' has no child 'bogus' " "in expression 'd1/bogus'" assert expected in message message = _find_message(el, "l1[5]") - expected = "List element u'l1' has no child u'5' " "in expression u'l1[5]'" + expected = "List element 'l1' has no child '5' " "in expression 'l1[5]'" assert expected in message diff --git a/tests/schema/test_properties.py b/tests/schema/test_properties.py index bd0995f..9782c8f 100644 --- a/tests/schema/test_properties.py +++ b/tests/schema/test_properties.py @@ -1,5 +1,4 @@ from flatland import String -from flatland._compat import PY2, iterkeys, itervalues from flatland.schema.properties import Properties import pytest @@ -42,10 +41,8 @@ class Base: assert sorted(props.items()) == [("abc", 123), ("def", 456)] assert sorted(props.keys()) == ["abc", "def"] - assert sorted(iterkeys(props)) == ["abc", "def"] assert sorted(props.values()) == [123, 456] - assert sorted(itervalues(props)) == [123, 456] assert props.get("abc") == 123 assert props.get("abc", "blah") == 123 @@ -284,21 +281,6 @@ class Broken: assert Broken.properties == "something else" -# python3 immediately raises an exception if there is such a name clash -if PY2: - - def test_perverse_slots(): - - class Base: - __slots__ = ("properties",) - properties = Properties() - - b = Base() - with pytest.raises(AttributeError): - # noinspection PyStatementEffect - b.properties["abc"] - - def test_dsl(): Sub = String.with_properties(abc=123) diff --git a/tests/schema/test_scalars.py b/tests/schema/test_scalars.py index 75b4b25..0d4388c 100644 --- a/tests/schema/test_scalars.py +++ b/tests/schema/test_scalars.py @@ -15,10 +15,8 @@ Unset, element_set, ) -from flatland._compat import PY2, long_type import pytest -from tests._util import requires_unicode_coercion def test_scalar_abstract(): @@ -112,9 +110,6 @@ def validate_element_set(type_, raw, value, uni, schema_opts={}, set_return=None assert element.__bool__() == bool(uni and value) -coerced_validate_element_set = requires_unicode_coercion(validate_element_set) - - def test_scalar_set(): # a variety of scalar set() failure cases, shoved through Integer for spec in ( @@ -123,11 +118,6 @@ def test_scalar_set(): ): validate_element_set(Integer, *spec) - if PY2: - # TODO: test below fails on py3 and it is unclear what it is about. - for spec in (("\xef\xf0", None, "\ufffd\ufffd"),): - coerced_validate_element_set(Integer, *spec) - def test_scalar_set_signal(): data = [] @@ -168,7 +158,7 @@ def test_integer(): def test_long(): - L = long_type + L = int # Python 3 for spec in ( ("123", L(123), "123"), (" 123 ", L(123), "123"), diff --git a/tests/test_i18n.py b/tests/test_i18n.py index f9d837b..6ed0e69 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -98,7 +98,7 @@ def test_builtin_gettext(): try: # translators can go into the builtins - from flatland._compat import builtins + import builtins builtins.ugettext = catalog.ugettext builtins.ungettext = catalog.ungettext diff --git a/tests/validation/test_containers.py b/tests/validation/test_containers.py index b860eaa..46322f4 100644 --- a/tests/validation/test_containers.py +++ b/tests/validation/test_containers.py @@ -13,7 +13,6 @@ SetWithKnownFields, ) -from tests._util import unicode_coercion_allowed import pytest @@ -54,23 +53,20 @@ def _test_no_duplicates(schema, a, b): el = schema([a, b, a]) assert not el.validate() assert valid_of_children(el) == [True, True, False] - with unicode_coercion_allowed(): - assert el[2].errors == [f"{label} {3}"] + assert el[2].errors == [f"{label} {3}"] el = schema([a, b, a, b]) assert not el.validate() assert valid_of_children(el) == [True, True, False, False] - with unicode_coercion_allowed(): - assert el[2].errors == [f"{label} {3}"] - assert el[3].errors == [f"{label} {4}"] + assert el[2].errors == [f"{label} {3}"] + assert el[3].errors == [f"{label} {4}"] el = schema([a, a, a, a]) assert not el.validate() assert valid_of_children(el) == [True, False, False, False] - with unicode_coercion_allowed(): - assert el[1].errors == [f"{label} {2}"] - assert el[2].errors == [f"{label} {3}"] - assert el[3].errors == [f"{label} {4}"] + assert el[1].errors == [f"{label} {2}"] + assert el[2].errors == [f"{label} {3}"] + assert el[3].errors == [f"{label} {4}"] def test_no_duplicates_list_scalar(): diff --git a/tests/validation/test_network.py b/tests/validation/test_network.py index 8235272..8c4c0d5 100644 --- a/tests/validation/test_network.py +++ b/tests/validation/test_network.py @@ -9,8 +9,6 @@ ) from flatland.validation.network import _url_parts -from tests._util import unicode_coercion_allowed - def email(value): return String(value, name="email", strip=False) @@ -41,8 +39,7 @@ def test_email(): def test_email_idna(): - with unicode_coercion_allowed(): - assert_email_valid("bob@snow\u2603man.com") + assert_email_valid("bob@snow\u2603man.com") def test_email_non_local(): diff --git a/tests/validation/test_scalars.py b/tests/validation/test_scalars.py index 74a3d7e..a13f182 100644 --- a/tests/validation/test_scalars.py +++ b/tests/validation/test_scalars.py @@ -22,8 +22,6 @@ ValuesEqual, ) -from tests._util import unicode_coercion_allowed - def form(value): @@ -88,8 +86,7 @@ def test_value_less_than(): assert V(date.today() + timedelta(days=2)).validate(d, None) two_days_ago = date.today() - timedelta(days=2) assert not V(two_days_ago).validate(d, None) - with unicode_coercion_allowed(): - assert d.errors == ["test must be less than %s." % two_days_ago] + assert d.errors == ["test must be less than %s." % two_days_ago] def test_value_at_most():