Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-purl for openSUSE:Factory 
checked in at 2022-10-18 12:44:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-purl (Old)
 and      /work/SRC/openSUSE:Factory/.python-purl.new.2275 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-purl"

Tue Oct 18 12:44:40 2022 rev:4 rq:1018091 version:1.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-purl/python-purl.changes  2020-05-04 
18:34:08.840272246 +0200
+++ /work/SRC/openSUSE:Factory/.python-purl.new.2275/python-purl.changes        
2022-10-18 12:44:58.001719201 +0200
@@ -1,0 +2,9 @@
+Mon Oct 17 10:46:23 UTC 2022 - pgaj...@suse.com
+
+- version update to 1.6
+  * Use `pytest` insteed of `nose`.
+  * Fix warning around regex string.
+- deleted patches
+  - use_pytest.patch (upstreamed)
+
+-------------------------------------------------------------------

Old:
----
  1.5.tar.gz
  use_pytest.patch

New:
----
  1.6.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-purl.spec ++++++
--- /var/tmp/diff_new_pack.WeG1Re/_old  2022-10-18 12:44:58.445720212 +0200
+++ /var/tmp/diff_new_pack.WeG1Re/_new  2022-10-18 12:44:58.449720221 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-purl
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,15 +18,13 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-purl
-Version:        1.5
+Version:        1.6
 Release:        0
 Summary:        An immutable URL class for URL building and manipulation
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/codeinthehole/purl
 Source:         https://github.com/codeinthehole/purl/archive/%{version}.tar.gz
-# https://github.com/codeinthehole/purl/pull/42
-Patch0:         use_pytest.patch
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module six}
@@ -41,7 +39,6 @@
 
 %prep
 %setup -q -n purl-%{version}
-%patch0 -p1
 
 %build
 %python_build
@@ -51,7 +48,9 @@
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %check
-%pytest tests/utils_tests.py tests/expansion_tests.py tests/template_tests.py  
tests/url_tests.py
+pushd tests
+%pytest
+popd
 
 %files %{python_files}
 %doc README.rst

++++++ 1.5.tar.gz -> 1.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/.travis.yml new/purl-1.6/.travis.yml
--- old/purl-1.5/.travis.yml    2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/.travis.yml    2021-05-15 23:00:41.000000000 +0200
@@ -1,9 +1,11 @@
 language: python
 python:
   - 2.7
-  - 3.3
-  - 3.4
+  - 3.6
+  - 3.7
+  - 3.8
   - pypy
+  - pypy3
 install:
   - make install
 script:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/README.rst new/purl-1.6/README.rst
--- old/purl-1.5/README.rst     2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/README.rst     2021-05-15 23:00:41.000000000 +0200
@@ -3,7 +3,7 @@
 ================================
 
 A simple, immutable URL class with a clean API for interrogation and
-manipulation.  Supports Pythons 2.7, 3.3, 3.4, 3.5, 3.6 and pypy.
+manipulation.  Supports Pythons 2.7, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8 and pypy.
 
 Also supports template URLs as per `RFC 6570`_
 
@@ -166,6 +166,12 @@
 Changelog
 ---------
 
+v1.6 - 2021-05-15
+~~~~~~~~~~~~~~~~~
+
+* Use `pytest` insteed of `nose`.
+* Fix warning around regex string.
+
 v1.5 - 2019-03-10
 ~~~~~~~~~~~~~~~~~
 
@@ -283,7 +289,7 @@
 
 Ensure tests pass using::
 
-    (purl) $ ./runtests.sh
+    (purl) $ pytest
 
 or::
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/docs/conf.py new/purl-1.6/docs/conf.py
--- old/purl-1.5/docs/conf.py   2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/docs/conf.py   2021-05-15 23:00:41.000000000 +0200
@@ -120,11 +120,6 @@
 # pixels large.
 #html_favicon = None
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
 #html_last_updated_fmt = '%b %d, %Y'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/makefile new/purl-1.6/makefile
--- old/purl-1.5/makefile       2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/makefile       2021-05-15 23:00:41.000000000 +0200
@@ -3,7 +3,7 @@
        python setup.py develop
 
 test:
-       nosetests
+       pytest
 
 package: clean
        # Test these packages in a fresh virtualenvs:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/purl/__init__.py 
new/purl-1.6/purl/__init__.py
--- old/purl-1.5/purl/__init__.py       2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/purl/__init__.py       2021-05-15 23:00:41.000000000 +0200
@@ -1,4 +1,6 @@
 from .url import URL  # noqa
 from .template import expand, Template  # noqa
 
+__version__ = '1.6'
+
 __all__ = ['URL', 'expand', 'Template']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/purl/template.py 
new/purl-1.6/purl/template.py
--- old/purl-1.5/purl/template.py       2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/purl/template.py       2021-05-15 23:00:41.000000000 +0200
@@ -13,7 +13,7 @@
 __all__ = ['Template', 'expand']
 
 
-patterns = re.compile("{([^\}]+)}")
+patterns = re.compile(r"{([^\}]+)}")
 
 
 class Template(object):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/pytest.ini new/purl-1.6/pytest.ini
--- old/purl-1.5/pytest.ini     1970-01-01 01:00:00.000000000 +0100
+++ new/purl-1.6/pytest.ini     2021-05-15 23:00:41.000000000 +0200
@@ -0,0 +1,3 @@
+[pytest]
+addopts = -vv --doctest-modules --doctest-glob='*.rst'
+doctest_optionflags=ALLOW_UNICODE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/requirements.txt 
new/purl-1.6/requirements.txt
--- old/purl-1.5/requirements.txt       2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/requirements.txt       2021-05-15 23:00:41.000000000 +0200
@@ -4,5 +4,5 @@
 wheel==0.33.1
 
 # Testing
-nose==1.3.7
+pytest
 tox==3.7.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/runtests.sh new/purl-1.6/runtests.sh
--- old/purl-1.5/runtests.sh    2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/runtests.sh    1970-01-01 01:00:00.000000000 +0100
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-# The doctests only work in Python2.* due to unicode issues that I
-#??don't know how to solve.
-nosetests --with-doctest --doctest-extension=rst -s $@
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/setup.py new/purl-1.6/setup.py
--- old/purl-1.5/setup.py       2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/setup.py       2021-05-15 23:00:41.000000000 +0200
@@ -3,7 +3,7 @@
 
 setup(
     name='purl',
-    version='1.5',
+    version='1.6',
     description=(
         "An immutable URL class for easy URL-building and manipulation"),
     long_description=open('README.rst').read(),
@@ -23,10 +23,10 @@
         'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.3',
-        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: Implementation :: PyPy',
         'Topic :: Software Development :: Libraries :: Python Modules',
     ],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/expansion_tests.py 
