diff --git a/requirements/main.in b/requirements/main.in index ff62e5248969..9c40b32d6cbc 100644 --- a/requirements/main.in +++ b/requirements/main.in @@ -25,7 +25,7 @@ lxml mistune msgpack natsort -packaging>=15.2 +packaging>=20.9 paginate>=0.5.2 paginate_sqlalchemy passlib>=1.6.4 diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 235c56700087..42643c9f0332 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -20,7 +20,6 @@ from cgi import FieldStorage from unittest import mock -import pkg_resources import pretend import pytest import requests @@ -669,6 +668,45 @@ def test_is_duplicate_true(self, pyramid_config, db_request): assert legacy._is_duplicate_file(db_request.db, filename, hashes) + @pytest.mark.parametrize("file_name_prefix", ["foo_bar", "foo.bar"]) + @pytest.mark.parametrize("project_name", ["foo_bar", "foo.bar"]) + def test_is_duplicate_true_non_normalized_filename( + self, pyramid_config, db_request, file_name_prefix, project_name + ): + pyramid_config.testing_securitypolicy(userid=1) + + user = UserFactory.create() + EmailFactory.create(user=user) + project = ProjectFactory.create(name=project_name) + release = ReleaseFactory.create(project=project, version="1.0") + RoleFactory.create(user=user, project=project) + + filename = "{}-{}.tar.gz".format(project_name, release.version) + file_content = io.BytesIO(_TAR_GZ_PKG_TESTDATA) + file_value = file_content.getvalue() + + hashes = { + "sha256": hashlib.sha256(file_value).hexdigest(), + "md5": hashlib.md5(file_value).hexdigest(), + "blake2_256": hashlib.blake2b(file_value, digest_size=256 // 8).hexdigest(), + } + db_request.db.add( + File( + release=release, + filename=filename, + md5_digest=hashes["md5"], + sha256_digest=hashes["sha256"], + blake2_256_digest=hashes["blake2_256"], + path="source/{name[0]}/{name}/{filename}".format( + name=project.name, filename=filename + ), + ) + ) + + duplicate_filename = "{}-{}.tar.gz".format(file_name_prefix, release.version) + + assert legacy._is_duplicate_file(db_request.db, duplicate_filename, hashes) + def test_is_duplicate_none(self, pyramid_config, db_request): pyramid_config.testing_securitypolicy(userid=1) @@ -2412,7 +2450,7 @@ def test_upload_fails_with_diff_filename_same_blake2( "filetype": "sdist", "md5_digest": hashlib.md5(file_content.getvalue()).hexdigest(), "content": pretend.stub( - filename="{}-fake.tar.gz".format(project.name), + filename="{}-0.1.tar.gz".format(project.name), file=file_content, type="application/tar", ), @@ -2446,7 +2484,14 @@ def test_upload_fails_with_diff_filename_same_blake2( "400 File already exists. See /the/help/url/ for more information." ) - def test_upload_fails_with_wrong_filename(self, pyramid_config, db_request): + @pytest.mark.parametrize( + "filename", + [ + "nope-{version}.tar.gz", + "nope-{version}-py3-none-any.whl", + ], + ) + def test_upload_fails_with_wrong_prefix(self, pyramid_config, db_request, filename): pyramid_config.testing_securitypolicy(userid=1) user = UserFactory.create() @@ -2456,7 +2501,7 @@ def test_upload_fails_with_wrong_filename(self, pyramid_config, db_request): release = ReleaseFactory.create(project=project, version="1.0") RoleFactory.create(user=user, project=project) - filename = "nope-{}.tar.gz".format(release.version) + filename = filename.format(version=release.version) db_request.POST = MultiDict( { @@ -2480,11 +2525,108 @@ def test_upload_fails_with_wrong_filename(self, pyramid_config, db_request): assert resp.status_code == 400 assert resp.status == ( - "400 Start filename for {!r} with {!r}.".format( - project.name, pkg_resources.safe_name(project.name).lower() + "400 Invalid filename: Start filename for project {!r} with {!r}.".format( + project.name, project.normalized_name.replace("-", "_") ) ) + def test_wheel_upload_passes_with_period_in_prefix( + self, pyramid_config, db_request, metrics, monkeypatch + ): + """ + This tests existing behavior which allows periods in the project name, + which violates the wheel spec at [1], which requires that the filename + start with the PEP 503 normalization of the project name, followed by + replacing - with _. + + [1] https://packaging.python.org/specifications/binary-distribution-format/ + """ + pyramid_config.testing_securitypolicy(userid=1) + + user = UserFactory.create() + db_request.user = user + EmailFactory.create(user=user) + project = ProjectFactory.create(name="some.project") + release = ReleaseFactory.create(project=project, version="1.0") + RoleFactory.create(user=user, project=project) + + filename = "{project_name}-{version}-py3-none-any.whl".format( + version=release.version, project_name=project.name + ) + monkeypatch.setattr(legacy, "_is_valid_dist_file", lambda *a, **kw: True) + + db_request.find_service = pretend.call_recorder( + lambda svc, name=None, context=None: { + IFileStorage: pretend.stub(store=lambda *a, **kw: None), + IMetricsService: metrics, + }.get(svc) + ) + + db_request.user_agent = "warehouse-tests/6.6.6" + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "version": release.version, + "filetype": "bdist_wheel", + "pyversion": "py3", + "md5_digest": _TAR_GZ_PKG_MD5, + "content": pretend.stub( + filename=filename, + file=io.BytesIO(_TAR_GZ_PKG_TESTDATA), + type="application/tar", + ), + } + ) + + resp = legacy.file_upload(db_request) + + assert resp.status_code == 200 + + @pytest.mark.parametrize( + "filename", + [ + "nope-notaversion.tar.gz", + "nope-notaversion-py3-none-any.whl", + ], + ) + def test_upload_fails_with_invalid_version( + self, pyramid_config, db_request, filename + ): + pyramid_config.testing_securitypolicy(userid=1) + + user = UserFactory.create() + db_request.user = user + EmailFactory.create(user=user) + project = ProjectFactory.create() + release = ReleaseFactory.create(project=project, version="1.0") + RoleFactory.create(user=user, project=project) + + filename = filename.format(version=release.version) + + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "version": release.version, + "filetype": "sdist", + "md5_digest": "nope!", + "content": pretend.stub( + filename=filename, + file=io.BytesIO(b"a" * (legacy.MAX_FILESIZE + 1)), + type="application/tar", + ), + } + ) + + with pytest.raises(HTTPBadRequest) as excinfo: + legacy.file_upload(db_request) + + resp = excinfo.value + + assert resp.status_code == 400 + assert resp.status == "400 Invalid filename: Invalid version: 'notaversion'." + def test_upload_fails_with_invalid_extension(self, pyramid_config, db_request): pyramid_config.testing_securitypolicy(userid=1) diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py index 3187bce7ccec..49235986f312 100644 --- a/warehouse/forklift/legacy.py +++ b/warehouse/forklift/legacy.py @@ -26,7 +26,6 @@ import packaging.specifiers import packaging.utils import packaging.version -import pkg_resources import requests import stdlib_list import wtforms @@ -737,6 +736,15 @@ def _is_valid_dist_file(filename, filetype): return True +def _parse_filename(filename): + if filename.endswith(".whl"): + parse_func = packaging.utils.parse_wheel_filename + else: + parse_func = packaging.utils.parse_sdist_filename + + return parse_func(filename) + + def _is_duplicate_file(db_session, filename, hashes): """ Check to see if file already exists, and if it's content matches. @@ -760,7 +768,8 @@ def _is_duplicate_file(db_session, filename, hashes): if file_ is not None: return ( - file_.filename == filename + # This has the effect of canonicalizing the project name and version + _parse_filename(file_.filename) == _parse_filename(filename) and file_.sha256_digest == hashes["sha256"] and file_.md5_digest == hashes["md5"] and file_.blake2_256_digest == hashes["blake2_256"] @@ -1229,13 +1238,31 @@ def file_upload(request): "for more information.", ) - # Make sure that our filename matches the project that it is being uploaded - # to. - prefix = pkg_resources.safe_name(project.name).lower() - if not pkg_resources.safe_name(filename).lower().startswith(prefix): + try: + parsed_filename_parts = _parse_filename(filename) + except ( + packaging.utils.InvalidSdistFilename, + packaging.utils.InvalidWheelFilename, + packaging.version.InvalidVersion, + ) as e: + raise _exc_with_message(HTTPBadRequest, f"Invalid filename: {e}.") + + # Make sure that the normalized version of the project name in the filename + # matches the normalized project name that it is being uploaded to. + # + # NB: This allows periods the project name, which violates the wheel spec + # at [1], which requires that the filename start with the PEP 503 + # normalization of the project name, followed by replacing - with _. + # + # [1] https://packaging.python.org/specifications/binary-distribution-format/ + if not parsed_filename_parts[0] == project.normalized_name: + escaped_normalized_project_name = project.normalized_name.replace("-", "_") + help_url = request.help_url(_anchor="invalid-file-name") raise _exc_with_message( HTTPBadRequest, - "Start filename for {!r} with {!r}.".format(project.name, prefix), + f"Invalid filename: Start filename for project {project.name!r} " + f"with {escaped_normalized_project_name!r}. " + f"See {help_url} for more information", ) # Check the content type of what is being uploaded diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 45cbb56c6e03..f0f9ee8d930b 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -376,68 +376,68 @@ msgstr "" #: warehouse/templates/packaging/detail.html:350 #: warehouse/templates/pages/classifiers.html:25 #: warehouse/templates/pages/help.html:20 -#: warehouse/templates/pages/help.html:208 -#: warehouse/templates/pages/help.html:215 -#: warehouse/templates/pages/help.html:229 -#: warehouse/templates/pages/help.html:245 -#: warehouse/templates/pages/help.html:249 -#: warehouse/templates/pages/help.html:306 -#: warehouse/templates/pages/help.html:333 -#: warehouse/templates/pages/help.html:338 -#: warehouse/templates/pages/help.html:343 +#: warehouse/templates/pages/help.html:210 +#: warehouse/templates/pages/help.html:217 +#: warehouse/templates/pages/help.html:231 +#: warehouse/templates/pages/help.html:247 +#: warehouse/templates/pages/help.html:251 +#: warehouse/templates/pages/help.html:308 +#: warehouse/templates/pages/help.html:335 +#: warehouse/templates/pages/help.html:340 #: warehouse/templates/pages/help.html:345 -#: warehouse/templates/pages/help.html:350 -#: warehouse/templates/pages/help.html:351 +#: warehouse/templates/pages/help.html:347 #: warehouse/templates/pages/help.html:352 -#: warehouse/templates/pages/help.html:356 -#: warehouse/templates/pages/help.html:389 +#: warehouse/templates/pages/help.html:353 +#: warehouse/templates/pages/help.html:354 +#: warehouse/templates/pages/help.html:358 #: warehouse/templates/pages/help.html:391 -#: warehouse/templates/pages/help.html:394 -#: warehouse/templates/pages/help.html:430 -#: warehouse/templates/pages/help.html:435 -#: warehouse/templates/pages/help.html:441 -#: warehouse/templates/pages/help.html:499 -#: warehouse/templates/pages/help.html:519 -#: warehouse/templates/pages/help.html:525 -#: warehouse/templates/pages/help.html:528 +#: warehouse/templates/pages/help.html:393 +#: warehouse/templates/pages/help.html:396 +#: warehouse/templates/pages/help.html:432 +#: warehouse/templates/pages/help.html:437 +#: warehouse/templates/pages/help.html:443 +#: warehouse/templates/pages/help.html:501 +#: warehouse/templates/pages/help.html:521 +#: warehouse/templates/pages/help.html:527 #: warehouse/templates/pages/help.html:530 -#: warehouse/templates/pages/help.html:539 -#: warehouse/templates/pages/help.html:551 -#: warehouse/templates/pages/help.html:558 -#: warehouse/templates/pages/help.html:570 -#: warehouse/templates/pages/help.html:571 -#: warehouse/templates/pages/help.html:576 -#: warehouse/templates/pages/help.html:601 -#: warehouse/templates/pages/help.html:614 -#: warehouse/templates/pages/help.html:619 -#: warehouse/templates/pages/help.html:631 -#: warehouse/templates/pages/help.html:652 -#: warehouse/templates/pages/help.html:675 -#: warehouse/templates/pages/help.html:682 -#: warehouse/templates/pages/help.html:694 -#: warehouse/templates/pages/help.html:705 -#: warehouse/templates/pages/help.html:710 -#: warehouse/templates/pages/help.html:718 -#: warehouse/templates/pages/help.html:729 -#: warehouse/templates/pages/help.html:746 -#: warehouse/templates/pages/help.html:753 -#: warehouse/templates/pages/help.html:761 -#: warehouse/templates/pages/help.html:777 -#: warehouse/templates/pages/help.html:782 -#: warehouse/templates/pages/help.html:787 +#: warehouse/templates/pages/help.html:532 +#: warehouse/templates/pages/help.html:541 +#: warehouse/templates/pages/help.html:553 +#: warehouse/templates/pages/help.html:560 +#: warehouse/templates/pages/help.html:572 +#: warehouse/templates/pages/help.html:573 +#: warehouse/templates/pages/help.html:578 +#: warehouse/templates/pages/help.html:603 +#: warehouse/templates/pages/help.html:616 +#: warehouse/templates/pages/help.html:621 +#: warehouse/templates/pages/help.html:633 +#: warehouse/templates/pages/help.html:654 +#: warehouse/templates/pages/help.html:677 +#: warehouse/templates/pages/help.html:684 +#: warehouse/templates/pages/help.html:696 +#: warehouse/templates/pages/help.html:707 +#: warehouse/templates/pages/help.html:712 +#: warehouse/templates/pages/help.html:720 +#: warehouse/templates/pages/help.html:731 +#: warehouse/templates/pages/help.html:748 +#: warehouse/templates/pages/help.html:768 +#: warehouse/templates/pages/help.html:776 +#: warehouse/templates/pages/help.html:792 #: warehouse/templates/pages/help.html:797 -#: warehouse/templates/pages/help.html:806 -#: warehouse/templates/pages/help.html:820 -#: warehouse/templates/pages/help.html:828 -#: warehouse/templates/pages/help.html:836 -#: warehouse/templates/pages/help.html:844 -#: warehouse/templates/pages/help.html:854 +#: warehouse/templates/pages/help.html:802 +#: warehouse/templates/pages/help.html:812 +#: warehouse/templates/pages/help.html:821 +#: warehouse/templates/pages/help.html:835 +#: warehouse/templates/pages/help.html:843 +#: warehouse/templates/pages/help.html:851 +#: warehouse/templates/pages/help.html:859 #: warehouse/templates/pages/help.html:869 #: warehouse/templates/pages/help.html:884 -#: warehouse/templates/pages/help.html:885 -#: warehouse/templates/pages/help.html:886 -#: warehouse/templates/pages/help.html:887 -#: warehouse/templates/pages/help.html:892 +#: warehouse/templates/pages/help.html:899 +#: warehouse/templates/pages/help.html:900 +#: warehouse/templates/pages/help.html:901 +#: warehouse/templates/pages/help.html:902 +#: warehouse/templates/pages/help.html:907 #: warehouse/templates/pages/security.html:36 #: warehouse/templates/pages/sponsors.html:33 #: warehouse/templates/pages/sponsors.html:37 @@ -522,7 +522,7 @@ msgstr "" #: warehouse/templates/base.html:41 warehouse/templates/base.html:55 #: warehouse/templates/base.html:256 #: warehouse/templates/includes/current-user-indicator.html:55 -#: warehouse/templates/pages/help.html:105 +#: warehouse/templates/pages/help.html:106 #: warehouse/templates/pages/sitemap.html:27 msgid "Help" msgstr "" @@ -1881,7 +1881,7 @@ msgstr "" #: warehouse/templates/includes/packaging/project-data.html:84 #: warehouse/templates/includes/packaging/project-data.html:86 -#: warehouse/templates/pages/help.html:562 +#: warehouse/templates/pages/help.html:564 msgid "Maintainer:" msgstr "" @@ -3437,7 +3437,7 @@ msgid "" msgstr "" #: warehouse/templates/manage/roles.html:39 -#: warehouse/templates/pages/help.html:561 +#: warehouse/templates/pages/help.html:563 msgid "There are two possible roles for collaborators:" msgstr "" @@ -4348,97 +4348,101 @@ msgid "" msgstr "" #: warehouse/templates/pages/help.html:89 -msgid "Why isn't my desired project name available?" +msgid "Why am I getting an \"Invalid filename\" error?" msgstr "" #: warehouse/templates/pages/help.html:90 -msgid "How do I claim an abandoned or previously registered project name?" +msgid "Why isn't my desired project name available?" msgstr "" #: warehouse/templates/pages/help.html:91 -msgid "What collaborator roles are available for a project on PyPI?" +msgid "How do I claim an abandoned or previously registered project name?" msgstr "" #: warehouse/templates/pages/help.html:92 -msgid "How do I become an owner/maintainer of a project on PyPI?" +msgid "What collaborator roles are available for a project on PyPI?" msgstr "" #: warehouse/templates/pages/help.html:93 -msgid "How can I upload a project description in a different format?" +msgid "How do I become an owner/maintainer of a project on PyPI?" msgstr "" #: warehouse/templates/pages/help.html:94 -msgid "How do I request a new trove classifier?" +msgid "How can I upload a project description in a different format?" msgstr "" #: warehouse/templates/pages/help.html:95 +msgid "How do I request a new trove classifier?" +msgstr "" + +#: warehouse/templates/pages/help.html:96 msgid "Where can I report a bug or provide feedback about PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:97 +#: warehouse/templates/pages/help.html:98 msgid "Who maintains PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:98 +#: warehouse/templates/pages/help.html:99 msgid "What powers PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:99 +#: warehouse/templates/pages/help.html:100 msgid "Can I depend on PyPI being available?" msgstr "" -#: warehouse/templates/pages/help.html:100 +#: warehouse/templates/pages/help.html:101 msgid "How can I contribute to PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:101 +#: warehouse/templates/pages/help.html:102 msgid "How do I keep up with upcoming changes to PyPI?" msgstr "" -#: warehouse/templates/pages/help.html:102 +#: warehouse/templates/pages/help.html:103 msgid "" "What does the \"beta feature\" badge mean? What are Warehouse's current " "beta features?" msgstr "" -#: warehouse/templates/pages/help.html:103 +#: warehouse/templates/pages/help.html:104 msgid "How do I pronounce \"PyPI\"?" msgstr "" -#: warehouse/templates/pages/help.html:110 +#: warehouse/templates/pages/help.html:111 msgid "Common questions" msgstr "" -#: warehouse/templates/pages/help.html:113 -#: warehouse/templates/pages/help.html:196 +#: warehouse/templates/pages/help.html:114 +#: warehouse/templates/pages/help.html:198 msgid "Basics" msgstr "" -#: warehouse/templates/pages/help.html:124 +#: warehouse/templates/pages/help.html:125 msgid "My Account" msgstr "" -#: warehouse/templates/pages/help.html:141 -#: warehouse/templates/pages/help.html:516 +#: warehouse/templates/pages/help.html:142 +#: warehouse/templates/pages/help.html:518 msgid "Integrating" msgstr "" -#: warehouse/templates/pages/help.html:151 -#: warehouse/templates/pages/help.html:543 +#: warehouse/templates/pages/help.html:152 +#: warehouse/templates/pages/help.html:545 msgid "Administration of projects on PyPI" msgstr "" -#: warehouse/templates/pages/help.html:166 -#: warehouse/templates/pages/help.html:627 +#: warehouse/templates/pages/help.html:167 +#: warehouse/templates/pages/help.html:629 msgid "Troubleshooting" msgstr "" -#: warehouse/templates/pages/help.html:183 -#: warehouse/templates/pages/help.html:773 +#: warehouse/templates/pages/help.html:185 +#: warehouse/templates/pages/help.html:788 msgid "About" msgstr "" -#: warehouse/templates/pages/help.html:199 +#: warehouse/templates/pages/help.html:201 #, python-format msgid "" "\n" @@ -4462,7 +4466,7 @@ msgid "" " " msgstr "" -#: warehouse/templates/pages/help.html:208 +#: warehouse/templates/pages/help.html:210 #, python-format msgid "" "To learn how to install a file from PyPI, visit the Python Packaging User Guide." msgstr "" -#: warehouse/templates/pages/help.html:215 +#: warehouse/templates/pages/help.html:217 #, python-format msgid "" "For full instructions on configuring, packaging and distributing your " @@ -4482,7 +4486,7 @@ msgid "" "target=\"_blank\" rel=\"noopener\">Python Packaging User Guide." msgstr "" -#: warehouse/templates/pages/help.html:222 +#: warehouse/templates/pages/help.html:224 #, python-format msgid "" "Classifiers are used to categorize projects on PyPI. See PyPI itself has not suffered a breach. This is a protective measure " @@ -4570,7 +4574,7 @@ msgid "" "href=\"%(reset_pwd_href)s\">reset your password.
" msgstr "" -#: warehouse/templates/pages/help.html:284 +#: warehouse/templates/pages/help.html:286 #, python-format msgid "" "All PyPI user events are stored under security history in account " @@ -4580,7 +4584,7 @@ msgid "" "href=\"mailto:%(admin_email)s\">%(admin_email)s
" msgstr "" -#: warehouse/templates/pages/help.html:296 +#: warehouse/templates/pages/help.html:298 msgid "" "A PyPI API token linked to your account was posted on a public " "website. It was automatically revoked, but before regenerating a new one," @@ -4589,7 +4593,7 @@ msgid "" "applies too.
" msgstr "" -#: warehouse/templates/pages/help.html:306 +#: warehouse/templates/pages/help.html:308 #, python-format msgid "" "Two factor authentication (2FA) makes your account more secure by " @@ -4608,7 +4612,7 @@ msgid "" "rel=\"noopener\">discuss.python.org.
" msgstr "" -#: warehouse/templates/pages/help.html:333 +#: warehouse/templates/pages/help.html:335 #, python-format msgid "" "PyPI users can set up two-factor authentication using any authentication " @@ -4617,21 +4621,21 @@ msgid "" "password\">TOTP standard." msgstr "" -#: warehouse/templates/pages/help.html:334 +#: warehouse/templates/pages/help.html:336 msgid "" "TOTP authentication " "applications generate a regularly changing authentication code to use " "when logging into your account." msgstr "" -#: warehouse/templates/pages/help.html:335 +#: warehouse/templates/pages/help.html:337 msgid "" "Because TOTP is an " "open standard, there are many applications that are compatible with your " "PyPI account. Popular applications include:" msgstr "" -#: warehouse/templates/pages/help.html:338 +#: warehouse/templates/pages/help.html:340 #, python-format msgid "" "Google Authenticator for iOS" msgstr "" -#: warehouse/templates/pages/help.html:341 #: warehouse/templates/pages/help.html:343 -#: warehouse/templates/pages/help.html:348 +#: warehouse/templates/pages/help.html:345 #: warehouse/templates/pages/help.html:350 +#: warehouse/templates/pages/help.html:352 msgid "(proprietary)" msgstr "" -#: warehouse/templates/pages/help.html:345 +#: warehouse/templates/pages/help.html:347 #, python-format msgid "" "Duo Mobile for iOS" msgstr "" -#: warehouse/templates/pages/help.html:351 -#: warehouse/templates/pages/help.html:352 +#: warehouse/templates/pages/help.html:353 +#: warehouse/templates/pages/help.html:354 msgid "(open source)" msgstr "" -#: warehouse/templates/pages/help.html:356 +#: warehouse/templates/pages/help.html:358 #, python-format msgid "" "Some password managers (e.g. 2FA with an " "authentication application:" msgstr "" -#: warehouse/templates/pages/help.html:366 +#: warehouse/templates/pages/help.html:368 msgid "" "Open an authentication (TOTP) application" msgstr "" -#: warehouse/templates/pages/help.html:367 +#: warehouse/templates/pages/help.html:369 msgid "" "Log in to your PyPI account, go to your account settings, and choose " "\"Add 2FA with " "authentication application\"" msgstr "" -#: warehouse/templates/pages/help.html:368 +#: warehouse/templates/pages/help.html:370 msgid "" "PyPI will generate a secret key, specific to your account. This is " "displayed as a QR code, and as a text code." msgstr "" -#: warehouse/templates/pages/help.html:369 +#: warehouse/templates/pages/help.html:371 msgid "" "Scan the QR code with your authentication application, or type it in " "manually. The method of input will depend on the application you have " "chosen." msgstr "" -#: warehouse/templates/pages/help.html:370 +#: warehouse/templates/pages/help.html:372 msgid "" "Your application will generate an authentication code - use this to " "verify your set up on PyPI" msgstr "" -#: warehouse/templates/pages/help.html:373 +#: warehouse/templates/pages/help.html:375 msgid "" "The PyPI server and your application now share your PyPI secret key, " "allowing your application to generate valid authentication codes for your" " PyPI account." msgstr "" -#: warehouse/templates/pages/help.html:375 -#: warehouse/templates/pages/help.html:417 +#: warehouse/templates/pages/help.html:377 +#: warehouse/templates/pages/help.html:419 msgid "Next time you log in to PyPI you'll need to:" msgstr "" -#: warehouse/templates/pages/help.html:377 -#: warehouse/templates/pages/help.html:469 +#: warehouse/templates/pages/help.html:379 +#: warehouse/templates/pages/help.html:471 msgid "Provide your username and password, as normal" msgstr "" -#: warehouse/templates/pages/help.html:378 +#: warehouse/templates/pages/help.html:380 msgid "Open your authentication application to generate an authentication code" msgstr "" -#: warehouse/templates/pages/help.html:379 +#: warehouse/templates/pages/help.html:381 msgid "Use this code to finish logging into PyPI" msgstr "" -#: warehouse/templates/pages/help.html:385 +#: warehouse/templates/pages/help.html:387 msgid "" "A security device is a USB key or other " "device that generates a one-time password and sends that password to " @@ -4741,11 +4745,11 @@ msgid "" "user." msgstr "" -#: warehouse/templates/pages/help.html:387 +#: warehouse/templates/pages/help.html:389 msgid "To set up two factor authentication with a USB key, you'll need:" msgstr "" -#: warehouse/templates/pages/help.html:389 +#: warehouse/templates/pages/help.html:391 #, python-format msgid "" "To use a :" msgstr "" -#: warehouse/templates/pages/help.html:394 +#: warehouse/templates/pages/help.html:396 #, python-format msgid "" "Popular keys include Thetis." msgstr "" -#: warehouse/templates/pages/help.html:401 +#: warehouse/templates/pages/help.html:403 msgid "" "Note that some older Yubico USB keys do not follow the FIDO " "specification, and will therefore not work with PyPI" msgstr "" -#: warehouse/templates/pages/help.html:406 +#: warehouse/templates/pages/help.html:408 msgid "Follow these steps:" msgstr "" -#: warehouse/templates/pages/help.html:408 +#: warehouse/templates/pages/help.html:410 msgid "" "\n" "__token__
"
msgstr ""
-#: warehouse/templates/pages/help.html:495
+#: warehouse/templates/pages/help.html:497
msgid ""
"Set your password to the token value, including the pypi-
"
"prefix"
msgstr ""
-#: warehouse/templates/pages/help.html:499
+#: warehouse/templates/pages/help.html:501
#, python-format
msgid ""
"Where you edit or add these values will depend on your individual use "
@@ -4963,14 +4967,14 @@ msgid ""
"rel=\"noopener\">.travis.yml
if you are using Travis)."
msgstr ""
-#: warehouse/templates/pages/help.html:503
+#: warehouse/templates/pages/help.html:505
msgid ""
"Advanced users may wish to inspect their token by decoding it with "
"base64, and checking the output against the unique identifier displayed "
"on PyPI."
msgstr ""
-#: warehouse/templates/pages/help.html:507
+#: warehouse/templates/pages/help.html:509
msgid ""
"\n"
" PyPI asks you to confirm your password before you want to "
@@ -4984,15 +4988,15 @@ msgid ""
" "
msgstr ""
-#: warehouse/templates/pages/help.html:519
+#: warehouse/templates/pages/help.html:521
msgid "Yes, including RSS feeds of new packages and new releases."
msgstr ""
-#: warehouse/templates/pages/help.html:519
+#: warehouse/templates/pages/help.html:521
msgid "See the API reference."
msgstr ""
-#: warehouse/templates/pages/help.html:522
+#: warehouse/templates/pages/help.html:524
#, python-format
msgid ""
"If you need to run your own mirror of PyPI, the GitHub apps."
msgstr ""
-#: warehouse/templates/pages/help.html:528
+#: warehouse/templates/pages/help.html:530
#, python-format
msgid ""
"You can analyze PyPI project/package metadata and via our public dataset on Google BigQuery."
msgstr ""
-#: warehouse/templates/pages/help.html:530
+#: warehouse/templates/pages/help.html:532
#, python-format
msgid ""
"other relevant factors."
msgstr ""
-#: warehouse/templates/pages/help.html:539
+#: warehouse/templates/pages/help.html:541
#, python-format
msgid ""
"For recent statistics on uptime and performance, see ."
msgstr ""
-#: warehouse/templates/pages/help.html:546
+#: warehouse/templates/pages/help.html:548
#, python-format
msgid ""
"PyPI does not support publishing private packages. If you need to publish"
@@ -5051,7 +5055,7 @@ msgid ""
"run your own deployment of the devpi project."
msgstr ""
-#: warehouse/templates/pages/help.html:549
+#: warehouse/templates/pages/help.html:551
msgid ""
"Your publishing tool may return an error that your new project can't be "
"created with your desired name, despite no evidence of a project or "
@@ -5059,7 +5063,7 @@ msgid ""
"reasons this may occur:"
msgstr ""
-#: warehouse/templates/pages/help.html:551
+#: warehouse/templates/pages/help.html:553
#, python-format
msgid ""
"The project name conflicts with a module from any major version from 2.5 to present."
msgstr ""
-#: warehouse/templates/pages/help.html:552
+#: warehouse/templates/pages/help.html:554
msgid ""
"The project name is too similar to an existing project and may be "
"confusable."
msgstr ""
-#: warehouse/templates/pages/help.html:553
+#: warehouse/templates/pages/help.html:555
#, python-format
msgid ""
"The project name has been explicitly prohibited by the PyPI "
@@ -5082,18 +5086,18 @@ msgid ""
"with a malicious package."
msgstr ""
-#: warehouse/templates/pages/help.html:554
+#: warehouse/templates/pages/help.html:556
msgid ""
"The project name has been registered by another user, but no releases "
"have been created."
msgstr ""
-#: warehouse/templates/pages/help.html:554
+#: warehouse/templates/pages/help.html:556
#, python-format
msgid "See %(anchor_text)s"
msgstr ""
-#: warehouse/templates/pages/help.html:558
+#: warehouse/templates/pages/help.html:560
#, python-format
msgid ""
"Follow the PEP 541."
msgstr ""
-#: warehouse/templates/pages/help.html:562
+#: warehouse/templates/pages/help.html:564
msgid ""
"Can upload releases for a package. Cannot add collaborators. Cannot "
"delete files, releases, or the project."
msgstr ""
-#: warehouse/templates/pages/help.html:563
+#: warehouse/templates/pages/help.html:565
msgid "Owner:"
msgstr ""
-#: warehouse/templates/pages/help.html:563
+#: warehouse/templates/pages/help.html:565
msgid ""
"Can upload releases. Can add other collaborators. Can delete files, "
"releases, or the entire project."
msgstr ""
-#: warehouse/templates/pages/help.html:566
+#: warehouse/templates/pages/help.html:568
msgid ""
"Only the current owners of a project have the ability to add new owners "
"or maintainers. If you need to request ownership, you should contact the "
@@ -5126,12 +5130,12 @@ msgid ""
"project page."
msgstr ""
-#: warehouse/templates/pages/help.html:567
+#: warehouse/templates/pages/help.html:569
#, python-format
msgid "If the owner is unresponsive, see %(anchor_text)s"
msgstr ""
-#: warehouse/templates/pages/help.html:570
+#: warehouse/templates/pages/help.html:572
#, python-format
msgid ""
"By default, an upload's description will render with file an issue and tell us:"
msgstr ""
-#: warehouse/templates/pages/help.html:585
-#: warehouse/templates/pages/help.html:606
+#: warehouse/templates/pages/help.html:587
+#: warehouse/templates/pages/help.html:608
msgid "A link to your project on PyPI (or Test PyPI)"
msgstr ""
-#: warehouse/templates/pages/help.html:586
+#: warehouse/templates/pages/help.html:588
msgid "The size of your release, in megabytes"
msgstr ""
-#: warehouse/templates/pages/help.html:587
+#: warehouse/templates/pages/help.html:589
msgid "Which index/indexes you need the increase for (PyPI, Test PyPI, or both)"
msgstr ""
-#: warehouse/templates/pages/help.html:588
-#: warehouse/templates/pages/help.html:608
+#: warehouse/templates/pages/help.html:590
+#: warehouse/templates/pages/help.html:610
msgid ""
"A brief description of your project, including the reason for the "
"additional size."
msgstr ""
-#: warehouse/templates/pages/help.html:594
+#: warehouse/templates/pages/help.html:596
msgid ""
"If you can't upload your project's release to PyPI because you're hitting"
" the project size limit, first remove any unnecessary releases or "
"individual files to lower your overall project size."
msgstr ""
-#: warehouse/templates/pages/help.html:601
+#: warehouse/templates/pages/help.html:603
#, python-format
msgid ""
"If that is not possible, we can sometimes increase your limit. File an issue and tell us:"
msgstr ""
-#: warehouse/templates/pages/help.html:607
+#: warehouse/templates/pages/help.html:609
msgid "The total size of your project, in gigabytes"
msgstr ""
-#: warehouse/templates/pages/help.html:614
+#: warehouse/templates/pages/help.html:616
#, python-format
msgid ""
"PyPI receives reports on vulnerabilities in the packages hosted on it "
@@ -5213,7 +5217,7 @@ msgid ""
"Advisory Database."
msgstr ""
-#: warehouse/templates/pages/help.html:619
+#: warehouse/templates/pages/help.html:621
#, python-format
msgid ""
"If you believe vulnerability data for your project is invalid or "
@@ -5221,7 +5225,7 @@ msgid ""
"target=\"_blank\" rel=\"noopener\">file an issue with details."
msgstr ""
-#: warehouse/templates/pages/help.html:631
+#: warehouse/templates/pages/help.html:633
#, python-format
msgid ""
"PyPI will reject uploads if the package description fails to render. You "
@@ -5229,41 +5233,41 @@ msgid ""
"command to locally check a description for validity."
msgstr ""
-#: warehouse/templates/pages/help.html:637
+#: warehouse/templates/pages/help.html:639
msgid ""
"If you've forgotten your PyPI password but you remember your email "
"address or username, follow these steps to reset your password:"
msgstr ""
-#: warehouse/templates/pages/help.html:639
+#: warehouse/templates/pages/help.html:641
#, python-format
msgid "Go to reset your password."
msgstr ""
-#: warehouse/templates/pages/help.html:640
+#: warehouse/templates/pages/help.html:642
msgid "Enter the email address or username you used for PyPI and submit the form."
msgstr ""
-#: warehouse/templates/pages/help.html:641
+#: warehouse/templates/pages/help.html:643
msgid "You'll receive an email with a password reset link."
msgstr ""
-#: warehouse/templates/pages/help.html:646
+#: warehouse/templates/pages/help.html:648
msgid "If you've lost access to your PyPI account due to:"
msgstr ""
-#: warehouse/templates/pages/help.html:648
+#: warehouse/templates/pages/help.html:650
msgid "Lost access to the email address associated with your account"
msgstr ""
-#: warehouse/templates/pages/help.html:649
+#: warehouse/templates/pages/help.html:651
msgid ""
"Lost two factor authentication application, device, and recovery "
"codes"
msgstr ""
-#: warehouse/templates/pages/help.html:652
+#: warehouse/templates/pages/help.html:654
#, python-format
msgid ""
"You can proceed to API Token for uploads:"
msgstr ""
-#: warehouse/templates/pages/help.html:666
+#: warehouse/templates/pages/help.html:668
msgid "Ensure that your API Token is valid and has not been revoked."
msgstr ""
-#: warehouse/templates/pages/help.html:667
+#: warehouse/templates/pages/help.html:669
msgid ""
"Ensure that your API Token is properly "
"formatted and does not contain any trailing characters such as "
"newlines."
msgstr ""
-#: warehouse/templates/pages/help.html:669
+#: warehouse/templates/pages/help.html:671
msgid ""
"In both cases, remember that PyPI and TestPyPI each require you to create"
" an account, so your credentials may be different."
msgstr ""
-#: warehouse/templates/pages/help.html:671
+#: warehouse/templates/pages/help.html:673
msgid ""
"\n"
" If you're using Windows and trying to paste your password or "
@@ -5317,7 +5321,7 @@ msgid ""
" "
msgstr ""
-#: warehouse/templates/pages/help.html:675
+#: warehouse/templates/pages/help.html:677
#, python-format
msgid ""
"This is a Learn why on the PSF blog."
msgstr ""
-#: warehouse/templates/pages/help.html:689
+#: warehouse/templates/pages/help.html:691
#, python-format
msgid ""
"If you are having trouble with %(command)s
and get a "
@@ -5346,7 +5350,7 @@ msgid ""
"information:"
msgstr ""
-#: warehouse/templates/pages/help.html:691
+#: warehouse/templates/pages/help.html:693
msgid ""
"If you see an error like There was a problem confirming the ssl "
"certificate
or tlsv1 alert protocol version
or "
@@ -5354,7 +5358,7 @@ msgid ""
"PyPI with a newer TLS support library."
msgstr ""
-#: warehouse/templates/pages/help.html:692
+#: warehouse/templates/pages/help.html:694
msgid ""
"The specific steps you need to take will depend on your operating system "
"version, where your installation of Python originated (python.org, your "
@@ -5362,7 +5366,7 @@ msgid ""
" Python, setuptools
, and pip
."
msgstr ""
-#: warehouse/templates/pages/help.html:694
+#: warehouse/templates/pages/help.html:696
#, python-format
msgid ""
"For help, go to %(command)s."
msgstr ""
-#: warehouse/templates/pages/help.html:705
+#: warehouse/templates/pages/help.html:707
#, python-format
msgid ""
"We take , so we can try to fix the problem, for you and others."
msgstr ""
-#: warehouse/templates/pages/help.html:718
+#: warehouse/templates/pages/help.html:720
#, python-format
msgid ""
"In a previous version of PyPI, it used to be possible for maintainers to "
@@ -5401,7 +5405,7 @@ msgid ""
"rel=\"noopener\">use twine to upload your project to PyPI."
msgstr ""
-#: warehouse/templates/pages/help.html:727
+#: warehouse/templates/pages/help.html:729
msgid ""
"Spammers return to PyPI with some regularity hoping to place their Search"
" Engine Optimized phishing, scam, and click-farming content on the site. "
@@ -5410,7 +5414,7 @@ msgid ""
"prime target."
msgstr ""
-#: warehouse/templates/pages/help.html:729
+#: warehouse/templates/pages/help.html:731
#, python-format
msgid ""
"When the PyPI administrators are overwhelmed by spam or "
@@ -5421,29 +5425,29 @@ msgid ""
"have updated it with reasoning for the intervention."
msgstr ""
-#: warehouse/templates/pages/help.html:738
+#: warehouse/templates/pages/help.html:740
msgid "PyPI will return these errors for one of these reasons:"
msgstr ""
-#: warehouse/templates/pages/help.html:740
+#: warehouse/templates/pages/help.html:742
msgid "Filename has been used and file exists"
msgstr ""
-#: warehouse/templates/pages/help.html:741
+#: warehouse/templates/pages/help.html:743
msgid "Filename has been used but file no longer exists"
msgstr ""
-#: warehouse/templates/pages/help.html:742
+#: warehouse/templates/pages/help.html:744
msgid "A file with the exact same content exists"
msgstr ""
-#: warehouse/templates/pages/help.html:744
+#: warehouse/templates/pages/help.html:746
msgid ""
"PyPI does not allow for a filename to be reused, even once a project has "
"been deleted and recreated."
msgstr ""
-#: warehouse/templates/pages/help.html:746
+#: warehouse/templates/pages/help.html:748
#, python-format
msgid ""
"To avoid this situation, pypi.org."
msgstr ""
-#: warehouse/templates/pages/help.html:753
+#: warehouse/templates/pages/help.html:754
+msgid ""
+"Package files uploaded to PyPI need to have names in a standard format. "
+"Your packaging tools should create files with suitable names by default. "
+"The filenames consist of a number of parts, separated by hyphens "
+"(-
):"
+msgstr ""
+
+#: warehouse/templates/pages/help.html:756
+msgid ""
+"For source distributions: normalized project name and version, e.g. "
+"importlib_metadata-4.10.1.tar.gz
"
+msgstr ""
+
+#: warehouse/templates/pages/help.html:757
+msgid ""
+"For wheels: normalized project name, version, build tag (optional), "
+"Python tag, ABI tag, and platform tag, e.g. "
+"importlib_metadata-4.10.1-py3-none-any.whl
"
+msgstr ""
+
+#: warehouse/templates/pages/help.html:760
+#, python-format
+msgid ""
+"A normalized project name must be fully lower-cased, with any runs of "
+"_-.
characters replaced with a single underscore "
+"(_
). The version number should be normalized as described in"
+" PEP 440. All parts are expected to consist "
+"only of ASCII characters."
+msgstr ""
+
+#: warehouse/templates/pages/help.html:764
+msgid ""
+"These rules have become stricter over time, so you may see existing "
+"packages with names which would no longer be allowed for new uploads."
+msgstr ""
+
+#: warehouse/templates/pages/help.html:768
#, python-format
msgid ""
"If you would like to request a new trove classifier file a pull request "
@@ -5461,7 +5502,7 @@ msgid ""
" to include a brief justification of why it is important."
msgstr ""
-#: warehouse/templates/pages/help.html:761
+#: warehouse/templates/pages/help.html:776
#, python-format
msgid ""
"If you're experiencing an issue with PyPI itself, we welcome "
@@ -5472,14 +5513,14 @@ msgid ""
" first check that a similar issue does not already exist."
msgstr ""
-#: warehouse/templates/pages/help.html:768
+#: warehouse/templates/pages/help.html:783
msgid ""
"If you are having an issue is with a specific package installed from "
"PyPI, you should reach out to the maintainers of that project directly "
"instead."
msgstr ""
-#: warehouse/templates/pages/help.html:777
+#: warehouse/templates/pages/help.html:792
#, python-format
msgid ""
"PyPI is powered by the Warehouse project; ."
msgstr ""
-#: warehouse/templates/pages/help.html:804
+#: warehouse/templates/pages/help.html:819
msgid ""
"As of April 16, 2018, PyPI.org is at \"production\" status, meaning that "
"it has moved out of beta and replaced the old site (pypi.python.org). It "
"is now robust, tested, and ready for expected browser and API traffic."
msgstr ""
-#: warehouse/templates/pages/help.html:806
+#: warehouse/templates/pages/help.html:821
#, python-format
msgid ""
"PyPI is heavily cached and distributed via private index."
msgstr ""
-#: warehouse/templates/pages/help.html:820
+#: warehouse/templates/pages/help.html:835
#, python-format
msgid ""
"We have a huge amount of work to do to continue to maintain and improve "
@@ -5552,22 +5593,22 @@ msgid ""
"target=\"_blank\" rel=\"noopener\">the Warehouse project)."
msgstr ""
-#: warehouse/templates/pages/help.html:825
+#: warehouse/templates/pages/help.html:840
msgid "Financial:"
msgstr ""
-#: warehouse/templates/pages/help.html:825
+#: warehouse/templates/pages/help.html:840
#, python-format
msgid ""
"We would deeply appreciate your donations to fund "
"development and maintenance."
msgstr ""
-#: warehouse/templates/pages/help.html:826
+#: warehouse/templates/pages/help.html:841
msgid "Development:"
msgstr ""
-#: warehouse/templates/pages/help.html:826
+#: warehouse/templates/pages/help.html:841
msgid ""
"Warehouse is open source, and we would love to see some new faces working"
" on the project. You do not need to be an experienced "
@@ -5575,7 +5616,7 @@ msgid ""
" you make your first open source pull request!"
msgstr ""
-#: warehouse/templates/pages/help.html:828
+#: warehouse/templates/pages/help.html:843
#, python-format
msgid ""
"If you have skills in Python, ElasticSearch, HTML, SCSS, JavaScript, or "
@@ -5589,7 +5630,7 @@ msgid ""
"here."
msgstr ""
-#: warehouse/templates/pages/help.html:836
+#: warehouse/templates/pages/help.html:851
#, python-format
msgid ""
"Issues are grouped into Python packaging forum on Discourse."
msgstr ""
-#: warehouse/templates/pages/help.html:854
+#: warehouse/templates/pages/help.html:869
#, python-format
msgid ""
"Changes to PyPI are generally announced on both the {% trans %}Troubleshooting{% endtrans %}
{% trans %}Package files uploaded to PyPI need to have names in a standard format. Your packaging tools should create files with suitable names by default. The filenames consist of a number of parts, separated by hyphens (-
):{% endtrans %}
importlib_metadata-4.10.1.tar.gz
{% endtrans %}importlib_metadata-4.10.1-py3-none-any.whl
{% endtrans %}
+ {% trans trimmed pep440="https://peps.python.org/pep-0440/" %}
+ A normalized project name must be fully lower-cased, with any runs of _-.
characters replaced with a single underscore (_
). The version number should be normalized as described in PEP 440. All parts are expected to consist only of ASCII characters.
+ {% endtrans %}
+
{% trans %}These rules have become stricter over time, so you may see existing packages with names which would no longer be allowed for new uploads.{% endtrans %}
+{% trans trimmed href='https://github.com/pypa/trove-classifiers/', title=gettext('External link') %}