diff --git a/.travis.yml b/.travis.yml
index 685ee76..517970b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,4 @@
 language: python
-sudo: false
 cache: pip
 
 # Python targets, as defined by https://github.com/travis-ci/travis-build/blob
@@ -28,10 +27,8 @@ matrix:
   include:
     - python: 3.7
       dist: xenial     # required for Python 3.7 (travis-ci/travis-ci#9069)
-      sudo: required   # required for Python 3.7 (travis-ci/travis-ci#9069)
     - python: nightly  # Python 3.8.0a0
       dist: xenial     # required for Python 3.8-dev (travis-ci/travis-ci#9069)
-      sudo: required   # required for Python 3.8-dev (travis-ci/travis-ci#9069)
   allow_failures:
     - python: nightly
 
diff --git a/boltons/cacheutils.py b/boltons/cacheutils.py
index 6f90651..01009f0 100644
--- a/boltons/cacheutils.py
+++ b/boltons/cacheutils.py
@@ -511,6 +511,7 @@ class CachedMethod(object):
     """
     def __init__(self, func, cache, scoped=True, typed=False, key=None):
         self.func = func
+        self.__isabstractmethod__ = getattr(func, '__isabstractmethod__', False)
         if isinstance(cache, basestring):
             self.get_cache = attrgetter(cache)
         elif callable(cache):
@@ -652,6 +653,7 @@ class cachedproperty(object):
     """
     def __init__(self, func):
         self.__doc__ = getattr(func, '__doc__')
+        self.__isabstractmethod__ = getattr(func, '__isabstractmethod__', False)
         self.func = func
 
     def __get__(self, obj, objtype=None):
diff --git a/boltons/dictutils.py b/boltons/dictutils.py
index f3583f7..c38fc26 100644
--- a/boltons/dictutils.py
+++ b/boltons/dictutils.py
@@ -106,17 +106,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': [1, 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))
@@ -129,6 +123,19 @@ 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
+       <https://bugs.python.org/issue34320>`_ 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 __init__(self, *args, **kwargs):
         if len(args) > 1:
diff --git a/boltons/fileutils.py b/boltons/fileutils.py
index 459b762..4d05647 100644
--- a/boltons/fileutils.py
+++ b/boltons/fileutils.py
@@ -455,9 +455,9 @@ _CUR_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def iter_find_files(directory, patterns, ignored=None):
-    """Returns a generator that yields file paths under a *directory*,
-    matching *patterns* using `glob`_ syntax (e.g., ``*.txt``). Also
-    supports *ignored* patterns.
+    """Returns a generator that yields file paths under a *directory*
+    (recursively), matching *patterns* using `glob`_ syntax (e.g., ``*.txt``).
+    Also supports *ignored* patterns.
 
     Args:
         directory (str): Path that serves as the root of the
diff --git a/boltons/funcutils.py b/boltons/funcutils.py
index d5b7f33..3e30017 100644
--- a/boltons/funcutils.py
+++ b/boltons/funcutils.py
@@ -514,8 +514,6 @@ class FunctionBuilder(object):
         body = self.body or self._default_body
 
         tmpl = 'def {name}{sig_str}:'
-        if self.doc:
-            tmpl += '\n    """{doc}"""'
         tmpl += '\n{body}'
 
         body = _indent(self.body, ' ' * self.indent)
diff --git a/boltons/iterutils.py b/boltons/iterutils.py
index 8f93cb2..bf89f1f 100644
--- a/boltons/iterutils.py
+++ b/boltons/iterutils.py
@@ -1182,6 +1182,51 @@ guid_iter = GUIDerator()
 seq_guid_iter = SequentialGUIDerator()
 
 
