diff --git a/boltons/ioutils.py b/boltons/ioutils.py index 76c52d3c..bfcaf3fc 100644 --- a/boltons/ioutils.py +++ b/boltons/ioutils.py @@ -277,8 +277,6 @@ def __del__(self): except Exception: pass - __nonzero__ = __bool__ - class SpooledBytesIO(SpooledIOBase): """ diff --git a/boltons/namedutils.py b/boltons/namedutils.py index f88adaba..0311f814 100644 --- a/boltons/namedutils.py +++ b/boltons/namedutils.py @@ -203,7 +203,7 @@ def namedtuple(typename, field_names, verbose=False, rename=False): try: exec(class_definition, namespace) except SyntaxError as e: - raise SyntaxError(e.message + ':\n' + class_definition) + raise SyntaxError(e.msg + ':\n' + class_definition) result = namespace[typename] # For pickling to work, the __module__ variable needs to be set to the frame @@ -368,7 +368,7 @@ def _itemsetter(obj, value): try: exec(class_definition, namespace) except SyntaxError as e: - raise SyntaxError(e.message + ':\n' + class_definition) + raise SyntaxError(e.msg + ':\n' + class_definition) result = namespace[typename] # For pickling to work, the __module__ variable needs to be set to diff --git a/boltons/setutils.py b/boltons/setutils.py index 44df4f3d..3ed5ab32 100644 --- a/boltons/setutils.py +++ b/boltons/setutils.py @@ -220,7 +220,10 @@ def __repr__(self): def __eq__(self, other): if isinstance(other, IndexedSet): return len(self) == len(other) and list(self) == list(other) - return set(self) == set(other) + try: + return set(self) == set(other) + except TypeError: + return False @classmethod def from_iterable(cls, it): @@ -964,4 +967,3 @@ def __bool__(self): return bool(self._included) return True - __nonzero__ = __bool__ # py2 compat diff --git a/boltons/tbutils.py b/boltons/tbutils.py index b3a5159d..3146544e 100644 --- a/boltons/tbutils.py +++ b/boltons/tbutils.py @@ -477,7 +477,7 @@ def _populate_context_lines(self, pivot=8): DL, lineno = _DeferredLine, self.lineno try: module_globals = self.line.module_globals - except Exception: + except AttributeError: module_globals = None start_line = max(0, lineno - pivot) pre_lines = [DL(self.module_path, ln, module_globals) diff --git a/boltons/txurl_notes.md b/boltons/txurl_notes.md deleted file mode 100644 index bdd84979..00000000 --- a/boltons/txurl_notes.md +++ /dev/null @@ -1,60 +0,0 @@ -# Notes on url - -* aha: absolute path and absolute URI are different. the ambiguity - tripped me up. -* still, absolute() doesn't require host, i don't think -* doctest in child() doesn't have "d", only "c" -* could try moving off urlparse onto urlutils functions -* what would a mutable api look like? -* queryparamsdict has its own independent applications -* no mention of family (ip hosts?) -* percentDecode includes a decode call with utf8 hardcoded -* if a percent-encoded item fails to decode, and stays percent - encoded, then what happens when you re-encode? It doesn't get - double-percent encoded because of the safe + '%' on line 71? -* asURI says "decoded", shouldn't it say "encoded"? - -and more: - -* so a URL does not know if it's an IRI or a URI. -* In the current API there's no way to go from a URI with non-UTF8 - percent encodings to a fully-decoded IRI -* probably shouldn't even bother with path params (even though i think - it's supported by stdlib by urllib.splitattr and - urlparse._splitparams and maybe elsewhere) -* is fragment really quotable? -* better to have path as list or path as string -* for username:password with an empty password, does the colon really still - have to be present? ยง3.2.1 suggests yes -* Invalid URLs can have invalid IPv6 constants or invalid port strings - (including on txurl an empty port) -* is userinfo percent encoded or idna encoded? -* interesting how IDNA is only mentioned <5 times in RFC3986 -* surprising lack of error handling on .fromText(), given that it's - the most common dev interface -* per _percentDecode if a string can't be encoded to ascii, then it - won't be unquoted. - - -# inno - -* path should be a string, because the "rooted" thing is awkward to - work with. also because slash-separation doesn't apply to URNs and - other URL use cases. -* userinfo as a string is a bit dangerous. database connection strings - still use username:password, and the password might need to be - escaped. expecting the user to join them safely can lead to issues. -* could have a better error message on non-ascii encoded bytestrings - passed into fromText() (right now it gives a codec error on path - split) -* with_port to force port to be in the output, even if it's the default -* larger default port map -* the new URL carries with it a more usable API, with the possible - limitation that you can't create a URL without knowing its - underlying encoding. While URLs may technically be able to store - binary data, I did not find any instances of this in the - wild. Binary data in URLs is usually represented with base64-encoded - data. -* TODO: what's up with uses_netloc (e.g., urn is not urn://a:b:c, but - rather urn:a:b:c) -* _url.URL(u'lol0l0lxyz:asdfk@bcd') silently makes a URL obj with a scheme diff --git a/boltons/typeutils.py b/boltons/typeutils.py index d01e567e..fc25a229 100644 --- a/boltons/typeutils.py +++ b/boltons/typeutils.py @@ -85,11 +85,9 @@ def __repr__(self): def __reduce__(self): return self.var_name - def __nonzero__(self): + def __bool__(self): return False - __bool__ = __nonzero__ - if var_name: frame = sys._getframe(1) module = frame.f_globals.get('__name__') diff --git a/boltons/urlutils.py b/boltons/urlutils.py index 0494d8d3..d509f983 100644 --- a/boltons/urlutils.py +++ b/boltons/urlutils.py @@ -56,10 +56,6 @@ from unicodedata import normalize unicode = str -try: - unichr -except NameError: - unichr = chr # The unreserved URI characters (per RFC 3986 Section 2.3) _UNRESERVED_CHARS = frozenset('~-._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -74,8 +70,8 @@ _HEX_CHAR_MAP = {(a + b).encode('ascii'): - unichr(int(a + b, 16)).encode('charmap') - for a in string.hexdigits for b in string.hexdigits} + chr(int(a + b, 16)).encode('charmap') + for a in string.hexdigits for b in string.hexdigits} _ASCII_RE = re.compile('([\x00-\x7f]+)') @@ -136,7 +132,7 @@ def to_unicode(obj): # regex from gruber via tornado # doesn't support ipv6 # doesn't support mailto (netloc-less schemes) -_FIND_ALL_URL_RE = re.compile(to_unicode(r"""\b((?:([\w-]+):(/{1,3})|www[.])(?:(?:(?:[^\s&()<>]|&|")*(?:[^!"#$%'()*+,.:;<=>?@\[\]^`{|}~\s]))|(?:\((?:[^\s&()]|&|")*\)))+)""")) +_FIND_ALL_URL_RE = re.compile(r"""\b((?:([\w-]+):(/{1,3})|www[.])(?:(?:(?:[^\s&()<>]|&|")*(?:[^!"#$%'()*+,.:;<=>?@\[\]^`{|}~\s]))|(?:\((?:[^\s&()]|&|")*\)))+)""") def find_all_links(text, with_text=False, default_scheme='https', schemes=()): @@ -1005,7 +1001,7 @@ class OrderedMultiDict(dict): * stacking data from multiple dictionaries in a non-destructive way The OrderedMultiDict constructor is identical to the built-in - :class:`dict`, and overall the API is constitutes an intuitive + :class:`dict`, and overall the API constitutes an intuitive superset of the built-in type: >>> omd = OrderedMultiDict() @@ -1038,17 +1034,11 @@ class OrderedMultiDict(dict): >>> omd OrderedMultiDict([('b', 2)]) - Note that calling :func:`dict` on an OMD results in a dict of keys - to *lists* of values: + If you want a safe-to-modify or flat dictionary, use + :meth:`OrderedMultiDict.todict()`. - >>> from pprint import pprint as pp # ensuring proper key ordering + >>> from pprint import pprint as pp # preserve printed ordering >>> omd = OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)]) - >>> pp(dict(omd)) - {'a': 3, 'b': 2} - - Note that modifying those lists will modify the OMD. If you want a - safe-to-modify or flat dictionary, use :meth:`OrderedMultiDict.todict()`. - >>> pp(omd.todict()) {'a': 3, 'b': 2} >>> pp(omd.todict(multi=True)) @@ -1061,19 +1051,43 @@ class OrderedMultiDict(dict): >>> OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)]).items(multi=False) [('a', 3), ('b', 2)] + .. warning:: + + ``dict(omd)`` changed behavior `in Python 3.7 + `_ due to changes made to + support the transition from :class:`collections.OrderedDict` to + the built-in dictionary being ordered. Before 3.7, the result + would be a new dictionary, with values that were lists, similar + to ``omd.todict(multi=True)`` (but only shallow-copy; the lists + were direct references to OMD internal structures). From 3.7 + onward, the values became singular, like + ``omd.todict(multi=False)``. For reliable cross-version + behavior, just use :meth:`~OrderedMultiDict.todict()`. + """ + def __new__(cls, *a, **kw): + ret = super().__new__(cls) + ret._clear_ll() + return ret + def __init__(self, *args, **kwargs): if len(args) > 1: raise TypeError('%s expected at most 1 argument, got %s' % (self.__class__.__name__, len(args))) super().__init__() - self._clear_ll() if args: self.update_extend(args[0]) if kwargs: self.update(kwargs) + def __getstate__(self): + return list(self.iteritems(multi=True)) + + def __setstate__(self, state): + self.clear() + self.update_extend(state) + def _clear_ll(self): try: _map = self._map @@ -1111,6 +1125,8 @@ def addlist(self, k, v): Called ``addlist`` for consistency with :meth:`getlist`, but tuples and other sequences and iterables work. """ + if not v: + return self_insert = self._insert values = super().setdefault(k, []) for subv in v: @@ -1180,7 +1196,7 @@ def update(self, E, **F): del self[k] for k, v in E.iteritems(multi=True): self_add(k, v) - elif hasattr(E, 'keys'): + elif callable(getattr(E, 'keys', None)): for k in E.keys(): self[k] = E[k] else: @@ -1259,6 +1275,10 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) + def __ior__(self, other): + self.update(other) + return self + def pop(self, k, default=_MISSING): """Remove all values under key *k*, returning the most-recently inserted value. Raises :exc:`KeyError` if the key is not @@ -1294,7 +1314,9 @@ def poplast(self, k=_MISSING, default=_MISSING): if self: k = self.root[PREV][KEY] else: - raise KeyError('empty %r' % type(self)) + if default is _MISSING: + raise KeyError('empty %r' % type(self)) + return default try: self._remove(k) except KeyError: @@ -1403,7 +1425,7 @@ def sorted(self, key=None, reverse=False): OrderedMultiDict([('o', 'd'), ('l', 'l'), ('e', 'o'), ('l', 'r'), ('h', 'w')]) """ cls = self.__class__ - return cls(sorted(self.iteritems(), key=key, reverse=reverse)) + return cls(sorted(self.iteritems(multi=True), key=key, reverse=reverse)) def sortedvalues(self, key=None, reverse=False): """Returns a copy of the :class:`OrderedMultiDict` with the same keys @@ -1527,6 +1549,7 @@ def viewitems(self): return ItemsView(self) + try: # try to import the built-in one anyways from .dictutils import OrderedMultiDict @@ -1582,6 +1605,4 @@ def to_text(self, full_quote=False): ret_list.append('='.join((key, val))) return '&'.join(ret_list) -# TODO: cleanup OMD/cachedproperty etc.? - # end urlutils.py