new/purl-1.6/tests/expansion_tests.py
--- old/purl-1.5/tests/expansion_tests.py       2019-03-10 21:51:02.000000000 
+0100
+++ new/purl-1.6/tests/expansion_tests.py       1970-01-01 01:00:00.000000000 
+0100
@@ -1,129 +0,0 @@
-# -*- coding: utf-8 -*-
-import collections
-
-from nose.tools import eq_
-
-from purl.template import expand
-
-# Define variables as in the RFC (http://tools.ietf.org/html/rfc6570)
-level1_vars = {
-    'var': 'value',
-    'hello': 'Hello World!',
-}
-level2_vars = level1_vars.copy()
-level2_vars.update({
-    'path': '/foo/bar'
-})
-level3_vars = level2_vars.copy()
-level3_vars.update({
-    'empty': '',
-    'x': '1024',
-    'y': '768'
-})
-level4_vars = level2_vars.copy()
-level4_vars.update({
-    'list': ['red', 'green', 'blue'],
-    'keys': [('semi', ';'), ('dot', '.'), ('comma', ',')]
-})
-
-data = [
-    # Level 1
-    ('{var}', level1_vars, 'value'),
-    ('{hello}', level1_vars, 'Hello%20World%21'),
-    # Level 2 - reserved expansion
-    ('{+var}', level2_vars, 'value'),
-    ('{+hello}', level2_vars, 'Hello%20World!'),
-    ('{+path}/here', level2_vars, '/foo/bar/here'),
-    ('here?ref={+path}', level2_vars, 'here?ref=/foo/bar'),
-    # Level 2 - fragment expansion
-    ('X{#var}', level2_vars, 'X#value'),
-    ('X{#hello}', level2_vars, 'X#Hello%20World!'),
-    # Level 3 - string expansion with multiple variables
-    ('map?{x,y}', level3_vars, 'map?1024,768'),
-    ('{x,hello,y}', level3_vars, '1024,Hello%20World%21,768'),
-    # Level 3 - reserved expansion with multiple variables
-    ('{+x,hello,y}', level3_vars, '1024,Hello%20World!,768'),
-    ('{+path,x}/here', level3_vars, '/foo/bar,1024/here'),
-    # Level 3 - fragment expansion with multiple variables
-    ('{#x,hello,y}', level3_vars, '#1024,Hello%20World!,768'),
-    ('{#path,x}/here', level3_vars, '#/foo/bar,1024/here'),
-    # Level 3 - label expansion
-    ('X{.var}', level3_vars, 'X.value'),
-    ('X{.x,y}', level3_vars, 'X.1024.768'),
-    # Level 3 - path segments, slash prefixed
-    ('{/var}', level3_vars, '/value'),
-    ('{/nokey}', level3_vars, ''),
-    ('{/var,x}/here', level3_vars, '/value/1024/here'),
-    # Level 3 - path segments, semi-colon prefixed
-    ('{;x,y}', level3_vars, ';x=1024;y=768'),
-    ('{;x,y,empty}', level3_vars, ';x=1024;y=768;empty'),
-    # Level 3 - form-style query, ampersand-separated
-    ('{?x,y}', level3_vars, '?x=1024&y=768'),
-    ('{?x,y,empty}', level3_vars, '?x=1024&y=768&empty='),
-    # Level 3 - form-style query continuation
-    ('?fixed=yes{&x}', level3_vars, '?fixed=yes&x=1024'),
-    ('{&x,y,empty}', level3_vars, '&x=1024&y=768&empty='),
-    # Level 4 - string expansion with value modifiers
-    ('{var:3}', level4_vars, 'val'),
-    ('{var:30}', level4_vars, 'value'),
-    ('{list}', level4_vars, 'red,green,blue'),
-    ('{list*}', level4_vars, 'red,green,blue'),
-    ('{keys}', level4_vars, 'semi,%3B,dot,.,comma,%2C'),
-    ('{keys*}', level4_vars, 'semi=%3B,dot=.,comma=%2C'),
-    # Level 4 - reserved expansion with value modifiers
-    ('{+path:6}/here', level4_vars, '/foo/b/here'),
-    ('{+list}', level4_vars, 'red,green,blue'),
-    ('{+list*}', level4_vars, 'red,green,blue'),
-    ('{+keys}', level4_vars, 'semi,;,dot,.,comma,,'),
-    ('{+keys*}', level4_vars, 'semi=;,dot=.,comma=,'),
-    # Level 4 - fragment expansion with value modifiers
-    ('{#path:6}/here', level4_vars, '#/foo/b/here'),
-    ('{#list}', level4_vars, '#red,green,blue'),
-    ('{#list*}', level4_vars, '#red,green,blue'),
-    ('{#keys}', level4_vars, '#semi,;,dot,.,comma,,'),
-    ('{#keys*}', level4_vars, '#semi=;,dot=.,comma=,'),
-    # Level 4 - label expansion, dot-prefixed
-    ('X{.var:3}', level4_vars, 'X.val'),
-    ('X{.list}', level4_vars, 'X.red,green,blue'),
-    ('X{.list*}', level4_vars, 'X.red.green.blue'),
-    ('X{.keys}', level4_vars, 'X.semi,%3B,dot,.,comma,%2C'),
-    ('X{.keys*}', level4_vars, 'X.semi=%3B.dot=..comma=%2C'),
-    # Level 4 - path segments, slash-prefixed
-    ('{/var:1,var}', level4_vars, '/v/value'),
-    ('{/list}', level4_vars, '/red,green,blue'),
-    ('{/list*}', level4_vars, '/red/green/blue'),
-    ('{/list*,path:4}', level4_vars, '/red/green/blue/%2Ffoo'),
-    ('{/keys}', level4_vars, '/semi,%3B,dot,.,comma,%2C'),
-    ('{/keys*}', level4_vars, '/semi=%3B/dot=./comma=%2C'),
-    # Level 4 - path-style parameters, semicolon-prefixed
-    ('{;hello:5}', level4_vars, ';hello=Hello'),
-    ('{;list}', level4_vars, ';list=red,green,blue'),
-    ('{;list*}', level4_vars, ';list=red;list=green;list=blue'),
-    ('{;keys}', level4_vars, ';keys=semi,%3B,dot,.,comma,%2C'),
-    ('{;keys*}', level4_vars, ';semi=%3B;dot=.;comma=%2C'),
-    # Level 4 - form-style query, ampersand-separated
-    ('{?var:3}', level4_vars, '?var=val'),
-    ('{?list}', level4_vars, '?list=red,green,blue'),
-    ('{?list*}', level4_vars, '?list=red&list=green&list=blue'),
-    ('{?keys}', level4_vars, '?keys=semi,%3B,dot,.,comma,%2C'),
-    ('{?keys*}', level4_vars, '?semi=%3B&dot=.&comma=%2C'),
-    # Level 4 - form-sytyle query continuation
-    ('{&var:3}', level4_vars, '&var=val'),
-    ('{&list}', level4_vars, '&list=red,green,blue'),
-    ('{&list*}', level4_vars, '&list=red&list=green&list=blue'),
-    ('{&keys}', level4_vars, '&keys=semi,%3B,dot,.,comma,%2C'),
-    ('{&keys*}', level4_vars, '&semi=%3B&dot=.&comma=%2C'),
-]
-
-
-def assert_expansion(template, fields, expected):
-    eq_(expand(template, fields), expected)
-
-
-def test_expansion():
-    for template, fields, expected in data:
-        yield assert_expansion, template, fields, expected
-
-def test_unicode():
-    expand('{/name}', {'name': u'??? hello'})
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/template_tests.py 
new/purl-1.6/tests/template_tests.py
--- old/purl-1.5/tests/template_tests.py        2019-03-10 21:51:02.000000000 
+0100
+++ new/purl-1.6/tests/template_tests.py        1970-01-01 01:00:00.000000000 
+0100
@@ -1,20 +0,0 @@
-from unittest import TestCase
-
-import purl
-
-
-class TemplateTests(TestCase):
-
-    def test_basic_expansion(self):
-        template = purl.Template('http://example.com{+path,x}/here')
-        url = template.expand({'path': '/foo/bar', 'x': 1024})
-        self.assertEquals('http://example.com/foo/bar,1024/here',
-                          url.as_string())
-
-    def test_github_api_expansion(self):
-        template = purl.Template(
-            'https://api.github.com/repos/codeinthehole/purl/labels{/name}')
-        url = template.expand()
-        self.assertEquals(
-            'https://api.github.com/repos/codeinthehole/purl/labels',
-            url.as_string())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/test_expansion.py 
new/purl-1.6/tests/test_expansion.py
--- old/purl-1.5/tests/test_expansion.py        1970-01-01 01:00:00.000000000 
+0100
+++ new/purl-1.6/tests/test_expansion.py        2021-05-15 23:00:41.000000000 
+0200
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+import collections
+
+import pytest
+
+from purl.template import expand
+
+# Define variables as in the RFC (http://tools.ietf.org/html/rfc6570)
+level1_vars = {
+    'var': 'value',
+    'hello': 'Hello World!',
+}
+level2_vars = level1_vars.copy()
+level2_vars.update({
+    'path': '/foo/bar'
+})
+level3_vars = level2_vars.copy()
+level3_vars.update({
+    'empty': '',
+    'x': '1024',
+    'y': '768'
+})
+level4_vars = level2_vars.copy()
+level4_vars.update({
+    'list': ['red', 'green', 'blue'],
+    'keys': [('semi', ';'), ('dot', '.'), ('comma', ',')]
+})
+
+data = [
+    # Level 1
+    ('{var}', level1_vars, 'value'),
+    ('{hello}', level1_vars, 'Hello%20World%21'),
+    # Level 2 - reserved expansion
+    ('{+var}', level2_vars, 'value'),
+    ('{+hello}', level2_vars, 'Hello%20World!'),
+    ('{+path}/here', level2_vars, '/foo/bar/here'),
+    ('here?ref={+path}', level2_vars, 'here?ref=/foo/bar'),
+    # Level 2 - fragment expansion
+    ('X{#var}', level2_vars, 'X#value'),
+    ('X{#hello}', level2_vars, 'X#Hello%20World!'),
+    # Level 3 - string expansion with multiple variables
+    ('map?{x,y}', level3_vars, 'map?1024,768'),
+    ('{x,hello,y}', level3_vars, '1024,Hello%20World%21,768'),
+    # Level 3 - reserved expansion with multiple variables
+    ('{+x,hello,y}', level3_vars, '1024,Hello%20World!,768'),
+    ('{+path,x}/here', level3_vars, '/foo/bar,1024/here'),
+    # Level 3 - fragment expansion with multiple variables
+    ('{#x,hello,y}', level3_vars, '#1024,Hello%20World!,768'),
+    ('{#path,x}/here', level3_vars, '#/foo/bar,1024/here'),
+    # Level 3 - label expansion
+    ('X{.var}', level3_vars, 'X.value'),
+    ('X{.x,y}', level3_vars, 'X.1024.768'),
+    # Level 3 - path segments, slash prefixed
+    ('{/var}', level3_vars, '/value'),
+    ('{/nokey}', level3_vars, ''),
+    ('{/var,x}/here', level3_vars, '/value/1024/here'),
+    # Level 3 - path segments, semi-colon prefixed
+    ('{;x,y}', level3_vars, ';x=1024;y=768'),
+    ('{;x,y,empty}', level3_vars, ';x=1024;y=768;empty'),
+    # Level 3 - form-style query, ampersand-separated
+    ('{?x,y}', level3_vars, '?x=1024&y=768'),
+    ('{?x,y,empty}', level3_vars, '?x=1024&y=768&empty='),
+    # Level 3 - form-style query continuation
+    ('?fixed=yes{&x}', level3_vars, '?fixed=yes&x=1024'),
+    ('{&x,y,empty}', level3_vars, '&x=1024&y=768&empty='),
+    # Level 4 - string expansion with value modifiers
+    ('{var:3}', level4_vars, 'val'),
+    ('{var:30}', level4_vars, 'value'),
+    ('{list}', level4_vars, 'red,green,blue'),
+    ('{list*}', level4_vars, 'red,green,blue'),
+    ('{keys}', level4_vars, 'semi,%3B,dot,.,comma,%2C'),
+    ('{keys*}', level4_vars, 'semi=%3B,dot=.,comma=%2C'),
+    # Level 4 - reserved expansion with value modifiers
+    ('{+path:6}/here', level4_vars, '/foo/b/here'),
+    ('{+list}', level4_vars, 'red,green,blue'),
+    ('{+list*}', level4_vars, 'red,green,blue'),
+    ('{+keys}', level4_vars, 'semi,;,dot,.,comma,,'),
+    ('{+keys*}', level4_vars, 'semi=;,dot=.,comma=,'),
+    # Level 4 - fragment expansion with value modifiers
+    ('{#path:6}/here', level4_vars, '#/foo/b/here'),
+    ('{#list}', level4_vars, '#red,green,blue'),
+    ('{#list*}', level4_vars, '#red,green,blue'),
+    ('{#keys}', level4_vars, '#semi,;,dot,.,comma,,'),
+    ('{#keys*}', level4_vars, '#semi=;,dot=.,comma=,'),
+    # Level 4 - label expansion, dot-prefixed
+    ('X{.var:3}', level4_vars, 'X.val'),
+    ('X{.list}', level4_vars, 'X.red,green,blue'),
+    ('X{.list*}', level4_vars, 'X.red.green.blue'),
+    ('X{.keys}', level4_vars, 'X.semi,%3B,dot,.,comma,%2C'),
+    ('X{.keys*}', level4_vars, 'X.semi=%3B.dot=..comma=%2C'),
+    # Level 4 - path segments, slash-prefixed
+    ('{/var:1,var}', level4_vars, '/v/value'),
+    ('{/list}', level4_vars, '/red,green,blue'),
+    ('{/list*}', level4_vars, '/red/green/blue'),
+    ('{/list*,path:4}', level4_vars, '/red/green/blue/%2Ffoo'),
+    ('{/keys}', level4_vars, '/semi,%3B,dot,.,comma,%2C'),
+    ('{/keys*}', level4_vars, '/semi=%3B/dot=./comma=%2C'),
+    # Level 4 - path-style parameters, semicolon-prefixed
+    ('{;hello:5}', level4_vars, ';hello=Hello'),
+    ('{;list}', level4_vars, ';list=red,green,blue'),
+    ('{;list*}', level4_vars, ';list=red;list=green;list=blue'),
+    ('{;keys}', level4_vars, ';keys=semi,%3B,dot,.,comma,%2C'),
+    ('{;keys*}', level4_vars, ';semi=%3B;dot=.;comma=%2C'),
+    # Level 4 - form-style query, ampersand-separated
+    ('{?var:3}', level4_vars, '?var=val'),
+    ('{?list}', level4_vars, '?list=red,green,blue'),
+    ('{?list*}', level4_vars, '?list=red&list=green&list=blue'),
+    ('{?keys}', level4_vars, '?keys=semi,%3B,dot,.,comma,%2C'),
+    ('{?keys*}', level4_vars, '?semi=%3B&dot=.&comma=%2C'),
+    # Level 4 - form-style query continuation
+    ('{&var:3}', level4_vars, '&var=val'),
+    ('{&list}', level4_vars, '&list=red,green,blue'),
+    ('{&list*}', level4_vars, '&list=red&list=green&list=blue'),
+    ('{&keys}', level4_vars, '&keys=semi,%3B,dot,.,comma,%2C'),
+    ('{&keys*}', level4_vars, '&semi=%3B&dot=.&comma=%2C'),
+]
+
+
+@pytest.mark.parametrize("template, fields, expected", data)
+def test_assert_expansion(template, fields, expected):
+    assert expand(template, fields) == expected
+
+def test_unicode():
+    expand('{/name}', {'name': u'??? hello'})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/test_template.py 
new/purl-1.6/tests/test_template.py
--- old/purl-1.5/tests/test_template.py 1970-01-01 01:00:00.000000000 +0100
+++ new/purl-1.6/tests/test_template.py 2021-05-15 23:00:41.000000000 +0200
@@ -0,0 +1,15 @@
+import purl
+
+
+class TestTemplate:
+
+    def test_basic_expansion(self):
+        template = purl.Template('http://example.com{+path,x}/here')
+        url = template.expand({'path': '/foo/bar', 'x': 1024})
+        assert 'http://example.com/foo/bar,1024/here' == url.as_string()
+
+    def test_github_api_expansion(self):
+        template = purl.Template(
+            'https://api.github.com/repos/codeinthehole/purl/labels{/name}')
+        url = template.expand()
+        assert 'https://api.github.com/repos/codeinthehole/purl/labels' == 
url.as_string()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/test_url.py 
new/purl-1.6/tests/test_url.py
--- old/purl-1.5/tests/test_url.py      1970-01-01 01:00:00.000000000 +0100
+++ new/purl-1.6/tests/test_url.py      2021-05-15 23:00:41.000000000 +0200
@@ -0,0 +1,463 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from purl import URL
+import pytest
+
+import pickle
+
+try:
+    from urllib.parse import quote
+except ImportError:
+    from urllib import quote
+
+
+class TestConstructor:
+    def test_url_can_be_created_with_just_host(self):
+        u = URL(host="google.com")
+        assert "http://google.com/"; == str(u)
+
+    def test_url_can_be_created_with_host_and_schema(self):
+        u = URL(host="google.com", scheme="https")
+        assert "https://google.com/"; == str(u)
+
+    def test_url_can_be_created_with_host_and_post(self):
+        u = URL(host="localhost", port=8000)
+        assert "http://localhost:8000/"; == str(u)
+
+    def test_url_can_be_created_with_username_only(self):
+        u = URL(
+            scheme="postgres",
+            username="user",
+            host="127.0.0.1",
+            port="5432",
+            path="/db_name",
+        )
+        assert "postgres://user@127.0.0.1:5432/db_name" == str(u)
+
+    def test_no_args_to_constructor(self):
+        u = URL()
+        assert "/" == str(u)
+
+    def test_as_string(self):
+        assert "/" == URL().as_string()
+
+    def test_full_url_can_be_used_as_first_param(self):
+        u = URL("https://github.com";)
+        assert "https://github.com"; == u.as_string()
+
+    def test_kwargs_take_priority_when_used_with_full_url(self):
+        u = URL("https://github.com";, scheme="http")
+        assert "http://github.com"; == u.as_string()
+
+    def test_creation_with_host_and_path(self):
+        u = URL(host="localhost", path="boo")
+        assert "http://localhost/boo"; == str(u)
+
+    def test_creation_with_host_and_path_2(self):
+        u = URL(host="localhost").add_path_segment("boo")
+        assert "http://localhost/boo"; == str(u)
+
+
+class TestMoreFactory:
+    def test_extracting_query_param(self):
+        url_str = 
"https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token=EC-6469953681606921P&AMT=200&CURRENCYCODE=GBP&RETURNURL=http%3A%2F%2Fexample.com%2Fcheckout%2Fpaypal%2Fresponse%2Fsuccess%2F&CANCELURL=http%3A%2F%2Fexample.com%2Fcheckout%2Fpaypal%2Fresponse%2Fcancel%2F";
+        url = URL.from_string(url_str)
+        return_url = url.query_param("RETURNURL")
+        assert "http://example.com/checkout/paypal/response/success/"; == 
return_url
+
+
+class TestFactory:
+
+    url_str = "http://www.google.com/search/?q=testing#fragment";
+    url = URL.from_string(url_str)
+
+    def test_scheme(self):
+        assert "http" == self.url.scheme()
+
+    def test_fragment(self):
+        assert "fragment" == self.url.fragment()
+
+    def test_path(self):
+        assert "/search/" == self.url.path()
+
+    def test_host(self):
+        assert "www.google.com" == self.url.host()
+
+    def test_string_version(self):
+        assert self.url_str == str(self.url)
+
+
+class TestEdgeCaseExtraction:
+    def test_no_equals_sign_means_empty_string(self):
+        url = URL.from_string("http://www.google.com/blog/article/1?q";)
+        assert "" == url.query_param("q")
+
+    def test_list_extraction(self):
+        url = URL.from_string("http://www.google.com/?q=1&q=2&q=3";)
+        assert ["1" == "2", "3"], url.query_param("q")
+
+    def test_username_extraction(self):
+        url = URL.from_string("ftp://user:p...@ftp.host";)
+        assert "user" == url.username()
+        assert "pw" == url.password()
+
+    def test_username_in_unicode_repr(self):
+        u = "ftp://user:p...@ftp.host";
+        url = URL.from_string(u)
+        assert u == str(url)
+
+    def test_auth_in_netloc(self):
+        url = URL.from_string("ftp://user:p...@ftp.host";)
+        assert "user:p...@ftp.host" == url.netloc()
+
+    def test_auth_with_special_char(self):
+        url = URL.from_string("ftp://user:b@z...@ftp.host";)
+        assert "user" == url.username()
+        assert "b@z" == url.password()
+
+    def test_port_in_netloc(self):
+        url = URL.from_string("http://localhost:5000";)
+        assert "localhost" == url.host()
+        assert 5000 == url.port()
+
+    def test_passwordless_netloc(self):
+        url = URL.from_string("postgres://user@127.0.0.1:5432/db_name")
+        assert "user" == url.username()
+        assert url.password() is None
+
+    def test_unicode_username_and_password(self):
+        url = 
URL.from_string("postgres://je????:nieje????@127.0.0.1:5432/db_name")
+        assert "je????" == url.username()
+        assert "nieje????" == url.password()
+
+    def test_unicode_username_only(self):
+        url = URL.from_string("postgres://je????@127.0.0.1:5432/db_name")
+        assert "je????" == url.username()
+        assert url.password() is None
+
+    def test_port_for_https_url(self):
+        url = URL.from_string("https://github.com";)
+        assert None == url.port()
+
+
+class TestSimpleExtraction:
+    url = URL.from_string("http://www.google.com/blog/article/1?q=testing";)
+
+    def test_has_actual_param(self):
+        assert self.url.has_query_param("q") is True
+
+    def test_remove_query_param(self):
+        new_url = self.url.remove_query_param("q")
+        assert "http://www.google.com/blog/article/1"; == new_url.as_string()
+
+    def test_has_query_params(self):
+        assert self.url.has_query_params(["q"]) is True
+
+    def test_has_query_params_negative(self):
+        assert self.url.has_query_params(["q", "r"]) is False
+
+    def test_netloc(self):
+        assert "www.google.com" == self.url.netloc()
+
+    def test_path_extraction(self):
+        assert "1" == self.url.path_segment(2)
+
+    def test_port_defaults_to_none(self):
+        assert self.url.port() is None
+
+    def test_scheme(self):
+        assert "http" == self.url.scheme()
+
+    def test_host(self):
+        assert "www.google.com" == self.url.host()
+
+    def test_domain(self):
+        assert "www.google.com" == self.url.domain()
+
+    def test_subdomains(self):
+        assert ["www" == "google", "com"], self.url.subdomains()
+
+    def test_subdomain(self):
+        assert "www" == self.url.subdomain(0)
+
+    def test_invalid_subdomain_raises_indexerror(self):
+        with pytest.raises(IndexError):
+            self.url.subdomain(10)
+
+    def test_path(self):
+        assert "/blog/article/1" == self.url.path()
+
+    def test_query(self):
+        assert "q=testing" == self.url.query()
+
+    def test_query_param_as_list(self):
+        assert ["testing"] == self.url.query_param("q", as_list=True)
+
+    def test_query_params(self):
+        assert {"q": ["testing"]} == self.url.query_params()
+
+    def test_path_extraction_returns_none_if_index_too_large(self):
+        assert self.url.path_segment(14) is None
+
+    def test_path_extraction_can_take_default_value(self):
+        assert "hello" == self.url.path_segment(3, default="hello")
+
+    def test_parameter_extraction(self):
+        assert "testing" == self.url.query_param("q")
+
+    def test_parameter_extraction_with_default(self):
+        assert "eggs" == self.url.query_param("p", default="eggs")
+
+    def test_parameter_extraction_is_none_if_not_found(self):
+        assert self.url.query_param("p") is None
+
+    def test_path_segments(self):
+        assert ("blog", "article", "1") == self.url.path_segments()
+
+    def test_relative(self):
+        assert "/blog/article/1?q=testing" == str(self.url.relative())
+
+
+class TestNoTrailingSlash:
+    def test_path_extraction_without_trailing_slash(self):
+        u = URL(host="google.com", path="/blog/article/1")
+        assert "1" == u.path_segment(2)
+
+
+class TestBuilder:
+    def test_setting_list_as_query_params(self):
+        first = URL.from_string("?q=testing")
+        second = URL().query_params(first.query_params())
+        assert first.query() == second.query()
+
+    def test_add_path_segment(self):
+        url = (
+            URL("http://example.com";)
+            .add_path_segment("one")
+            .add_path_segment("two")
+            .add_path_segment("three")
+        )
+        assert "/one/two/three" == url.path()
+
+    def test_setting_single_item_list_as_query_param(self):
+        url = URL().query_param("q", ["testing"])
+        assert "testing" == url.query_param("q")
+
+    def test_setting_list_as_query_param(self):
+        url = URL().query_param("q", ["testing", "eggs"])
+        assert ["testing" == "eggs"], url.query_param("q", as_list=True)
+
+    def test_build_relative_url(self):
+        url = URL().path("searching")
+        assert "/searching" == str(url)
+
+    def test_build_relative_url_with_params(self):
+        URL().path("/searching").query_param("q", "testing")
+
+    def test_build_with_path_segments(self):
+        u = URL().path_segments(["path", "to", "page"])
+        assert "/path/to/page" == u.as_string()
+
+    def test_set_fragment(self):
+        url = URL.from_string("http://www.google.com/";).fragment("hello")
+        assert "hello" == url.fragment()
+
+    def test_set_scheme(self):
+        url = URL.from_string("http://www.google.com/";).scheme("https")
+        assert "https" == url.scheme()
+
+    def test_set_host(self):
+        url = URL.from_string("http://www.google.com/";).host("maps.google.com")
+        assert "maps.google.com" == url.host()
+
+    def test_set_path(self):
+        url = URL.from_string("http://www.google.com/";).path("search")
+        assert "/search" == url.path()
+
+    def test_set_path_with_special_chars(self):
+        url = URL.from_string("http://www.google.com/";).path("search 
something")
+        assert "/search%20something" == url.path()
+
+    def test_set_query(self):
+        url = URL.from_string("http://www.google.com/";).query("q=testing")
+        assert "testing" == url.query_param("q")
+
+    def test_set_port(self):
+        url = URL.from_string("http://www.google.com/";).port(8000)
+        assert 8000 == url.port()
+
+    def test_set_path_segment(self):
+        url = URL.from_string("http://www.google.com/a/b/c/";).path_segment(1, 
"d")
+        assert "/a/d/c/" == url.path()
+
+    def test_set_query_param(self):
+        url = URL.from_string("http://www.google.com/search";).query_param(
+            "q", "testing"
+        )
+        assert "testing" == url.query_param("q")
+
+    def test_set_query_params(self):
+        url = URL.from_string("http://www.google.com/search";).query_params(
+            {"q": "testing"}
+        )
+        assert "testing" == url.query_param("q")
+
+    def test_set_subdomain(self):
+        url = URL.from_string("http://www.google.com/search";).subdomain(0, 
"www2")
+        assert "www2" == url.subdomain(0)
+
+    def test_set_subdomains(self):
+        url = URL().subdomains(["www", "google", "com"])
+        assert "http://www.google.com/"; == str(url)
+
+    def test_remove_domain(self):
+        url = URL("https://example.com/hello?x=100";)
+        new = url.domain("")
+        assert "/hello?x=100" == str(new)
+
+    def test_remove_port(self):
+        url = URL("https://example.com/hello?x=100";)
+        new = url.port("")
+        assert "https://example.com/hello?x=100"; == str(new)
+
+
+class TestMisc:
+    def test_url_can_be_used_as_key_in_dict(self):
+        u = URL.from_string("http://google.com";)
+        {u: 0}
+
+    def test_equality_comparison(self):
+        assert URL.from_string("http://google.com";) == URL.from_string(
+            "http://google.com";
+        )
+
+    def test_negative_equality_comparison(self):
+        assert URL.from_string("http://google.com";) != URL.from_string(
+            "https://google.com";
+        )
+
+    def test_urls_are_hashable(self):
+        u = URL.from_string("http://google.com";)
+        hash(u)
+
+    def test_urls_can_be_pickled(self):
+        u = URL.from_string("http://google.com";)
+        pickle.dumps(u)
+
+    def test_urls_can_be_pickled_and_restored(self):
+        u = URL.from_string("http://google.com";)
+        pickled = pickle.dumps(u)
+        v = pickle.loads(pickled)
+        assert u == v
+
+
+class TestQueryParamList:
+    def test_set_list(self):
+        base = URL("http://127.0.0.1/";)
+        url = base.query_param("q", ["something", "else"])
+        values = url.query_param("q", as_list=True)
+        assert ["something" == "else"], values
+
+    def test_remove_item_from_list(self):
+        base = URL("http://127.0.0.1/?q=a&q=b";)
+        url = base.remove_query_param("q", "a")
+        values = url.query_param("q", as_list=True)
+        assert ["b"] == values
+
+    def test_append_to_existing_list(self):
+        base = URL("http://127.0.0.1/?q=a&q=b";)
+        url = base.append_query_param("q", "c")
+        values = url.query_param("q", as_list=True)
+        assert ["a", "b", "c"] == values
+
+    def test_append_to_nonexistant_list(self):
+        base = URL("http://127.0.0.1/?q=a&q=b";)
+        url = base.append_query_param("p", "c")
+        values = url.query_param("p", as_list=True)
+        assert ["c"] == values
+
+
+class TestUnicodeExtraction:
+    def test_get_query_param_ascii_url(self):
+        unicode_param = "????????????????"
+
+        # Python 2.6 requires bytes for quote
+        urlencoded_param = quote(unicode_param.encode("utf8"))
+        url = "http://www.google.com/blog/article/1?q="; + urlencoded_param
+
+        ascii_url = URL.from_string(url.encode("ascii"))
+        param = ascii_url.query_param("q")
+        assert param == unicode_param
+
+    def test_get_query_param_unicode_url(self):
+        unicode_param = "????????????????"
+
+        # Python 2.6 requires bytes for quote
+        urlencoded_param = quote(unicode_param.encode("utf8"))
+        url = "http://www.google.com/blog/article/1?q="; + urlencoded_param
+
+        # django request.get_full_path() returns url as unicode
+        unicode_url = URL.from_string(url)
+
+        param = unicode_url.query_param("q")
+        assert param == unicode_param
+
+
+class TestUnicode:
+    base = URL("http://127.0.0.1/";)
+    text = "??"
+    bytes = text.encode("utf8")
+
+    def test_set_unicode_query_param_value(self):
+        url = self.base.query_param("q", self.text)
+        assert self.text == url.query_param("q")
+
+    def test_set_bytestring_query_param_value(self):
+        url = self.base.query_param("q", self.bytes)
+        assert self.text == url.query_param("q")
+
+    def test_set_unicode_query_param_key(self):
+        url = self.base.query_param(self.text, "value")
+        assert "value" == url.query_param(self.text)
+
+    def test_set_bytestring_query_param_key(self):
+        url = self.base.query_param(self.bytes, "value")
+        assert "value" == url.query_param(self.text)
+
+    def test_append_unicode_query_param(self):
+        url = self.base.append_query_param("q", self.text)
+        assert self.text == url.query_param("q")
+
+    def test_append_bytestring_query_param(self):
+        url = self.base.append_query_param("q", self.bytes)
+        assert self.text == url.query_param("q")
+
+    def test_set_unicode_query_params(self):
+        url = self.base.query_params({"q": self.text})
+        assert self.text == url.query_param("q")
+
+    def test_set_bytestring_query_params(self):
+        url = self.base.query_params({"q": self.bytes})
+        assert self.text == url.query_param("q")
+
+    def test_add_unicode_path_segment(self):
+        url = self.base.add_path_segment(self.text)
+        assert self.text == url.path_segment(0)
+
+    def test_add_bytestring_path_segment(self):
+        url = self.base.add_path_segment(self.bytes)
+        assert self.text == url.path_segment(0)
+
+    def test_add_unicode_fragment(self):
+        url = self.base.fragment(self.text)
+        assert self.text == url.fragment()
+
+
+class QuotedSlashesTests:
+    def test_slashes_in_path(self):
+        u = URL().add_path_segment("test/egg")
+        assert u.as_string() == "/test%2Fegg"
+
+    def test_slashes_in_path(self):
+        u = URL("/something").path_segment(0, "test/egg")
+        assert u.as_string() == "/test%2Fegg"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/test_utils.py 
new/purl-1.6/tests/test_utils.py
--- old/purl-1.5/tests/test_utils.py    1970-01-01 01:00:00.000000000 +0100
+++ new/purl-1.6/tests/test_utils.py    2021-05-15 23:00:41.000000000 +0200
@@ -0,0 +1,10 @@
+from purl.url import to_utf8, to_unicode
+
+
+class TestUnicodeHelper:
+
+    def test_convert_int_to_bytes(self):
+        assert '1024' == to_utf8(1024)
+
+    def test_convert_int_to_unicode(self):
+        assert u'1024' == to_unicode(1024)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/url_tests.py 
new/purl-1.6/tests/url_tests.py
--- old/purl-1.5/tests/url_tests.py     2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/tests/url_tests.py     1970-01-01 01:00:00.000000000 +0100
@@ -1,457 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-from purl import URL
-from unittest import TestCase
-
-import pickle
-
-try:
-    from urllib.parse import quote
-except ImportError:
-    from urllib import quote
-
-
-class ConstructorTests(TestCase):
-
-    def test_url_can_be_created_with_just_host(self):
-        u = URL(host='google.com')
-        self.assertEqual('http://google.com/', str(u))
-
-    def test_url_can_be_created_with_host_and_schema(self):
-        u = URL(host='google.com', scheme='https')
-        self.assertEqual('https://google.com/', str(u))
-
-    def test_url_can_be_created_with_host_and_post(self):
-        u = URL(host='localhost', port=8000)
-        self.assertEqual('http://localhost:8000/', str(u))
-
-    def test_url_can_be_created_with_username_only(self):
-        u = URL(scheme='postgres', username='user', host='127.0.0.1', 
port='5432', path='/db_name')
-        self.assertEqual('postgres://user@127.0.0.1:5432/db_name', str(u))
-
-    def test_no_args_to_constructor(self):
-        u = URL()
-        self.assertEqual('/', str(u))
-
-    def test_as_string(self):
-        self.assertEqual('/', URL().as_string())
-
-    def test_full_url_can_be_used_as_first_param(self):
-        u = URL('https://github.com')
-        self.assertEqual('https://github.com', u.as_string())
-
-    def test_kwargs_take_priority_when_used_with_full_url(self):
-        u = URL('https://github.com', scheme='http')
-        self.assertEqual('http://github.com', u.as_string())
-
-    def test_creation_with_host_and_path(self):
-        u = URL(host='localhost', path="boo")
-        self.assertEqual('http://localhost/boo', str(u))
-
-    def test_creation_with_host_and_path_2(self):
-        u = URL(host='localhost').add_path_segment('boo')
-        self.assertEqual('http://localhost/boo', str(u))
-
-
-class MoreFactoryTests(TestCase):
-
-    def setUp(self):
-        self.url_str = 
'https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token=EC-6469953681606921P&AMT=200&CURRENCYCODE=GBP&RETURNURL=http%3A%2F%2Fexample.com%2Fcheckout%2Fpaypal%2Fresponse%2Fsuccess%2F&CANCELURL=http%3A%2F%2Fexample.com%2Fcheckout%2Fpaypal%2Fresponse%2Fcancel%2F'
-        self.url = URL.from_string(self.url_str)
-
-    def test_extracting_query_param(self):
-        return_url = self.url.query_param('RETURNURL')
-        
self.assertEqual('http://example.com/checkout/paypal/response/success/',
-                         return_url)
-
-
-class FactoryTests(TestCase):
-
-    def setUp(self):
-        self.url_str = 'http://www.google.com/search/?q=testing#fragment'
-        self.url = URL.from_string(self.url_str)
-
-    def test_scheme(self):
-        self.assertEqual('http', self.url.scheme())
-
-    def test_fragment(self):
-        self.assertEqual('fragment', self.url.fragment())
-
-    def test_path(self):
-        self.assertEqual('/search/', self.url.path())
-
-    def test_host(self):
-        self.assertEqual('www.google.com', self.url.host())
-
-    def test_string_version(self):
-        self.assertEqual(self.url_str, str(self.url))
-
-
-class EdgeCaseExtractionTests(TestCase):
-
-    def test_no_equals_sign_means_empty_string(self):
-        url = URL.from_string('http://www.google.com/blog/article/1?q')
-        self.assertEqual('', url.query_param('q'))
-
-    def test_list_extraction(self):
-        url = URL.from_string('http://www.google.com/?q=1&q=2&q=3')
-        self.assertEqual(['1', '2', '3'], url.query_param('q'))
-
-    def test_username_extraction(self):
-        url = URL.from_string('ftp://user:p...@ftp.host')
-        self.assertEqual('user', url.username())
-        self.assertEqual('pw', url.password())
-
-    def test_username_in_unicode_repr(self):
-        u = 'ftp://user:p...@ftp.host'
-        url = URL.from_string(u)
-        self.assertEqual(u, str(url))
-
-    def test_auth_in_netloc(self):
-        url = URL.from_string('ftp://user:p...@ftp.host')
-        self.assertEqual('user:p...@ftp.host', url.netloc())
-
-    def test_auth_with_special_char(self):
-        url = URL.from_string('ftp://user:b@z...@ftp.host')
-        self.assertEqual('user', url.username())
-        self.assertEqual('b@z', url.password())
-
-    def test_port_in_netloc(self):
-        url = URL.from_string('http://localhost:5000')
-        self.assertEqual('localhost', url.host())
-        self.assertEqual(5000, url.port())
-
-    def test_passwordless_netloc(self):
-        url = URL.from_string('postgres://user@127.0.0.1:5432/db_name')
-        self.assertEqual('user', url.username())
-        self.assertTrue(url.password() is None)
-
-    def test_unicode_username_and_password(self):
-        url = 
URL.from_string('postgres://je????:nieje????@127.0.0.1:5432/db_name')
-        self.assertEqual('je????', url.username())
-        self.assertEqual('nieje????', url.password())
-
-    def test_unicode_username_only(self):
-        url = URL.from_string('postgres://je????@127.0.0.1:5432/db_name')
-        self.assertEqual('je????', url.username())
-        self.assertTrue(url.password() is None)
-
-    def test_port_for_https_url(self):
-        url = URL.from_string('https://github.com')
-        self.assertEqual(None, url.port())
-
-
-class SimpleExtractionTests(TestCase):
-
-    def setUp(self):
-        self.url = 
URL.from_string('http://www.google.com/blog/article/1?q=testing')
-
-    def test_has_actual_param(self):
-        self.assertTrue(self.url.has_query_param('q'))
-
-    def test_remove_query_param(self):
-        new_url = self.url.remove_query_param('q')
-        self.assertEqual('http://www.google.com/blog/article/1',
-                         new_url.as_string())
-
-    def test_has_query_params(self):
-        self.assertTrue(self.url.has_query_params(['q']))
-
-    def test_has_query_params_negative(self):
-        self.assertFalse(self.url.has_query_params(['q', 'r']))
-
-    def test_netloc(self):
-        self.assertEqual('www.google.com', self.url.netloc())
-
-    def test_path_extraction(self):
-        self.assertEqual('1', self.url.path_segment(2))
-
-    def test_port_defaults_to_none(self):
-        self.assert_(self.url.port() is None)
-
-    def test_scheme(self):
-        self.assertEqual('http', self.url.scheme())
-
-    def test_host(self):
-        self.assertEqual('www.google.com', self.url.host())
-
-    def test_domain(self):
-        self.assertEqual('www.google.com', self.url.domain())
-
-    def test_subdomains(self):
-        self.assertEqual(['www', 'google', 'com'], self.url.subdomains())
-
-    def test_subdomain(self):
-        self.assertEqual('www', self.url.subdomain(0))
-
-    def test_invalid_subdomain_raises_indexerror(self):
-        self.assertRaises(IndexError, self.url.subdomain, 10)
-
-    def test_path(self):
-        self.assertEqual('/blog/article/1', self.url.path())
-
-    def test_query(self):
-        self.assertEqual('q=testing', self.url.query())
-
-    def test_query_param_as_list(self):
-        self.assertEqual(['testing'], self.url.query_param('q', as_list=True))
-
-    def test_query_params(self):
-        self.assertEqual({'q': ['testing']}, self.url.query_params())
-
-    def test_path_extraction_returns_none_if_index_too_large(self):
-        self.assert_(self.url.path_segment(14) is None)
-
-    def test_path_extraction_can_take_default_value(self):
-        self.assertEqual('hello', self.url.path_segment(3, default='hello'))
-
-    def test_parameter_extraction(self):
-        self.assertEqual('testing', self.url.query_param('q'))
-
-    def test_parameter_extraction_with_default(self):
-        self.assertEqual('eggs', self.url.query_param('p', default='eggs'))
-
-    def test_parameter_extraction_is_none_if_not_found(self):
-        self.assert_(self.url.query_param('p') is None)
-
-    def test_path_segments(self):
-        self.assertEqual(('blog', 'article', '1'), self.url.path_segments())
-
-    def test_relative(self):
-        self.assertEqual('/blog/article/1?q=testing', str(self.url.relative()))
-
-
-class NoTrailingSlashTests(TestCase):
-
-    def test_path_extraction_without_trailing_slash(self):
-        u = URL(host='google.com', path='/blog/article/1')
-        self.assertEqual('1', u.path_segment(2))
-
-
-class BuilderTests(TestCase):
-
-    def test_setting_list_as_query_params(self):
-        first = URL.from_string('?q=testing')
-        second = URL().query_params(first.query_params())
-        self.assertEqual(first.query(), second.query())
-
-    def test_add_path_segment(self):
-        url = URL('http://example.com').add_path_segment('one')\
-                .add_path_segment('two')\
-                .add_path_segment('three')
-        self.assertEqual('/one/two/three', url.path())
-
-    def test_setting_single_item_list_as_query_param(self):
-        url = URL().query_param('q', ['testing'])
-        self.assertEqual('testing', url.query_param('q'))
-
-    def test_setting_list_as_query_param(self):
-        url = URL().query_param('q', ['testing', 'eggs'])
-        self.assertEqual(['testing', 'eggs'], url.query_param('q', 
as_list=True))
-
-    def test_build_relative_url(self):
-        url = URL().path('searching')
-        self.assertEqual('/searching', str(url))
-
-    def test_build_relative_url_with_params(self):
-        URL().path('/searching').query_param('q', 'testing')
-
-    def test_build_with_path_segments(self):
-        u = URL().path_segments(['path', 'to', 'page'])
-        self.assertEqual('/path/to/page', u.as_string())
-
-    def test_set_fragment(self):
-        url = URL.from_string('http://www.google.com/').fragment('hello')
-        self.assertEqual('hello', url.fragment())
-
-    def test_set_scheme(self):
-        url = URL.from_string('http://www.google.com/').scheme('https')
-        self.assertEqual('https', url.scheme())
-
-    def test_set_host(self):
-        url = URL.from_string('http://www.google.com/').host('maps.google.com')
-        self.assertEqual('maps.google.com', url.host())
-
-    def test_set_path(self):
-        url = URL.from_string('http://www.google.com/').path('search')
-        self.assertEqual('/search', url.path())
-
-    def test_set_path_with_special_chars(self):
-        url = URL.from_string('http://www.google.com/').path('search 
something')
-        self.assertEqual('/search%20something', url.path())
-
-    def test_set_query(self):
-        url = URL.from_string('http://www.google.com/').query('q=testing')
-        self.assertEqual('testing', url.query_param('q'))
-
-    def test_set_port(self):
-        url = URL.from_string('http://www.google.com/').port(8000)
-        self.assertEqual(8000, url.port())
-
-    def test_set_path_segment(self):
-        url = URL.from_string('http://www.google.com/a/b/c/').path_segment(1, 
'd')
-        self.assertEqual('/a/d/c/', url.path())
-
-    def test_set_query_param(self):
-        url = URL.from_string('http://www.google.com/search').query_param('q', 
'testing')
-        self.assertEqual('testing', url.query_param('q'))
-
-    def test_set_query_params(self):
-        url = 
URL.from_string('http://www.google.com/search').query_params({'q': 'testing'})
-        self.assertEqual('testing', url.query_param('q'))
-
-    def test_set_subdomain(self):
-        url = URL.from_string('http://www.google.com/search').subdomain(0, 
'www2')
-        self.assertEqual('www2', url.subdomain(0))
-
-    def test_set_subdomains(self):
-        url = URL().subdomains(['www', 'google', 'com'])
-        self.assertEqual('http://www.google.com/', str(url))
-
-    def test_remove_domain(self):
-        url = URL('https://example.com/hello?x=100')
-        new = url.domain('')
-        self.assertEqual('/hello?x=100', str(new))
-
-    def test_remove_port(self):
-        url = URL('https://example.com/hello?x=100')
-        new = url.port('')
-        self.assertEqual('https://example.com/hello?x=100', str(new))
-
-
-class MiscTests(TestCase):
-
-    def test_url_can_be_used_as_key_in_dict(self):
-        u = URL.from_string('http://google.com')
-        {u: 0}
-
-    def test_equality_comparison(self):
-        self.assertEqual(URL.from_string('http://google.com'),
-                         URL.from_string('http://google.com'))
-
-    def test_negative_equality_comparison(self):
-        self.assertNotEqual(URL.from_string('http://google.com'),
-                            URL.from_string('https://google.com'))
-
-    def test_urls_are_hashable(self):
-        u = URL.from_string('http://google.com')
-        hash(u)
-
-    def test_urls_can_be_pickled(self):
-        u = URL.from_string('http://google.com')
-        pickle.dumps(u)
-
-    def test_urls_can_be_pickled_and_restored(self):
-        u = URL.from_string('http://google.com')
-        pickled = pickle.dumps(u)
-        v = pickle.loads(pickled)
-        self.assertEqual(u, v)
-
-
-class QueryParamListTests(TestCase):
-
-    def test_set_list(self):
-        base = URL('http://127.0.0.1/')
-        url = base.query_param('q', ['something', 'else'])
-        values = url.query_param('q', as_list=True)
-        self.assertEqual(['something', 'else'], values)
-
-    def test_remove_item_from_list(self):
-        base = URL('http://127.0.0.1/?q=a&q=b')
-        url = base.remove_query_param('q', 'a')
-        values = url.query_param('q', as_list=True)
-        self.assertEqual(['b'], values)
-
-    def test_append_to_existing_list(self):
-        base = URL('http://127.0.0.1/?q=a&q=b')
-        url = base.append_query_param('q', 'c')
-        values = url.query_param('q', as_list=True)
-        self.assertEqual(['a', 'b', 'c'], values)
-
-    def test_append_to_nonexistant_list(self):
-        base = URL('http://127.0.0.1/?q=a&q=b')
-        url = base.append_query_param('p', 'c')
-        values = url.query_param('p', as_list=True)
-        self.assertEqual(['c'], values)
-
-
-class UnicodeExtractionTests(TestCase):
-    def setUp(self):
-        self.unicode_param = '????????????????'
-        # Python 2.6 requires bytes for quote
-        self.urlencoded_param = quote(self.unicode_param.encode('utf8'))
-        url = 'http://www.google.com/blog/article/1?q=' + self.urlencoded_param
-        self.ascii_url = URL.from_string(url.encode('ascii'))
-        # django request.get_full_path() returns url as unicode
-        self.unicode_url = URL.from_string(url)
-
-    def test_get_query_param_ascii_url(self):
-        param = self.ascii_url.query_param('q')
-        self.assertEqual(param, self.unicode_param)
-
-    def test_get_query_param_unicode_url(self):
-        param = self.unicode_url.query_param('q')
-        self.assertEqual(param, self.unicode_param)
-
-
-class UnicodeTests(TestCase):
-
-    def setUp(self):
-        self.base = URL('http://127.0.0.1/')
-        self.text = u'??'
-        self.bytes = self.text.encode('utf8')
-
-    def test_set_unicode_query_param_value(self):
-        url = self.base.query_param('q', self.text)
-        self.assertEqual(self.text, url.query_param('q'))
-
-    def test_set_bytestring_query_param_value(self):
-        url = self.base.query_param('q', self.bytes)
-        self.assertEqual(self.text, url.query_param('q'))
-
-    def test_set_unicode_query_param_key(self):
-        url = self.base.query_param(self.text, 'value')
-        self.assertEqual('value', url.query_param(self.text))
-
-    def test_set_bytestring_query_param_key(self):
-        url = self.base.query_param(self.bytes, 'value')
-        self.assertEqual('value', url.query_param(self.text))
-
-    def test_append_unicode_query_param(self):
-        url = self.base.append_query_param('q', self.text)
-        self.assertEqual(self.text, url.query_param('q'))
-
-    def test_append_bytestring_query_param(self):
-        url = self.base.append_query_param('q', self.bytes)
-        self.assertEqual(self.text, url.query_param('q'))
-
-    def test_set_unicode_query_params(self):
-        url = self.base.query_params({'q': self.text})
-        self.assertEqual(self.text, url.query_param('q'))
-
-    def test_set_bytestring_query_params(self):
-        url = self.base.query_params({'q': self.bytes})
-        self.assertEqual(self.text, url.query_param('q'))
-
-    def test_add_unicode_path_segment(self):
-        url = self.base.add_path_segment(self.text)
-        self.assertEqual(self.text, url.path_segment(0))
-
-    def test_add_bytestring_path_segment(self):
-        url = self.base.add_path_segment(self.bytes)
-        self.assertEqual(self.text, url.path_segment(0))
-
-    def test_add_unicode_fragment(self):
-        url = self.base.fragment(self.text)
-        self.assertEqual(self.text, url.fragment())
-
-
-class QuotedSlashesTests(TestCase):
-
-    def test_slashes_in_path(self):
-        u = URL().add_path_segment('test/egg')
-        self.assertEqual(u.as_string(), '/test%2Fegg')
-
-    def test_slashes_in_path(self):
-        u = URL('/something').path_segment(0, 'test/egg')
-        self.assertEqual(u.as_string(), '/test%2Fegg')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tests/utils_tests.py 
new/purl-1.6/tests/utils_tests.py
--- old/purl-1.5/tests/utils_tests.py   2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/tests/utils_tests.py   1970-01-01 01:00:00.000000000 +0100
@@ -1,12 +0,0 @@
-from unittest import TestCase
-
-from purl.url import to_utf8, to_unicode
-
-
-class TestUnicodeHelper(TestCase):
-
-    def test_convert_int_to_bytes(self):
-        self.assertEqual('1024', to_utf8(1024))
-
-    def test_convert_int_to_unicode(self):
-        self.assertEqual(u'1024', to_unicode(1024))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/purl-1.5/tox.ini new/purl-1.6/tox.ini
--- old/purl-1.5/tox.ini        2019-03-10 21:51:02.000000000 +0100
+++ new/purl-1.6/tox.ini        2021-05-15 23:00:41.000000000 +0200
@@ -4,8 +4,8 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = py26, py27, py34, pypy
+envlist = py26, py27, py36, py37, py38, pypy, pypy3
 
 [testenv]
-commands = nosetests []
+commands = pytest
 deps = -r{toxinidir}/requirements.txt

Reply via email to