+def soft_sorted(iterable, first=None, last=None, key=None, reverse=False):
+    """For when you care about the order of some elements, but not about
+    others.
+
+    Use this to float to the top and/or sink to the bottom a specific
+    ordering, while sorting the rest of the elements according to
+    normal :func:`sorted` rules.
+
+    >>> soft_sorted(['two', 'b', 'one', 'a'], first=['one', 'two'])
+    ['one', 'two', 'a', 'b']
+    >>> soft_sorted(range(7), first=[6, 15], last=[2, 4], reverse=True)
+    [6, 5, 3, 1, 0, 2, 4]
+    >>> import string
+    >>> ''.join(soft_sorted(string.hexdigits, first='za1', last='b', key=str.lower))
+    'aA1023456789cCdDeEfFbB'
+
+    Args:
+       iterable (list): A list or other iterable to sort.
+       first (list): A sequence to enforce for elements which should
+          appear at the beginning of the returned list.
+       last (list): A sequence to enforce for elements which should
+          appear at the end of the returned list.
+       key (callable): Callable used to generate a comparable key for
+          each item to be sorted, same as the key in
+          :func:`sorted`. Note that entries in *first* and *last*
+          should be the keys for the items. Defaults to
+          passthrough/the identity function.
+       reverse (bool): Whether or not elements not explicitly ordered
+          by *first* and *last* should be in reverse order or not.
+
+    Returns a new list in sorted order.
+    """
+    first = first or []
+    last = last or []
+    key = key or (lambda x: x)
+    seq = list(iterable)
+    other = [x for x in seq if not ((first and key(x) in first) or (last and key(x) in last))]
+    other.sort(key=key, reverse=reverse)
+
+    if first:
+        first = sorted([x for x in seq if key(x) in first], key=lambda x: first.index(key(x)))
+    if last:
+        last = sorted([x for x in seq if key(x) in last], key=lambda x: last.index(key(x)))
+    return first + other + last
+
 """
 May actually be faster to do an isinstance check for a str path
 
diff --git a/boltons/setutils.py b/boltons/setutils.py
index 3331069..6d0a957 100644
--- a/boltons/setutils.py
+++ b/boltons/setutils.py
@@ -402,9 +402,9 @@ class IndexedSet(MutableSet):
             self.item_index_map[item] = i
         del self.dead_indices[:]
 
-    def sort(self):
+    def sort(self, **kwargs):
         "sort() -> sort the contents of the set in-place"
-        sorted_list = sorted(self)
+        sorted_list = sorted(self, **kwargs)
         if sorted_list == self.item_list:
             return
         self.item_list[:] = sorted_list
diff --git a/boltons/strutils.py b/boltons/strutils.py
index 52f613d..a5baada 100644
--- a/boltons/strutils.py
+++ b/boltons/strutils.py
@@ -175,8 +175,12 @@ def singularize(word):
     """Semi-intelligently converts an English plural *word* to its
     singular form, preserving case pattern.
 
-    >>> singularize('records')
-    'record'
+    >>> singularize('chances')
+    'chance'
+    >>> singularize('Activities')
+    'Activity'
+    >>> singularize('Glasses')
+    'Glass'
     >>> singularize('FEET')
     'FOOT'
 
@@ -192,9 +196,9 @@ def singularize(word):
         return orig_word
     elif len(word) == 2:
         singular = word[:-1]  # or just return word?
-    elif word.endswith('ies') and word[-5:-4] not in 'aeiou':
+    elif word.endswith('ies') and word[-4:-3] not in 'aeiou':
         singular = word[:-3] + 'y'
-    elif word.endswith('es'):
+    elif word.endswith('es') and word[-3] == 's':
         singular = word[:-2]
     else:
         singular = word[:-1]
@@ -234,8 +238,8 @@ def _match_case(master, disciple):
         return disciple.lower()
     elif master.upper() == master:
         return disciple.upper()
-    elif master.capitalize() == master:
-        return disciple.capitalize()
+    elif master.title() == master:
+        return disciple.title()
     return disciple
 
 
diff --git a/boltons/tableutils.py b/boltons/tableutils.py
index bc769d7..5cd07e0 100644
--- a/boltons/tableutils.py
+++ b/boltons/tableutils.py
@@ -27,10 +27,12 @@ from itertools import islice
 from collections import Sequence, Mapping, MutableSequence
 try:
     string_types, integer_types = (str, unicode), (int, long)
+    from cgi import escape as html_escape
 except NameError:
     # Python 3 compat
     unicode = str
     string_types, integer_types = (str, bytes), (int,)
+    from html import escape as html_escape
 
 try:
     from typeutils import make_sentinel
@@ -75,7 +77,7 @@ def to_text(obj, maxlen=None):
 
 def escape_html(obj, maxlen=None):
     text = to_text(obj, maxlen=maxlen)
