def test_parse_devpi_devpi(): with open(join(DATA_DIR, 'devpi_devpi.html'), 'rb') as fp: assert parse_project_page( fp.read(), 'https://m.devpi.net/fschulze/dev/+simple/devpi', from_encoding='utf-8', ) == [ DistributionPackage( filename='devpi-2.2.0.tar.gz', project='devpi', version='2.2.0', package_type='sdist', url="https://m.devpi.net/root/pypi/+f/159/5e5f095022ce7/devpi-2.2.0.tar.gz#sha256=1595e5f095022ce7b569326ddceef5d638d936cfb79578d7fc472d46c556cd30", requires_python=None, has_sig=False, yanked=None, ), DistributionPackage( filename='devpi-2.1.0.tar.gz', project='devpi', version='2.1.0', package_type='sdist', url="https://m.devpi.net/root/pypi/+f/453/c95c8472d6645/devpi-2.1.0.tar.gz#sha256=453c95c8472d66456fd4b3a1526f7f9523f77095111068ef0d60e0dd97e5da27", requires_python=None, has_sig=False, yanked=None, ), DistributionPackage( filename='devpi-2.0.3.tar.gz', project='devpi', version='2.0.3', package_type='sdist', url="https://m.devpi.net/fschulze/dev/+f/197/3b59b5a67362a/devpi-2.0.3.tar.gz#sha256=1973b59b5a67362a44fbbcee6a5f078f221c5d6d5545215045b5ceb1fdb477c6", requires_python=None, has_sig=False, yanked=None, ), DistributionPackage( filename='devpi-2.0.2.tar.gz', project='devpi', version='2.0.2', package_type='sdist', url="https://m.devpi.net/root/pypi/+f/ed6/87407ee52c7da/devpi-2.0.2.tar.gz#sha256=ed687407ee52c7dacaaf5d51634a69464cf26c568acee87f6ce595ac3de261e5", requires_python=None, has_sig=False, yanked=None, ), DistributionPackage( filename='devpi-2.0.1.tar.gz', project='devpi', version='2.0.1', package_type='sdist', url="https://m.devpi.net/root/pypi/+f/bc1/475505afa93aa/devpi-2.0.1.tar.gz#sha256=bc1475505afa93aa41a8fe10249406c4e8804dce4d014ca802807505e3f7594a", requires_python=None, has_sig=False, yanked=None, ), ]
def test_parse_qypi_mixed(): with open(join(DATA_DIR, 'qypi_mixed.html'), 'rb') as fp: assert parse_project_page( fp.read(), PYPI_SIMPLE_ENDPOINT + 'qypi/', from_encoding='utf-8', ) == [ DistributionPackage( filename='qypi-0.1.0-py3-none-any.whl', project='qypi', version='0.1.0', package_type='wheel', url="https://files.pythonhosted.org/packages/82/fc/9e25534641d7f63be93079bc07fa92bab136ddf5d4181059a1308a346f96/qypi-0.1.0-py3-none-any.whl#sha256=da69d28dcd527c0e372b3fa7b92fc333b327f8470175f035abc4e351b539189f", requires_python=None, has_sig=False, yanked='Metadata was smelly', ), DistributionPackage( filename='qypi-0.1.0.tar.gz', project='qypi', version='0.1.0', package_type='sdist', url="https://files.pythonhosted.org/packages/e4/fe/3fdb222a2916b94e9ca12d80c92dbbad1f7068c82fca42872d6c1739fead/qypi-0.1.0.tar.gz#sha256=212093de95b4f5f22e19fa18fe57fa33eccd63adb9b325fe1b673bf71912c551", requires_python="~=3.4", has_sig=False, yanked='', ), DistributionPackage( filename='qypi-0.1.0.post1-py3-none-any.whl', project='qypi', version='0.1.0.post1', package_type='wheel', url="https://files.pythonhosted.org/packages/f9/3f/6b184713e79da15cd451f0dab91864633175242f4d321df0cacdd2dc8300/qypi-0.1.0.post1-py3-none-any.whl#sha256=5946a4557550479af90278e5418cd2c32a2626936075078a4c7096be52d43078", requires_python=None, has_sig=True, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.post1.tar.gz', project='qypi', version='0.1.0.post1', package_type='sdist', url="https://files.pythonhosted.org/packages/0e/49/3056ee68b44c8eab4d4698b52ae4d18c0db92c80abc312894c02c4722621/qypi-0.1.0.post1.tar.gz#sha256=c99eea315455cf9fde722599ab67eeefdff5c184bb3861a7fd82f8a9387c252d", requires_python="~=3.4", has_sig=True, yanked=None, ), DistributionPackage( filename='qypi-0.2.0-py3-none-any.whl', project='qypi', version='0.2.0', package_type='wheel', url="https://files.pythonhosted.org/packages/96/b8/9c2d0c3d0d95ccdaa04ebff77f8e85e9ca0888f2844b102d32a81ca3c92e/qypi-0.2.0-py3-none-any.whl#sha256=0923d60c5ff6aaf73c4805b5381868ccdf44d1cfe1d1a659d679be821fe38d53", requires_python=None, has_sig=False, yanked=None, ), ]
def test_parse_qypi_base(): with open(join(DATA_DIR, 'qypi_base.html'), 'rb') as fp: with pytest.warns(DeprecationWarning): files = parse_project_page( fp.read(), PYPI_SIMPLE_ENDPOINT + 'qypi/', from_encoding='utf-8', ) assert files == [ DistributionPackage( filename='qypi-0.1.0-py3-none-any.whl', project='qypi', version='0.1.0', package_type='wheel', url= "https://files.pythonhosted.org/packages/82/fc/9e25534641d7f63be93079bc07fa92bab136ddf5d4181059a1308a346f96/qypi-0.1.0-py3-none-any.whl#sha256=da69d28dcd527c0e372b3fa7b92fc333b327f8470175f035abc4e351b539189f", requires_python="~=3.4", has_sig=None, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.tar.gz', project='qypi', version='0.1.0', package_type='sdist', url= "https://files.pythonhosted.org/packages/e4/fe/3fdb222a2916b94e9ca12d80c92dbbad1f7068c82fca42872d6c1739fead/qypi-0.1.0.tar.gz#sha256=212093de95b4f5f22e19fa18fe57fa33eccd63adb9b325fe1b673bf71912c551", requires_python="~=3.4", has_sig=None, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.post1-py3-none-any.whl', project='qypi', version='0.1.0.post1', package_type='wheel', url= "https://files.pythonhosted.org/packages/f9/3f/6b184713e79da15cd451f0dab91864633175242f4d321df0cacdd2dc8300/qypi-0.1.0.post1-py3-none-any.whl#sha256=5946a4557550479af90278e5418cd2c32a2626936075078a4c7096be52d43078", requires_python="~=3.4", has_sig=None, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.post1.tar.gz', project='qypi', version='0.1.0.post1', package_type='sdist', url= "https://files.pythonhosted.org/packages/0e/49/3056ee68b44c8eab4d4698b52ae4d18c0db92c80abc312894c02c4722621/qypi-0.1.0.post1.tar.gz#sha256=c99eea315455cf9fde722599ab67eeefdff5c184bb3861a7fd82f8a9387c252d", requires_python="~=3.4", has_sig=None, yanked=None, ), ]
def test_get_no_digests(fragment): pkg = DistributionPackage( filename='qypi-0.1.0-py3-none-any.whl', project='qypi', version='0.1.0', package_type='wheel', url="https://files.pythonhosted.org/packages/82/fc/9e25534641d7f63be93079bc07fa92bab136ddf5d4181059a1308a346f96/qypi-0.1.0-py3-none-any.whl" + fragment, requires_python=None, has_sig=False, yanked=None, ) assert pkg.get_digests() == {}
def test_get_digests(): pkg = DistributionPackage( filename='qypi-0.1.0-py3-none-any.whl', project='qypi', version='0.1.0', package_type='wheel', url="https://files.pythonhosted.org/packages/82/fc/9e25534641d7f63be93079bc07fa92bab136ddf5d4181059a1308a346f96/qypi-0.1.0-py3-none-any.whl#sha256=da69d28dcd527c0e372b3fa7b92fc333b327f8470175f035abc4e351b539189f", requires_python=None, has_sig=False, yanked=None, ) assert pkg.get_digests() == { "sha256": "da69d28dcd527c0e372b3fa7b92fc333b327f8470175f035abc4e351b539189f" }
def test_latin2_declarations(content_type, body_decl): # This test is deliberately weird in order to make sure the code is # actually paying attention to the encoding declarations and not just # assuming UTF-8 because the input happens to be valid UTF-8. responses.add( method=responses.GET, url='https://test.nil/simple/project/', body=body_decl + b'<a href="../files/project-0.1.0-p\xC3\xBF42-none-any.whl">project-0.1.0-p\xC3\xBF42-none-any.whl</a>', content_type=content_type, ) simple = PyPISimple('https://test.nil/simple/') assert simple.get_project_page('project') == ProjectPage( project='project', packages=[ DistributionPackage( filename=u'project-0.1.0-p\u0102\u017C42-none-any.whl', project='project', version='0.1.0', package_type='wheel', url= u"https://test.nil/simple/files/project-0.1.0-p\u0102\u017C42-none-any.whl", requires_python=None, has_sig=None, yanked=None, ), ], last_serial=None, repository_version=None, )
def test_utf8_declarations(content_type, body_decl): responses.add( method=responses.GET, url='https://test.nil/simple/project/', body=body_decl + b'<a href="../files/project-0.1.0-p\xC3\xBF42-none-any.whl">project-0.1.0-p\xC3\xBF42-none-any.whl</a>', content_type=content_type, ) simple = PyPISimple('https://test.nil/simple/') assert simple.get_project_page('project') == ProjectPage( project='project', packages=[ DistributionPackage( filename='project-0.1.0-p\xFF42-none-any.whl', project='project', version='0.1.0', package_type='wheel', url= "https://test.nil/simple/files/project-0.1.0-p\xFF42-none-any.whl", requires_python=None, has_sig=None, yanked=None, ), ], last_serial=None, repository_version=None, )
def test_redirected_project_page(): responses.add( method=responses.GET, url='https://nil.test/simple/project/', status=301, headers={'Location': 'https://test.nil/simple/project/'}, ) responses.add( method=responses.GET, url='https://test.nil/simple/project/', body='<a href="../files/project-0.1.0.tar.gz">project-0.1.0.tar.gz</a>', content_type='text/html', ) simple = PyPISimple('https://nil.test/simple/') assert simple.get_project_page('project') == ProjectPage( project='project', packages=[ DistributionPackage( filename='project-0.1.0.tar.gz', project='project', version='0.1.0', package_type='sdist', url="https://test.nil/simple/files/project-0.1.0.tar.gz", requires_python=None, has_sig=None, yanked=None, ), ], last_serial=None, repository_version=None, )
def test_project_hint_received(): """ Test that the argument to ``get_project_page()`` is used to disambiguate filenames """ with (DATA_DIR / 'aws-adfs-ebsco.html').open() as fp: responses.add( method=responses.GET, url='https://test.nil/simple/aws-adfs-ebsco/', body=fp.read(), content_type='text/html', ) simple = PyPISimple('https://test.nil/simple/') assert simple.get_project_page('aws-adfs-ebsco') == ProjectPage( project='aws-adfs-ebsco', packages=[ DistributionPackage( filename='aws-adfs-ebsco-0.3.6-2.tar.gz', project='aws-adfs-ebsco', version='0.3.6-2', package_type='sdist', url= "https://files.pythonhosted.org/packages/13/b7/a69bdbf294db5ba0973ee45a2b2ce7045030cd922e1c0ca052d102c45b95/aws-adfs-ebsco-0.3.6-2.tar.gz#sha256=6eadd17408e1f26a313bc75afaa3011333bc2915461c446720bafd7608987e1e", requires_python=None, has_sig=None, yanked=None, ), DistributionPackage( filename='aws-adfs-ebsco-0.3.7-1.tar.gz', project='aws-adfs-ebsco', version='0.3.7-1', package_type='sdist', url= "https://files.pythonhosted.org/packages/86/8a/46c2a99113cfbb7d6c089b2128ca9e4faaea1f6a1d4e17577fd9a3396bee/aws-adfs-ebsco-0.3.7-1.tar.gz#sha256=7992abc36d0061896a3f06f055e053ffde9f3fcf483340adfa675c65ebfb3f8d", requires_python=None, has_sig=None, yanked=None, ), ], last_serial=None, repository_version=None, )
def test_utf8_declarations(content_type, body_decl): responses.add( method=responses.GET, url='https://test.nil/simple/project/', body=body_decl + b'<a href="../files/project-0.1.0-p\xC3\xBF42-none-any.whl">project-0.1.0-p\xC3\xBF42-none-any.whl</a>', content_type=content_type, ) simple = PyPISimple('https://test.nil/simple/') with pytest.warns(DeprecationWarning): files = simple.get_project_files('project') assert files == [ DistributionPackage( filename=u'project-0.1.0-p\xFF42-none-any.whl', project='project', version='0.1.0', package_type='wheel', url= u"https://test.nil/simple/files/project-0.1.0-p\xFF42-none-any.whl", requires_python=None, has_sig=None, yanked=None, ), ]
def test_session(content_type): session_dir = DATA_DIR / 'session01' with (session_dir / 'simple.html').open() as fp: responses.add( method=responses.GET, url='https://test.nil/simple/', body=fp.read(), content_type=content_type, headers={"x-pypi-last-serial": "12345"}, ) responses.add( method=responses.GET, url='https://test.nil/simple/', body='This URL should only be requested once.', status=500, ) with (session_dir / 'in-place.html').open() as fp: responses.add( method=responses.GET, url='https://test.nil/simple/in-place/', body=fp.read(), content_type=content_type, headers={"X-PYPI-LAST-SERIAL": "54321"}, ) responses.add( method=responses.GET, url='https://test.nil/simple/nonexistent/', body='Does not exist', status=404, ) simple = PyPISimple('https://test.nil/simple/') assert simple.get_index_page() == IndexPage( projects=['in_place', 'foo', 'BAR'], last_serial='12345', repository_version='1.0', ) assert simple.get_project_url('IN.PLACE') \ == 'https://test.nil/simple/in-place/' assert simple.get_project_page('IN.PLACE') == ProjectPage( project='IN.PLACE', packages=[ DistributionPackage( filename='in_place-0.1.1-py2.py3-none-any.whl', project='in_place', version='0.1.1', package_type='wheel', url= "https://files.pythonhosted.org/packages/34/81/2baaaa588ee1a6faa6354b7c9bc365f1b3da867707cd136dfedff7c06608/in_place-0.1.1-py2.py3-none-any.whl#sha256=e0732b6bdc2f1bfc4e1b96c1de2fbbd053bb2a9534547474a0485baa339bfa97", requires_python=None, has_sig=None, yanked=None, ), DistributionPackage( filename='in_place-0.1.1.tar.gz', project='in_place', version='0.1.1', package_type='sdist', url= "https://files.pythonhosted.org/packages/b9/ba/f1c67fb32c37ba4263326ae4ac6fd00b128025c9289b2fb31a60a0a22f90/in_place-0.1.1.tar.gz#sha256=ffa729fd0b818ac750aa31bafc886f266380e1c8557ba38f70f422d2f6a77e23", requires_python=None, has_sig=None, yanked=None, ), DistributionPackage( filename='in_place-0.2.0-py2.py3-none-any.whl', project='in_place', version='0.2.0', package_type='wheel', url= "https://files.pythonhosted.org/packages/9f/46/9f5679f3b2068e10b33c16a628a78b2b36531a9df08671bd0104f11d8461/in_place-0.2.0-py2.py3-none-any.whl#sha256=e1ad42a41dfde02092b411b1634a4be228e28c27553499a81ef04b377b28857c", requires_python=None, has_sig=None, yanked=None, ), DistributionPackage( filename='in_place-0.2.0.tar.gz', project='in_place', version='0.2.0', package_type='sdist', url= "https://files.pythonhosted.org/packages/f0/51/c30f1fad2b857f7b5d5ff76ec01f1f80dd0f2ab6b6afcde7b2aed54faa7e/in_place-0.2.0.tar.gz#sha256=ff783dca5d06f85b8d084871abd11a170d732423edb48c53ccb68c55fcbbeb76", requires_python=None, has_sig=None, yanked=None, ), DistributionPackage( filename='in_place-0.3.0-py2.py3-none-any.whl', project='in_place', version='0.3.0', package_type='wheel', url= "https://files.pythonhosted.org/packages/6f/84/ced31e646df335f8cd1b7884e3740b8c012314a28504542ef5631cdc1449/in_place-0.3.0-py2.py3-none-any.whl#sha256=af5ce9bd309f85a6bbe4119acbc0a67cda68f0ae616f0a76a947addc62791fda", requires_python=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4", has_sig=None, yanked=None, ), DistributionPackage( filename='in_place-0.3.0.tar.gz', project='in_place', version='0.3.0', package_type='sdist', url= "https://files.pythonhosted.org/packages/b6/cd/1dc736d5248420b15dd1546c2938aec7e6dab134e698e0768f54f1757af7/in_place-0.3.0.tar.gz#sha256=4758db1457c8addcd5f5b15ef870eab66b238e46e7d784bff99ab1b2126660ea", requires_python=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4", has_sig=None, yanked=None, ), ], last_serial='54321', repository_version='1.0', ) assert simple.get_project_page('nonexistent') is None
) @pytest.mark.parametrize('project,filename,base_url,encoding,page', [ ( 'qypi', 'qypi.html', PYPI_SIMPLE_ENDPOINT + 'qypi/', 'utf-8', ProjectPage( project='qypi', packages=[ DistributionPackage( filename='qypi-0.1.0-py3-none-any.whl', project='qypi', version='0.1.0', package_type='wheel', url="https://files.pythonhosted.org/packages/82/fc/9e25534641d7f63be93079bc07fa92bab136ddf5d4181059a1308a346f96/qypi-0.1.0-py3-none-any.whl#sha256=da69d28dcd527c0e372b3fa7b92fc333b327f8470175f035abc4e351b539189f", requires_python="~=3.4", has_sig=None, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.tar.gz', project='qypi', version='0.1.0', package_type='sdist', url="https://files.pythonhosted.org/packages/e4/fe/3fdb222a2916b94e9ca12d80c92dbbad1f7068c82fca42872d6c1739fead/qypi-0.1.0.tar.gz#sha256=212093de95b4f5f22e19fa18fe57fa33eccd63adb9b325fe1b673bf71912c551", requires_python="~=3.4", has_sig=None, yanked=None, ), DistributionPackage(
def test_parse_qypi(): with open(join(DATA_DIR, 'qypi.html'), 'rb') as fp: assert parse_project_page( fp.read(), PYPI_SIMPLE_ENDPOINT + 'qypi/', from_encoding='utf-8', ) == [ DistributionPackage( filename='qypi-0.1.0-py3-none-any.whl', project='qypi', version='0.1.0', package_type='wheel', url="https://files.pythonhosted.org/packages/82/fc/9e25534641d7f63be93079bc07fa92bab136ddf5d4181059a1308a346f96/qypi-0.1.0-py3-none-any.whl#sha256=da69d28dcd527c0e372b3fa7b92fc333b327f8470175f035abc4e351b539189f", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.tar.gz', project='qypi', version='0.1.0', package_type='sdist', url="https://files.pythonhosted.org/packages/e4/fe/3fdb222a2916b94e9ca12d80c92dbbad1f7068c82fca42872d6c1739fead/qypi-0.1.0.tar.gz#sha256=212093de95b4f5f22e19fa18fe57fa33eccd63adb9b325fe1b673bf71912c551", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.post1-py3-none-any.whl', project='qypi', version='0.1.0.post1', package_type='wheel', url="https://files.pythonhosted.org/packages/f9/3f/6b184713e79da15cd451f0dab91864633175242f4d321df0cacdd2dc8300/qypi-0.1.0.post1-py3-none-any.whl#sha256=5946a4557550479af90278e5418cd2c32a2626936075078a4c7096be52d43078", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.1.0.post1.tar.gz', project='qypi', version='0.1.0.post1', package_type='sdist', url="https://files.pythonhosted.org/packages/0e/49/3056ee68b44c8eab4d4698b52ae4d18c0db92c80abc312894c02c4722621/qypi-0.1.0.post1.tar.gz#sha256=c99eea315455cf9fde722599ab67eeefdff5c184bb3861a7fd82f8a9387c252d", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.2.0-py3-none-any.whl', project='qypi', version='0.2.0', package_type='wheel', url="https://files.pythonhosted.org/packages/96/b8/9c2d0c3d0d95ccdaa04ebff77f8e85e9ca0888f2844b102d32a81ca3c92e/qypi-0.2.0-py3-none-any.whl#sha256=0923d60c5ff6aaf73c4805b5381868ccdf44d1cfe1d1a659d679be821fe38d53", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.2.0.tar.gz', project='qypi', version='0.2.0', package_type='sdist', url="https://files.pythonhosted.org/packages/f6/6a/1d37c72684c19f28060bd7ed1bfe3bfb8c6b9b1132b0ea67f98c036930da/qypi-0.2.0.tar.gz#sha256=cf24ea8841d0f10a822fd5cf3809c1324e5b1eab34e148b841dae6ad54919c85", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.3.0-py3-none-any.whl', project='qypi', version='0.3.0', package_type='wheel', url="https://files.pythonhosted.org/packages/79/b4/dbdcc76c55d1714f2d51f1da25c2a8a59cd1e35357bcafefb7ef6efd8c78/qypi-0.3.0-py3-none-any.whl#sha256=4dddbfa57d6b0c23a0cc20aa17aa8b17c4b41bcbd57c8d273dad84601e85e2dd", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.3.0.tar.gz', project='qypi', version='0.3.0', package_type='sdist', url="https://files.pythonhosted.org/packages/46/08/08f54b999c68fb1973824d4ac290a872136e978c6747dca647fc8116c59f/qypi-0.3.0.tar.gz#sha256=d23f45234a2f7431bd331b9fd4dedc0ff8de1361e171f4f47bb83a15b5726ba1", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.4.0-py3-none-any.whl', project='qypi', version='0.4.0', package_type='wheel', url="https://files.pythonhosted.org/packages/b9/29/82545bfa0b65f8ace22e154f0dd26c3543101523ea86df668995abafcf85/qypi-0.4.0-py3-none-any.whl#sha256=f264f87c34b722afdfde2349999697418e404183c80e5180032b3d61202e9a4d", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.4.0.tar.gz', project='qypi', version='0.4.0', package_type='sdist', url="https://files.pythonhosted.org/packages/4a/77/c4cd613177fcc894408ba731abc9d3392dcdd4cc9d6be8f1899c942686dd/qypi-0.4.0.tar.gz#sha256=884d59dd776e091b610e967729a57dd29fe095125210ef29ec4f874245baf7b6", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.4.1-py3-none-any.whl', project='qypi', version='0.4.1', package_type='wheel', url="https://files.pythonhosted.org/packages/b3/43/ac36d6a00a86ba7dc9c61f3dd448c233aae2c014c6cae111908ca5644112/qypi-0.4.1-py3-none-any.whl#sha256=488a65d6bd8c10f211e098d2d6e4a66df003be12f028b8f6f858ac2863579eb1", requires_python="~=3.4", has_sig=False, yanked=None, ), DistributionPackage( filename='qypi-0.4.1.tar.gz', project='qypi', version='0.4.1', package_type='sdist', url="https://files.pythonhosted.org/packages/70/7f/8da79c0732787236a9a3a7787f2abfaf996f96f6ebccfdb533646f70640e/qypi-0.4.1.tar.gz#sha256=5f69adbf25e8369d25c31e41912ed0e6be429beb62faf4fc424aa667c561f657", requires_python="~=3.4", has_sig=False, yanked=None, ), ]