-    return cgi.escape(text, quote=True)
+    return html_escape(text, quote=True)
 
 
 _DNR = set((type(None), bool, complex, float,
diff --git a/boltons/urlutils.py b/boltons/urlutils.py
index c96ec08..223fad7 100644
--- a/boltons/urlutils.py
+++ b/boltons/urlutils.py
@@ -770,6 +770,12 @@ class URL(object):
         cn = self.__class__.__name__
         return u'%s(%r)' % (cn, self.to_text())
 
+    def __str__(self):
+        return self.to_text()
+
+    def __unicode__(self):
+        return self.to_text()
+
     def __eq__(self, other):
         for attr in self._cmp_attrs:
             if not getattr(self, attr) == getattr(other, attr, None):

Represent the URL when str(url)
When working with `boltons.urlutils.URL`, it would be nice for `str(url)` to generate a string version of the URL, the same way it does when executing `url.to_text()`.

As of now it returns `URL('...')`, which is what you would expect when executing `repr()` but not `str()` (as per python docs, str should return a nice human representation whereas repr a formal python expression).

A part from making URL more intuitive and pythonic to use, this small change would allow it to nicely integrate with other libraries like [requests](https://github.com/requests/requests), which [automatically performs `str()`](https://github.com/requests/requests/pull/766) to non-string passed-in objects, so this would work:
```python
url = boltons.urlutils.URL('http://foo.com')
response = requests.get(url)
```
Which is just extra nice and useful :-)

Anyway, it is just a small enhancement in an already amazing library. Congratulations for it!
diff --git a/tests/test_cacheutils.py b/tests/test_cacheutils.py
index e617492..3911606 100644
--- a/tests/test_cacheutils.py
+++ b/tests/test_cacheutils.py
@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*-
-
 import string
+from abc import abstractmethod, ABCMeta
+
+import pytest
 
 from boltons.cacheutils import LRU, LRI, cached, cachedmethod, cachedproperty, MinIDMap
 
@@ -263,6 +265,24 @@ def test_cachedmethod():
     return
 
 
+def test_cachedmethod_maintains_func_abstraction():
+    ABC = ABCMeta('ABC', (object,), {})
+
+    class Car(ABC):
+
+        def __init__(self, cache=None):
+            self.h_cache = LRI() if cache is None else cache
+            self.hand_count = 0
+
+        @cachedmethod('h_cache')
+        @abstractmethod
+        def hand(self, *a, **kw):
+            self.hand_count += 1
+
+    with pytest.raises(TypeError):
+        Car()
+
+
 def test_cachedproperty():
     class Proper(object):
         def __init__(self):
@@ -295,6 +315,20 @@ def test_cachedproperty():
     repr(Proper.useful_attr)
 
 
+def test_cachedproperty_maintains_func_abstraction():
+    ABC = ABCMeta('ABC', (object,), {})
+
+    class AbstractExpensiveCalculator(ABC):
+
+        @cachedproperty
+        @abstractmethod
+        def calculate(self):
+            pass
+
+    with pytest.raises(TypeError):
+        AbstractExpensiveCalculator()
+
+
 def test_min_id_map():
     import sys
     if '__pypy__' in sys.builtin_module_names:
diff --git a/tests/test_dictutils.py b/tests/test_dictutils.py
index 87c6317..01c9ab5 100644
--- a/tests/test_dictutils.py
+++ b/tests/test_dictutils.py
@@ -27,7 +27,7 @@ def test_todict():
     assert len(omd) == 1
     assert omd['A'] == 'One'
 
-    d = dict(omd)
+    d = omd.todict(multi=True)
     assert len(d) == 1
     assert d['A'] == ['One', 'One', 'One']
 
@@ -40,6 +40,7 @@ def test_todict():
 
         flat = omd.todict()
         assert flat == d
+    return
 
 
 def test_eq():
diff --git a/tests/test_funcutils.py b/tests/test_funcutils.py
index fe00c83..a0047a3 100644
--- a/tests/test_funcutils.py
+++ b/tests/test_funcutils.py
@@ -42,7 +42,7 @@ def test_copy_function():
     assert callee() == callee_copy()
 
 
-class test_total_ordering():
+def test_total_ordering():
     @total_ordering
     class Number(object):
         def __init__(self, val):
diff --git a/tests/test_funcutils_fb.py b/tests/test_funcutils_fb.py
index 625a85c..5420bc7 100644
--- a/tests/test_funcutils_fb.py
+++ b/tests/test_funcutils_fb.py
@@ -26,15 +26,15 @@ def test_wraps_basic():
 
     @pita_wrap(flag=True)
     def simple_func():
-        "my doc string"
+        '''"""a tricky docstring"""'''
         return 'hello'
 
     assert simple_func() == (True, 'simple_func', 'hello')
-    assert simple_func.__doc__ == "my doc string"
+    assert simple_func.__doc__ == '''"""a tricky docstring"""'''
 
     assert callable(simple_func.__wrapped__)
     assert simple_func.__wrapped__() == 'hello'
-    assert simple_func.__wrapped__.__doc__ == "my doc string"
+    assert simple_func.__wrapped__.__doc__ == '''"""a tricky docstring"""'''
 
     @pita_wrap(flag=False)
     def less_simple_func(arg='hello'):
diff --git a/tests/test_urlutils.py b/tests/test_urlutils.py
index 924bf45..a8e1539 100644
--- a/tests/test_urlutils.py
+++ b/tests/test_urlutils.py
@@ -463,3 +463,6 @@ def test_unicodey():
     assert url.to_text(full_quote=False) == unicodey
     fully_quoted = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA'
     assert url.to_text(full_quote=True) == fully_quoted
+
+def test_str_repr():
+    assert str(URL("http://googlewebsite.com/e-shops.aspx")) == "http://googlewebsite.com/e-shops.aspx"

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc
3.9
requirements-test.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
-e git+https://github.com/mahmoud/boltons.git@874e930bd6dbd92e64412e1bdb46a4053587c86d#egg=boltons
coverage==4.5.1
exceptiongroup==1.2.2
execnet==2.1.1
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
py==1.5.4
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==2.5.1
pytest-mock==3.14.0
pytest-xdist==3.6.1
six==1.17.0
tomli==2.2.1
tox==2.9.1
tox-virtualenv-no-download==1.0.2
typing_extensions==4.13.0
virtualenv==15.0.2

name: boltons
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==4.5.1
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - py==1.5.4
      - pytest==8.3.5
      - pytest-asyncio==0.26.0
      - pytest-cov==2.5.1
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - six==1.17.0
      - tomli==2.2.1
      - tox==2.9.1
      - tox-virtualenv-no-download==1.0.2
      - typing-extensions==4.13.0
      - virtualenv==15.0.2
prefix: /opt/conda/envs/boltons

tests/test_cacheutils.py::test_cachedmethod_maintains_func_abstraction tests/test_cacheutils.py::test_cachedproperty_maintains_func_abstraction tests/test_funcutils_fb.py::test_wraps_basic tests/test_urlutils.py::test_str_repr
tests/test_cacheutils.py::test_lru_add tests/test_cacheutils.py::test_lri tests/test_cacheutils.py::test_lru_basic tests/test_cacheutils.py::test_lru_with_dupes tests/test_cacheutils.py::test_lru_with_dupes_2 tests/test_cacheutils.py::test_cached_dec tests/test_cacheutils.py::test_unscoped_cached_dec tests/test_cacheutils.py::test_callable_cached_dec tests/test_cacheutils.py::test_cachedmethod tests/test_cacheutils.py::test_cachedproperty tests/test_cacheutils.py::test_min_id_map tests/test_dictutils.py::test_dict_init tests/test_dictutils.py::test_todict tests/test_dictutils.py::test_eq tests/test_dictutils.py::test_copy tests/test_dictutils.py::test_clear tests/test_dictutils.py::test_types tests/test_dictutils.py::test_multi_correctness tests/test_dictutils.py::test_kv_consistency tests/test_dictutils.py::test_update_basic tests/test_dictutils.py::test_update tests/test_dictutils.py::test_update_extend tests/test_dictutils.py::test_invert tests/test_dictutils.py::test_poplast tests/test_dictutils.py::test_pop tests/test_dictutils.py::test_pop_all tests/test_dictutils.py::test_reversed tests/test_dictutils.py::test_setdefault tests/test_dictutils.py::test_one_to_one tests/test_funcutils.py::test_partials tests/test_funcutils.py::test_copy_function tests/test_funcutils.py::test_total_ordering tests/test_funcutils_fb.py::test_wraps_injected tests/test_funcutils_fb.py::test_wraps_update_dict tests/test_funcutils_fb.py::test_wraps_unknown_args tests/test_funcutils_fb.py::test_FunctionBuilder_invalid_args tests/test_funcutils_fb.py::test_FunctionBuilder_invalid_body tests/test_funcutils_fb.py::test_FunctionBuilder_modify tests/test_funcutils_fb.py::test_wraps_wrappers tests/test_urlutils.py::test_regex[http://googlewebsite.com/e-shops.aspx] tests/test_urlutils.py::test_roundtrip[http://googlewebsite.com/e-shops.aspx] tests/test_urlutils.py::test_query_params[http://googlewebsite.com/e-shops.aspx] tests/test_urlutils.py::test_regex[http://example.com:8080/search?q=123&business=Nothing%20Special] tests/test_urlutils.py::test_roundtrip[http://example.com:8080/search?q=123&business=Nothing%20Special] tests/test_urlutils.py::test_query_params[http://example.com:8080/search?q=123&business=Nothing%20Special] tests/test_urlutils.py::test_regex[http://hatnote.com:9000?arg=1&arg=2&arg=3] tests/test_urlutils.py::test_roundtrip[http://hatnote.com:9000?arg=1&arg=2&arg=3] tests/test_urlutils.py::test_query_params[http://hatnote.com:9000?arg=1&arg=2&arg=3] tests/test_urlutils.py::test_regex[https://xn--bcher-kva.ch] tests/test_urlutils.py::test_roundtrip[https://xn--bcher-kva.ch] tests/test_urlutils.py::test_query_params[https://xn--bcher-kva.ch] tests/test_urlutils.py::test_regex[http://xn--ggbla1c4e.xn--ngbc5azd/] tests/test_urlutils.py::test_roundtrip[http://xn--ggbla1c4e.xn--ngbc5azd/] tests/test_urlutils.py::test_query_params[http://xn--ggbla1c4e.xn--ngbc5azd/] tests/test_urlutils.py::test_regex[http://tools.ietf.org/html/rfc3986#section-3.4] tests/test_urlutils.py::test_roundtrip[http://tools.ietf.org/html/rfc3986#section-3.4] tests/test_urlutils.py::test_query_params[http://tools.ietf.org/html/rfc3986#section-3.4] tests/test_urlutils.py::test_regex[http://wiki:pedia@hatnote.com] tests/test_urlutils.py::test_roundtrip[http://wiki:pedia@hatnote.com] tests/test_urlutils.py::test_query_params[http://wiki:pedia@hatnote.com] tests/test_urlutils.py::test_regex[ftp://ftp.rfc-editor.org/in-notes/tar/RFCs0001-0500.tar.gz] tests/test_urlutils.py::test_roundtrip[ftp://ftp.rfc-editor.org/in-notes/tar/RFCs0001-0500.tar.gz] tests/test_urlutils.py::test_query_params[ftp://ftp.rfc-editor.org/in-notes/tar/RFCs0001-0500.tar.gz] tests/test_urlutils.py::test_regex[http://[1080:0:0:0:8:800:200C:417A]/index.html] tests/test_urlutils.py::test_roundtrip[http://[1080:0:0:0:8:800:200C:417A]/index.html] tests/test_urlutils.py::test_query_params[http://[1080:0:0:0:8:800:200C:417A]/index.html] tests/test_urlutils.py::test_regex[ssh://192.0.2.16:2222/] tests/test_urlutils.py::test_roundtrip[ssh://192.0.2.16:2222/] tests/test_urlutils.py::test_query_params[ssh://192.0.2.16:2222/] tests/test_urlutils.py::test_regex[https://[::101.45.75.219]:80/?hi=bye] tests/test_urlutils.py::test_roundtrip[https://[::101.45.75.219]:80/?hi=bye] tests/test_urlutils.py::test_query_params[https://[::101.45.75.219]:80/?hi=bye] tests/test_urlutils.py::test_regex[ldap://[::192.9.5.5]/dc=example,dc=com??sub?(sn=Jensen)] tests/test_urlutils.py::test_roundtrip[ldap://[::192.9.5.5]/dc=example,dc=com??sub?(sn=Jensen)] tests/test_urlutils.py::test_query_params[ldap://[::192.9.5.5]/dc=example,dc=com??sub?(sn=Jensen)] tests/test_urlutils.py::test_regex[mailto:me@example.com?to=me@example.com&body=hi%20http://wikipedia.org] tests/test_urlutils.py::test_roundtrip[mailto:me@example.com?to=me@example.com&body=hi%20http://wikipedia.org] tests/test_urlutils.py::test_query_params[mailto:me@example.com?to=me@example.com&body=hi%20http://wikipedia.org] tests/test_urlutils.py::test_regex[news:alt.rec.motorcycle] tests/test_urlutils.py::test_roundtrip[news:alt.rec.motorcycle] tests/test_urlutils.py::test_query_params[news:alt.rec.motorcycle] tests/test_urlutils.py::test_regex[tel:+1-800-867-5309] tests/test_urlutils.py::test_roundtrip[tel:+1-800-867-5309] tests/test_urlutils.py::test_query_params[tel:+1-800-867-5309] tests/test_urlutils.py::test_regex[urn:oasis:member:A00024:x] tests/test_urlutils.py::test_roundtrip[urn:oasis:member:A00024:x] tests/test_urlutils.py::test_query_params[urn:oasis:member:A00024:x] tests/test_urlutils.py::test_regex[magnet:?xt=urn:btih:1a42b9e04e122b97a5254e3df77ab3c4b7da725f&dn=Puppy%20Linux%20precise-5.7.1.iso&tr=udp://tracker.openbittorrent.com:80&tr=udp://tracker.publicbt.com:80&tr=udp://tracker.istole.it:6969&tr=udp://tracker.ccc.de:80&tr=udp://open.demonii.com:1337] tests/test_urlutils.py::test_roundtrip[magnet:?xt=urn:btih:1a42b9e04e122b97a5254e3df77ab3c4b7da725f&dn=Puppy%20Linux%20precise-5.7.1.iso&tr=udp://tracker.openbittorrent.com:80&tr=udp://tracker.publicbt.com:80&tr=udp://tracker.istole.it:6969&tr=udp://tracker.ccc.de:80&tr=udp://open.demonii.com:1337] tests/test_urlutils.py::test_query_params[magnet:?xt=urn:btih:1a42b9e04e122b97a5254e3df77ab3c4b7da725f&dn=Puppy%20Linux%20precise-5.7.1.iso&tr=udp://tracker.openbittorrent.com:80&tr=udp://tracker.publicbt.com:80&tr=udp://tracker.istole.it:6969&tr=udp://tracker.ccc.de:80&tr=udp://open.demonii.com:1337] tests/test_urlutils.py::test_regex[http://localhost] tests/test_urlutils.py::test_roundtrip[http://localhost] tests/test_urlutils.py::test_query_params[http://localhost] tests/test_urlutils.py::test_regex[http://localhost/] tests/test_urlutils.py::test_roundtrip[http://localhost/] tests/test_urlutils.py::test_query_params[http://localhost/] tests/test_urlutils.py::test_regex[http://localhost/foo] tests/test_urlutils.py::test_roundtrip[http://localhost/foo] tests/test_urlutils.py::test_query_params[http://localhost/foo] tests/test_urlutils.py::test_regex[http://localhost/foo/] tests/test_urlutils.py::test_roundtrip[http://localhost/foo/] tests/test_urlutils.py::test_query_params[http://localhost/foo/] tests/test_urlutils.py::test_regex[http://localhost/foo!!bar/] tests/test_urlutils.py::test_roundtrip[http://localhost/foo!!bar/] tests/test_urlutils.py::test_query_params[http://localhost/foo!!bar/] tests/test_urlutils.py::test_regex[http://localhost/foo%20bar/] tests/test_urlutils.py::test_roundtrip[http://localhost/foo%20bar/] tests/test_urlutils.py::test_query_params[http://localhost/foo%20bar/] tests/test_urlutils.py::test_regex[http://localhost/foo%2Fbar/] tests/test_urlutils.py::test_roundtrip[http://localhost/foo%2Fbar/] tests/test_urlutils.py::test_query_params[http://localhost/foo%2Fbar/] tests/test_urlutils.py::test_regex[http://localhost/foo?n] tests/test_urlutils.py::test_roundtrip[http://localhost/foo?n] tests/test_urlutils.py::test_query_params[http://localhost/foo?n] tests/test_urlutils.py::test_regex[http://localhost/foo?n=v] tests/test_urlutils.py::test_roundtrip[http://localhost/foo?n=v] tests/test_urlutils.py::test_query_params[http://localhost/foo?n=v] tests/test_urlutils.py::test_regex[http://localhost/foo?n=/a/b] tests/test_urlutils.py::test_roundtrip[http://localhost/foo?n=/a/b] tests/test_urlutils.py::test_query_params[http://localhost/foo?n=/a/b] tests/test_urlutils.py::test_regex[http://example.com/foo!@$bar?b!@z=123] tests/test_urlutils.py::test_roundtrip[http://example.com/foo!@$bar?b!@z=123] tests/test_urlutils.py::test_query_params[http://example.com/foo!@$bar?b!@z=123] tests/test_urlutils.py::test_regex[http://localhost/asd?a=asd%20sdf/345] tests/test_urlutils.py::test_roundtrip[http://localhost/asd?a=asd%20sdf/345] tests/test_urlutils.py::test_query_params[http://localhost/asd?a=asd%20sdf/345] tests/test_urlutils.py::test_regex[http://(%2525)/(%2525)?(%2525)&(%2525)=(%2525)#(%2525)] tests/test_urlutils.py::test_roundtrip[http://(%2525)/(%2525)?(%2525)&(%2525)=(%2525)#(%2525)] tests/test_urlutils.py::test_query_params[http://(%2525)/(%2525)?(%2525)&(%2525)=(%2525)#(%2525)] tests/test_urlutils.py::test_regex[http://(%C3%A9)/(%C3%A9)?(%C3%A9)&(%C3%A9)=(%C3%A9)#(%C3%A9)] tests/test_urlutils.py::test_roundtrip[http://(%C3%A9)/(%C3%A9)?(%C3%A9)&(%C3%A9)=(%C3%A9)#(%C3%A9)] tests/test_urlutils.py::test_query_params[http://(%C3%A9)/(%C3%A9)?(%C3%A9)&(%C3%A9)=(%C3%A9)#(%C3%A9)] tests/test_urlutils.py::test_basic tests/test_urlutils.py::test_utf8_url tests/test_urlutils.py::test_idna tests/test_urlutils.py::test_iri_query tests/test_urlutils.py::test_iri_path tests/test_urlutils.py::test_url_copy tests/test_urlutils.py::test_invalid_port tests/test_urlutils.py::test_invalid_ipv6 tests/test_urlutils.py::test_parse_url tests/test_urlutils.py::test_parse_equals_in_qp_value tests/test_urlutils.py::test_identical_equal tests/test_urlutils.py::test_equal tests/test_urlutils.py::test_not_equal tests/test_urlutils.py::test_userinfo tests/test_urlutils.py::test_quoted_userinfo tests/test_urlutils.py::test_mailto tests/test_urlutils.py::test_rel_navigate tests/test_urlutils.py::test_navigate tests/test_urlutils.py::test_self_normalize tests/test_urlutils.py::test_normalize_with_case tests/test_urlutils.py::test_netloc_slashes tests/test_urlutils.py::test_find_all_links_basic tests/test_urlutils.py::test_find_all_links tests/test_urlutils.py::test_unicodey
boltons/dictutils.py boltons/funcutils.py boltons/strutils.py .travis.yml boltons/urlutils.py boltons/iterutils.py boltons/tableutils.py boltons/fileutils.py boltons/cacheutils.py boltons/setutils.py
boltons/dictutils.py boltons/funcutils.py boltons/strutils.py .travis.yml boltons/urlutils.py boltons/iterutils.py boltons/tableutils.py boltons/fileutils.py boltons/cacheutils.py boltons/setutils.py