From 51f05d4fdc63650781f0ad902be9e2b2bfbba67e Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Sun, 25 Jun 2023 10:36:56 -0400 Subject: [PATCH 01/13] Start deprecation notices for PEP 715 --- tests/unit/forklift/test_legacy.py | 58 +++++++++++++++++++ tests/unit/packaging/test_tasks.py | 16 +++++ warehouse/cli/projects.py | 33 +++++++++++ warehouse/email/__init__.py | 14 +++++ warehouse/forklift/legacy.py | 15 +++++ warehouse/locale/messages.pot | 46 +++++++++++++++ warehouse/packaging/tasks.py | 27 +++++++++ .../body.html | 32 ++++++++++ .../body.txt | 28 +++++++++ .../subject.txt | 17 ++++++ .../email/egg-uploads-deprecated/body.html | 32 ++++++++++ .../email/egg-uploads-deprecated/body.txt | 28 +++++++++ .../email/egg-uploads-deprecated/subject.txt | 17 ++++++ 13 files changed, 363 insertions(+) create mode 100644 warehouse/cli/projects.py create mode 100644 warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html create mode 100644 warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.txt create mode 100644 warehouse/templates/email/egg-uploads-deprecated-initial-notice/subject.txt create mode 100644 warehouse/templates/email/egg-uploads-deprecated/body.html create mode 100644 warehouse/templates/email/egg-uploads-deprecated/body.txt create mode 100644 warehouse/templates/email/egg-uploads-deprecated/subject.txt diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index c9056ecf56c4..310947f23758 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -56,6 +56,7 @@ from ...common.db.accounts import EmailFactory, UserFactory from ...common.db.classifiers import ClassifierFactory + from ...common.db.oidc import GitHubPublisherFactory from ...common.db.packaging import ( FileFactory, @@ -79,6 +80,13 @@ def _get_whl_testdata(name="fake_package", version="1.0"): return temp_f.getvalue() +def _get_egg_testdata(): + temp_f = io.BytesIO() + with zipfile.ZipFile(file=temp_f, mode="w") as zfp: + zfp.writestr(f"fake_package/PKG-INFO", "Fake metadata") + return temp_f.getvalue() + + def _storage_hash(data): return hashlib.blake2b(data, digest_size=256 // 8).hexdigest() @@ -94,6 +102,12 @@ def _storage_hash(data): _TAR_BZ2_PKG_STORAGE_HASH = _storage_hash(_TAR_BZ2_PKG_TESTDATA) +_EGG_PKG_TESTDATA = _get_egg_testdata() +_EGG_PKG_MD5 = hashlib.md5(_EGG_PKG_TESTDATA).hexdigest() +_EGG_PKG_SHA256 = hashlib.sha256(_EGG_PKG_TESTDATA).hexdigest() +_EGG_PKG_STORAGE_HASH = _storage_hash(_EGG_PKG_TESTDATA) + + class TestExcWithMessage: def test_exc_with_message(self): exc = legacy._exc_with_message(HTTPBadRequest, "My Test Message.") @@ -3677,6 +3691,50 @@ def test_fails_without_user(self, pyramid_config, pyramid_request): "See /the/help/url/ for more information." ) + def test_egg_upload_sends_pep_715_notice( + self, pyramid_config, db_request, metrics, monkeypatch + ): + user = UserFactory.create() + EmailFactory.create(user=user) + project = ProjectFactory.create() + RoleFactory.create(user=user, project=project) + + pyramid_config.testing_securitypolicy(identity=user) + db_request.user = user + db_request.user_agent = "warehouse-tests/6.6.6" + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "version": "1.0.0", + "summary": "This is my summary!", + "filetype": "bdist_egg", + "pyversion": "2.7", + "md5_digest": _EGG_PKG_MD5, + "content": pretend.stub( + filename="{}-{}.egg".format(project.name, "1.0.0"), + file=io.BytesIO(_EGG_PKG_TESTDATA), + type="application/zip", + ), + } + ) + + send_email = pretend.call_recorder(lambda *a, **kw: None) + monkeypatch.setattr(legacy, "send_egg_uploads_deprecated_email", send_email) + + storage_service = pretend.stub(store=lambda path, filepath, meta: None) + db_request.find_service = lambda svc, name=None, context=None: { + IFileStorage: storage_service, + IMetricsService: metrics, + }.get(svc) + + resp = legacy.file_upload(db_request) + + assert resp.status_code == 200 + assert send_email.calls == [ + pretend.call(db_request, user, project_name=project.name) + ] + @pytest.mark.parametrize("status", [True, False]) def test_legacy_purge(monkeypatch, status): diff --git a/tests/unit/packaging/test_tasks.py b/tests/unit/packaging/test_tasks.py index 8a88c8fdb4db..266e505b2449 100644 --- a/tests/unit/packaging/test_tasks.py +++ b/tests/unit/packaging/test_tasks.py @@ -946,3 +946,19 @@ def test_compute_2fa_metrics(db_request, monkeypatch): pretend.call("warehouse.2fa.total_users_with_webauthn_enabled", 1), pretend.call("warehouse.2fa.total_users_with_two_factor_enabled", 3), ] + + +# def test_send_pep_715_notices(db_request): +# no_egg_project = ProjectFactory() +# no_egg_project_owner_role = RoleFactory.create( +# FileFactory(project=no_egg_project, packagetype="bdist_wheel", upload_time="2022-06-01") +# FileFactory(project=no_egg_project, packagetype="bdist_wheel", upload_time="2023-06-01") +# +# some_egg_project = ProjectFactory() +# FileFactory(project=some_egg_project, packagetype="bdist_wheel", upload_time="2022-06-01") +# FileFactory(project=some_egg_project, packagetype="bdist_egg", upload_time="2022-06-01") +# FileFactory(project=some_egg_project, packagetype="bdist_wheel", upload_time="2023-06-01") +# FileFactory(project=some_egg_project, packagetype="bdist_egg", upload_time="2023-06-01") +# +# rotten_egg_project = ProjectFactory() +# FileFactory(project=rotten_egg_project, packagetype="bdist_egg", upload_time="2022-06-01") diff --git a/warehouse/cli/projects.py b/warehouse/cli/projects.py new file mode 100644 index 000000000000..18d776946eb9 --- /dev/null +++ b/warehouse/cli/projects.py @@ -0,0 +1,33 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import click + +from warehouse.cli import warehouse +from warehouse.packaging.tasks import send_pep_715_notices + + +@warehouse.group() # pragma: no branch +def projects(): + """ + Group for projects commands. + """ + + +@projects.command() +@click.pass_obj +def notify_pep_715(config): + """ + Notifies projects that have uploaded eggs since Jan 1, 2023 of PEP 715 + """ + request = config.task(send_pep_715_notices).get_request() + config.task(send_pep_715_notices).run(request) diff --git a/warehouse/email/__init__.py b/warehouse/email/__init__.py index 967160b50a24..df7262ab8f38 100644 --- a/warehouse/email/__init__.py +++ b/warehouse/email/__init__.py @@ -1022,6 +1022,20 @@ def send_pending_trusted_publisher_invalidated_email(request, user, project_name } +@_email("egg-uploads-deprecated") +def send_egg_uploads_deprecated_email(request, user, project_name): + return { + "project_name": project_name, + } + + +@_email("egg-uploads-deprecated-initial-notice") +def send_egg_uploads_deprecated_initial_email(request, user, project_name): + return { + "project_name": project_name, + } + + def includeme(config): email_sending_class = config.maybe_dotted(config.registry.settings["mail.backend"]) config.register_service_factory(email_sending_class.create_service, IEmailSender) diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py index 6ed6414992e3..c3ccb1e65d0b 100644 --- a/warehouse/forklift/legacy.py +++ b/warehouse/forklift/legacy.py @@ -51,6 +51,7 @@ from warehouse.classifiers.models import Classifier from warehouse.email import ( send_basic_auth_with_two_factor_email, + send_egg_uploads_deprecated_email, send_gpg_signature_uploaded_email, ) from warehouse.errors import BasicAuthTwoFactorEnabled @@ -1473,6 +1474,20 @@ def file_upload(request): request.db.flush() # flush db now so server default values are populated for celery + # Check that if it's a bdist_egg, notify regarding deprecation. + if filename.endswith(".egg"): + # send deprecation notice + contributors = project.users + if project.organization: + contributors += project.organization.owners + + for contributor in sorted(contributors): + send_egg_uploads_deprecated_email( + request, + contributor, + project_name=project.name, + ) + # Push updates to BigQuery dist_metadata = { "metadata_version": form["metadata_version"].data, diff --git a/warehouse/locale/messages.pot b/warehouse/locale/messages.pot index 5496faad5e4f..04214081979e 100644 --- a/warehouse/locale/messages.pot +++ b/warehouse/locale/messages.pot @@ -1754,6 +1754,8 @@ msgid "" msgstr "" #: warehouse/templates/email/basic-auth-with-2fa/body.html:17 +#: warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html:17 +#: warehouse/templates/email/egg-uploads-deprecated/body.html:17 #: warehouse/templates/email/gpg-signature-uploaded/body.html:17 #: warehouse/templates/email/password-compromised-hibp/body.html:18 #: warehouse/templates/email/password-compromised/body.html:18 @@ -1778,6 +1780,8 @@ msgid "" msgstr "" #: warehouse/templates/email/basic-auth-with-2fa/body.html:25 +#: warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html:25 +#: warehouse/templates/email/egg-uploads-deprecated/body.html:25 #: warehouse/templates/email/gpg-signature-uploaded/body.html:22 #: warehouse/templates/email/password-compromised-hibp/body.html:32 #: warehouse/templates/email/password-compromised/body.html:31 @@ -1818,6 +1822,48 @@ msgid "" "organization" msgstr "" +#: warehouse/templates/email/egg-uploads-deprecated/body.html:19 +#, python-format +msgid "" +"During a recent upload of %(project_name)s to %(site)s, we noticed you " +"uploaded an .egg/bdist_egg distribution file." +msgstr "" + +#: warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html:22 +#: warehouse/templates/email/egg-uploads-deprecated/body.html:22 +#, python-format +msgid "" +"%(site)s is in the process of deprecating bdist_egg " +"distribution file uploads, and will not accept them beginning August 1, " +"2023." +msgstr "" + +#: warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html:27 +#: warehouse/templates/email/egg-uploads-deprecated/body.html:27 +msgid "" +"Update your release tooling to cease upload of " +".egg/bdist_egg distribution files. If you are " +"not already, and require binary releases, update your release tooling to " +"upload .whl/bdist_wheel distribution files " +"instead." +msgstr "" + +#: warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html:30 +#: warehouse/templates/email/egg-uploads-deprecated/body.html:30 +msgid "" +"Read more about Wheels vs Egg distribution files." +msgstr "" + +#: warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html:19 +#, python-format +msgid "" +"Since January 1, 2023, we noticed upload(s) of " +".egg/bdist_egg distribution file(s) to %(site)s" +" for %(project_name)s." +msgstr "" + #: warehouse/templates/email/gpg-signature-uploaded/body.html:19 #, python-format msgid "" diff --git a/warehouse/packaging/tasks.py b/warehouse/packaging/tasks.py index 3fe99edd4ead..c070dd30231e 100644 --- a/warehouse/packaging/tasks.py +++ b/warehouse/packaging/tasks.py @@ -24,6 +24,7 @@ from warehouse import tasks from warehouse.accounts.models import User, WebAuthn +from warehouse.email import send_egg_uploads_deprecated_initial_email from warehouse.metrics import IMetricsService from warehouse.packaging.interfaces import IFileStorage from warehouse.packaging.models import Description, File, Project, Release, Role @@ -508,3 +509,29 @@ def populate_data_using_schema(file): json_rows, table_name, job_config=LoadJobConfig(schema=table_schema) ).result() break + + +@tasks.task(ignore_result=True, acks_late=True) +def send_pep_715_notices(request): + """ + Notifies projects that have uploaded eggs since Jan 1, 2023 of PEP 715 + """ + projects = set() + for release_file in ( + request.db.query(File) + .filter(File.packagetype == "bdist_egg") + .filter(File.upload_time >= "2023-01-01") + ): + projects.add(release_file.release.project) + + for project in projects: + contributors = project.users + if project.organization: + contributors += project.organization.owners + + for contributor in sorted(contributors): + send_egg_uploads_deprecated_initial_email( + request, + contributor, + project_name=project.name, + ) diff --git a/warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html b/warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html new file mode 100644 index 000000000000..f6c4f44e6cee --- /dev/null +++ b/warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.html @@ -0,0 +1,32 @@ +{# + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-#} +{% extends "email/_base/body.html" %} + +{% block content %} +

{% trans %}What?{% endtrans %}

+

+ {% trans site=request.registry.settings["site.name"] %}Since January 1, 2023, we noticed upload(s) of .egg/bdist_egg distribution file(s) to {{ site }} for {{ project_name }}.{% endtrans %} +

+

+ {% trans site=request.registry.settings["site.name"] %}{{ site }} is in the process of deprecating bdist_egg distribution file uploads, and will not accept them beginning August 1, 2023.{% endtrans %} +

+ +

{% trans %}What should I do?{% endtrans %}

+

+ {% trans %}Update your release tooling to cease upload of .egg/bdist_egg distribution files. If you are not already, and require binary releases, update your release tooling to upload .whl/bdist_wheel distribution files instead.{% endtrans %} +

+

+ {% trans %}Read more about Wheels vs Egg distribution files.{% endtrans %} +

+{% endblock %} diff --git a/warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.txt b/warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.txt new file mode 100644 index 000000000000..15293a92b24f --- /dev/null +++ b/warehouse/templates/email/egg-uploads-deprecated-initial-notice/body.txt @@ -0,0 +1,28 @@ +{# + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-#} +{% extends "email/_base/body.txt" %} + +{% block content %} +# {% trans %}What?{% endtrans %} + +{% trans site=request.registry.settings["site.name"] %}Since January 1, 2023, we noticed upload(s) of `.egg`/`bdist_egg` distribution file(s) to {{ site }} for {{ project_name }}.{% endtrans %} + +{% trans site=request.registry.settings["site.name"] %}{{ site }} is in the process of deprecating `bdist_egg` distribution file uploads, and will not accept them beginning August 1, 2023.{% endtrans %} + +# {% trans %}What should I do?{% endtrans %} + +{% trans %}Update your release tooling to cease upload of `.egg`/`bdist_egg` distribution files. If you are not already, and require binary releases, update your release tooling to upload `.whl`/`bdist_wheel` distribution files instead.{% endtrans %} + +{% trans %}Read more about [Wheels vs Egg](https://packaging.python.org/en/latest/discussions/wheel-vs-egg) distribution files.{% endtrans %} +{% endblock %} diff --git a/warehouse/templates/email/egg-uploads-deprecated-initial-notice/subject.txt b/warehouse/templates/email/egg-uploads-deprecated-initial-notice/subject.txt new file mode 100644 index 000000000000..f8a0610b80f8 --- /dev/null +++ b/warehouse/templates/email/egg-uploads-deprecated-initial-notice/subject.txt @@ -0,0 +1,17 @@ +{# + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-#} + +{% extends "email/_base/subject.txt" %} + +{% block subject %}{% trans site=request.registry.settings["site.name"] %}Deprecated bdist_egg uploaded to {{ site }}{% endtrans %}{% endblock %} diff --git a/warehouse/templates/email/egg-uploads-deprecated/body.html b/warehouse/templates/email/egg-uploads-deprecated/body.html new file mode 100644 index 000000000000..2b609dd8477c --- /dev/null +++ b/warehouse/templates/email/egg-uploads-deprecated/body.html @@ -0,0 +1,32 @@ +{# + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-#} +{% extends "email/_base/body.html" %} + +{% block content %} +

{% trans %}What?{% endtrans %}

+

+ {% trans site=request.registry.settings["site.name"] %}During a recent upload of {{ project_name }} to {{ site }}, we noticed you uploaded an .egg/bdist_egg distribution file.{% endtrans %} +

+

+ {% trans site=request.registry.settings["site.name"] %}{{ site }} is in the process of deprecating bdist_egg distribution file uploads, and will not accept them beginning August 1, 2023.{% endtrans %} +

+ +

{% trans %}What should I do?{% endtrans %}

+

+ {% trans %}Update your release tooling to cease upload of .egg/bdist_egg distribution files. If you are not already, and require binary releases, update your release tooling to upload .whl/bdist_wheel distribution files instead.{% endtrans %} +

+

+ {% trans %}Read more about Wheels vs Egg distribution files.{% endtrans %} +

+{% endblock %} diff --git a/warehouse/templates/email/egg-uploads-deprecated/body.txt b/warehouse/templates/email/egg-uploads-deprecated/body.txt new file mode 100644 index 000000000000..11d90646cc01 --- /dev/null +++ b/warehouse/templates/email/egg-uploads-deprecated/body.txt @@ -0,0 +1,28 @@ +{# + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-#} +{% extends "email/_base/body.txt" %} + +{% block content %} +# {% trans %}What?{% endtrans %} + +{% trans site=request.registry.settings["site.name"] %}During a recent upload of {{ project_name }} to {{ site }}, we noticed you uploaded an `.egg`/`bdist_egg` distribution file.{% endtrans %} + +{% trans site=request.registry.settings["site.name"] %}{{ site }} is in the process of deprecating `bdist_egg` distribution file uploads, and will not accept them beginning August 1, 2023.{% endtrans %} + +# {% trans %}What should I do?{% endtrans %} + +{% trans %}Update your release tooling to cease upload of `.egg`/`bdist_egg` distribution files. If you are not already, and require binary releases, update your release tooling to upload `.whl`/`bdist_wheel` distribution files instead.{% endtrans %} + +{% trans %}Read more about [Wheels vs Egg](https://packaging.python.org/en/latest/discussions/wheel-vs-egg) distribution files.{% endtrans %} +{% endblock %} diff --git a/warehouse/templates/email/egg-uploads-deprecated/subject.txt b/warehouse/templates/email/egg-uploads-deprecated/subject.txt new file mode 100644 index 000000000000..f8a0610b80f8 --- /dev/null +++ b/warehouse/templates/email/egg-uploads-deprecated/subject.txt @@ -0,0 +1,17 @@ +{# + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-#} + +{% extends "email/_base/subject.txt" %} + +{% block subject %}{% trans site=request.registry.settings["site.name"] %}Deprecated bdist_egg uploaded to {{ site }}{% endtrans %}{% endblock %} From c70901cb3c16da5f54dc169dcdbcdf37a1671404 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 25 Jun 2023 17:17:18 -0400 Subject: [PATCH 02/13] tests: add PEP 715 email template tests --- tests/unit/email/test_init.py | 86 +++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/unit/email/test_init.py b/tests/unit/email/test_init.py index 869974b4f895..52d559f1835c 100644 --- a/tests/unit/email/test_init.py +++ b/tests/unit/email/test_init.py @@ -5822,3 +5822,89 @@ def test_trusted_publisher_emails( }, ) ] + + +class TestPEP715Emails: + @pytest.mark.parametrize( + "fn, template_name", + [ + (email.send_egg_uploads_deprecated_email, "egg-uploads-deprecated"), + (email.send_egg_uploads_deprecated_initial_email, "egg-uploads-deprecated-initial-notice"), + ], + ) + def test_pep_715_emails( + self, pyramid_request, pyramid_config, monkeypatch, fn, template_name + ): + stub_user = pretend.stub( + id="id", + username="username", + name="", + email="email@example.com", + primary_email=pretend.stub(email="email@example.com", verified=True), + ) + subject_renderer = pyramid_config.testing_add_renderer( + f"email/{ template_name }/subject.txt" + ) + subject_renderer.string_response = "Email Subject" + body_renderer = pyramid_config.testing_add_renderer( + f"email/{ template_name }/body.txt" + ) + body_renderer.string_response = "Email Body" + html_renderer = pyramid_config.testing_add_renderer( + f"email/{ template_name }/body.html" + ) + html_renderer.string_response = "Email HTML Body" + + send_email = pretend.stub( + delay=pretend.call_recorder(lambda *args, **kwargs: None) + ) + pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email) + monkeypatch.setattr(email, "send_email", send_email) + + pyramid_request.db = pretend.stub( + query=lambda a: pretend.stub( + filter=lambda *a: pretend.stub( + one=lambda: pretend.stub(user_id=stub_user.id) + ) + ), + ) + pyramid_request.user = stub_user + pyramid_request.registry.settings = {"mail.sender": "noreply@example.com"} + + project_name = "test_project" + result = fn( + pyramid_request, + stub_user, + project_name=project_name, + ) + + assert result == { + "project_name": project_name, + } + subject_renderer.assert_() + body_renderer.assert_(project_name=project_name) + html_renderer.assert_(project_name=project_name) + assert pyramid_request.task.calls == [pretend.call(send_email)] + assert send_email.delay.calls == [ + pretend.call( + f"{stub_user.username} <{stub_user.email}>", + { + "subject": "Email Subject", + "body_text": "Email Body", + "body_html": ( + "\n\n" + "

Email HTML Body

\n\n" + ), + }, + { + "tag": "account:email:sent", + "user_id": stub_user.id, + "additional": { + "from_": "noreply@example.com", + "to": stub_user.email, + "subject": "Email Subject", + "redact_ip": False, + }, + }, + ) + ] From 0ad0bc8c7654e55e6e81bae83d1ff46e0bc0e211 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 25 Jun 2023 17:48:16 -0400 Subject: [PATCH 03/13] tests: lintage, initial tasks test This test isn't passing yet. --- tests/unit/email/test_init.py | 5 ++- tests/unit/forklift/test_legacy.py | 1 - tests/unit/packaging/test_tasks.py | 68 ++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/tests/unit/email/test_init.py b/tests/unit/email/test_init.py index 52d559f1835c..ee92857ec116 100644 --- a/tests/unit/email/test_init.py +++ b/tests/unit/email/test_init.py @@ -5829,7 +5829,10 @@ class TestPEP715Emails: "fn, template_name", [ (email.send_egg_uploads_deprecated_email, "egg-uploads-deprecated"), - (email.send_egg_uploads_deprecated_initial_email, "egg-uploads-deprecated-initial-notice"), + ( + email.send_egg_uploads_deprecated_initial_email, + "egg-uploads-deprecated-initial-notice", + ), ], ) def test_pep_715_emails( diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 310947f23758..f3d9017b20cf 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -56,7 +56,6 @@ from ...common.db.accounts import EmailFactory, UserFactory from ...common.db.classifiers import ClassifierFactory - from ...common.db.oidc import GitHubPublisherFactory from ...common.db.packaging import ( FileFactory, diff --git a/tests/unit/packaging/test_tasks.py b/tests/unit/packaging/test_tasks.py index 266e505b2449..4340b0afb31c 100644 --- a/tests/unit/packaging/test_tasks.py +++ b/tests/unit/packaging/test_tasks.py @@ -24,6 +24,7 @@ import warehouse.packaging.tasks from warehouse.accounts.models import WebAuthn +from warehouse.packaging import tasks from warehouse.packaging.models import Description from warehouse.packaging.tasks import ( check_file_cache_tasks_outstanding, @@ -948,17 +949,56 @@ def test_compute_2fa_metrics(db_request, monkeypatch): ] -# def test_send_pep_715_notices(db_request): -# no_egg_project = ProjectFactory() -# no_egg_project_owner_role = RoleFactory.create( -# FileFactory(project=no_egg_project, packagetype="bdist_wheel", upload_time="2022-06-01") -# FileFactory(project=no_egg_project, packagetype="bdist_wheel", upload_time="2023-06-01") -# -# some_egg_project = ProjectFactory() -# FileFactory(project=some_egg_project, packagetype="bdist_wheel", upload_time="2022-06-01") -# FileFactory(project=some_egg_project, packagetype="bdist_egg", upload_time="2022-06-01") -# FileFactory(project=some_egg_project, packagetype="bdist_wheel", upload_time="2023-06-01") -# FileFactory(project=some_egg_project, packagetype="bdist_egg", upload_time="2023-06-01") -# -# rotten_egg_project = ProjectFactory() -# FileFactory(project=rotten_egg_project, packagetype="bdist_egg", upload_time="2022-06-01") +def test_send_pep_715_notices(db_request, monkeypatch): + no_egg_project = ProjectFactory() + no_egg_project_owner = UserFactory.create() + RoleFactory.create(user=no_egg_project_owner, project=no_egg_project) + no_egg_release = ReleaseFactory.create(project=no_egg_project) + FileFactory( + release=no_egg_release, packagetype="bdist_wheel", upload_time="2022-06-01" + ) + FileFactory( + release=no_egg_release, packagetype="bdist_wheel", upload_time="2023-06-01" + ) + + some_egg_project = ProjectFactory() + some_egg_project_owner = UserFactory.create() + RoleFactory.create(user=some_egg_project_owner, project=some_egg_project) + some_egg_release = ReleaseFactory.create(project=some_egg_project) + FileFactory( + release=some_egg_release, packagetype="bdist_wheel", upload_time="2022-06-01" + ) + FileFactory( + release=some_egg_release, packagetype="bdist_egg", upload_time="2022-06-01" + ) + FileFactory( + release=some_egg_release, packagetype="bdist_wheel", upload_time="2023-06-01" + ) + FileFactory( + release=some_egg_release, packagetype="bdist_egg", upload_time="2023-06-01" + ) + + rotten_egg_project = ProjectFactory() + rotten_egg_project_user = UserFactory.create() + RoleFactory.create(user=rotten_egg_project_user, project=rotten_egg_project) + rotten_egg_release = ReleaseFactory.create(project=rotten_egg_project) + FileFactory( + release=rotten_egg_release, packagetype="bdist_egg", upload_time="2022-06-01" + ) + + send_egg_uploads_deprecated_initial_email = pretend.call_recorder( + lambda *a, **kw: None + ) + monkeypatch.setattr( + tasks, + "send_egg_uploads_deprecated_initial_email", + send_egg_uploads_deprecated_initial_email, + ) + + # The only emails sent are to `some_egg_project`'s contributors, + # since it's the only one that uploaded an egg after 2023-01-01. + assert send_egg_uploads_deprecated_initial_email.calls == [ + pretend.call( + db_request, some_egg_project_owner, project_name=some_egg_project.name + ) + ] From 91c6cf2e2124099333965d80d22bd1fb8c50ee58 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 25 Jun 2023 18:07:51 -0400 Subject: [PATCH 04/13] tests: fixup PEP 715 task test --- tests/unit/packaging/test_tasks.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/unit/packaging/test_tasks.py b/tests/unit/packaging/test_tasks.py index 4340b0afb31c..d4dc738bfd0c 100644 --- a/tests/unit/packaging/test_tasks.py +++ b/tests/unit/packaging/test_tasks.py @@ -23,6 +23,11 @@ import warehouse.packaging.tasks +from tests.common.db.organizations import ( + OrganizationFactory, + OrganizationProjectFactory, + OrganizationRoleFactory, +) from warehouse.accounts.models import WebAuthn from warehouse.packaging import tasks from warehouse.packaging.models import Description @@ -964,6 +969,12 @@ def test_send_pep_715_notices(db_request, monkeypatch): some_egg_project = ProjectFactory() some_egg_project_owner = UserFactory.create() RoleFactory.create(user=some_egg_project_owner, project=some_egg_project) + + some_egg_org = OrganizationFactory() + OrganizationProjectFactory(organization=some_egg_org, project=some_egg_project) + some_egg_org_owner = UserFactory.create() + OrganizationRoleFactory.create(user=some_egg_org_owner, organization=some_egg_org) + some_egg_release = ReleaseFactory.create(project=some_egg_project) FileFactory( release=some_egg_release, packagetype="bdist_wheel", upload_time="2022-06-01" @@ -995,10 +1006,16 @@ def test_send_pep_715_notices(db_request, monkeypatch): send_egg_uploads_deprecated_initial_email, ) - # The only emails sent are to `some_egg_project`'s contributors, - # since it's the only one that uploaded an egg after 2023-01-01. - assert send_egg_uploads_deprecated_initial_email.calls == [ + tasks.send_pep_715_notices(db_request) + + # The only emails sent are to `some_egg_project`'s contributors and + # containing org contributors, since it's the only one that uploaded an + # egg after 2023-01-01. + assert set(tasks.send_egg_uploads_deprecated_initial_email.calls) == { pretend.call( db_request, some_egg_project_owner, project_name=some_egg_project.name - ) - ] + ), + pretend.call( + db_request, some_egg_org_owner, project_name=some_egg_project.name + ), + } From 1b5385034c1d9eb7b57ff829dff925cff63d16c0 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 25 Jun 2023 18:08:04 -0400 Subject: [PATCH 05/13] packaging/tasks: consolidate filters --- warehouse/packaging/tasks.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/warehouse/packaging/tasks.py b/warehouse/packaging/tasks.py index c070dd30231e..c7984d883f8a 100644 --- a/warehouse/packaging/tasks.py +++ b/warehouse/packaging/tasks.py @@ -517,10 +517,9 @@ def send_pep_715_notices(request): Notifies projects that have uploaded eggs since Jan 1, 2023 of PEP 715 """ projects = set() - for release_file in ( - request.db.query(File) - .filter(File.packagetype == "bdist_egg") - .filter(File.upload_time >= "2023-01-01") + for release_file in request.db.query(File).filter( + File.packagetype == "bdist_egg", + File.upload_time >= "2023-01-01", ): projects.add(release_file.release.project) From dcef8419ef61936db9bed290bb95c3e56fec0321 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 25 Jun 2023 18:17:33 -0400 Subject: [PATCH 06/13] test_legacy: remove fstring --- tests/unit/forklift/test_legacy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index f3d9017b20cf..55502c9be5e5 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -82,7 +82,7 @@ def _get_whl_testdata(name="fake_package", version="1.0"): def _get_egg_testdata(): temp_f = io.BytesIO() with zipfile.ZipFile(file=temp_f, mode="w") as zfp: - zfp.writestr(f"fake_package/PKG-INFO", "Fake metadata") + zfp.writestr("fake_package/PKG-INFO", "Fake metadata") return temp_f.getvalue() From 023ad4ff7da5d4eded4aa0ff52d2fd4beba37d2e Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 25 Jun 2023 18:17:42 -0400 Subject: [PATCH 07/13] tests: increase coverage --- tests/unit/packaging/test_tasks.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/tests/unit/packaging/test_tasks.py b/tests/unit/packaging/test_tasks.py index d4dc738bfd0c..134e51d6135e 100644 --- a/tests/unit/packaging/test_tasks.py +++ b/tests/unit/packaging/test_tasks.py @@ -955,6 +955,7 @@ def test_compute_2fa_metrics(db_request, monkeypatch): def test_send_pep_715_notices(db_request, monkeypatch): + # No eggs, no emails. no_egg_project = ProjectFactory() no_egg_project_owner = UserFactory.create() RoleFactory.create(user=no_egg_project_owner, project=no_egg_project) @@ -966,6 +967,8 @@ def test_send_pep_715_notices(db_request, monkeypatch): release=no_egg_release, packagetype="bdist_wheel", upload_time="2023-06-01" ) + # Projects with eggs uploaded in 2023 get emails sent to their + # contributors and org contributors. some_egg_project = ProjectFactory() some_egg_project_owner = UserFactory.create() RoleFactory.create(user=some_egg_project_owner, project=some_egg_project) @@ -989,6 +992,25 @@ def test_send_pep_715_notices(db_request, monkeypatch): release=some_egg_release, packagetype="bdist_egg", upload_time="2023-06-01" ) + # Same as above, but without an organization in the mix. + another_egg_project = ProjectFactory() + another_egg_project_owner = UserFactory.create() + RoleFactory.create(user=another_egg_project_owner, project=another_egg_project) + another_egg_release = ReleaseFactory.create(project=another_egg_project) + FileFactory( + release=another_egg_release, packagetype="bdist_wheel", upload_time="2022-06-01" + ) + FileFactory( + release=another_egg_release, packagetype="bdist_egg", upload_time="2022-06-01" + ) + FileFactory( + release=another_egg_release, packagetype="bdist_wheel", upload_time="2023-06-01" + ) + FileFactory( + release=another_egg_release, packagetype="bdist_egg", upload_time="2023-06-01" + ) + + # Old eggs (pre-2023), no emails. rotten_egg_project = ProjectFactory() rotten_egg_project_user = UserFactory.create() RoleFactory.create(user=rotten_egg_project_user, project=rotten_egg_project) @@ -1008,9 +1030,6 @@ def test_send_pep_715_notices(db_request, monkeypatch): tasks.send_pep_715_notices(db_request) - # The only emails sent are to `some_egg_project`'s contributors and - # containing org contributors, since it's the only one that uploaded an - # egg after 2023-01-01. assert set(tasks.send_egg_uploads_deprecated_initial_email.calls) == { pretend.call( db_request, some_egg_project_owner, project_name=some_egg_project.name @@ -1018,4 +1037,7 @@ def test_send_pep_715_notices(db_request, monkeypatch): pretend.call( db_request, some_egg_org_owner, project_name=some_egg_project.name ), + pretend.call( + db_request, another_egg_project_owner, project_name=another_egg_project.name + ), } From 4ce522cbdbd386811437008a13a9abb2ab1e7d01 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Sun, 25 Jun 2023 18:35:15 -0400 Subject: [PATCH 08/13] tests: round out coverage --- tests/unit/cli/test_projects.py | 33 ++++++++++++++++++ tests/unit/forklift/test_legacy.py | 55 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 tests/unit/cli/test_projects.py diff --git a/tests/unit/cli/test_projects.py b/tests/unit/cli/test_projects.py new file mode 100644 index 000000000000..6cf97f9ce6c0 --- /dev/null +++ b/tests/unit/cli/test_projects.py @@ -0,0 +1,33 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pretend + +from warehouse.cli import projects +from warehouse.packaging.tasks import send_pep_715_notices + + +def test_notify_pep_715(cli): + request = pretend.stub() + task = pretend.stub( + get_request=pretend.call_recorder(lambda: request), + run=pretend.call_recorder(lambda r: None), + ) + config = pretend.stub(task=pretend.call_recorder(lambda f: task)) + + cli.invoke(projects.notify_pep_715, obj=config) + assert config.task.calls == [ + pretend.call(send_pep_715_notices), + pretend.call(send_pep_715_notices), + ] + assert task.get_request.calls == [pretend.call()] + assert task.run.calls == [pretend.call(request)] diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 55502c9be5e5..deabd12aa1f7 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -33,6 +33,11 @@ from wtforms.form import Form from wtforms.validators import ValidationError +from tests.common.db.organizations import ( + OrganizationFactory, + OrganizationProjectFactory, + OrganizationRoleFactory, +) from warehouse.admin.flags import AdminFlag, AdminFlagValue from warehouse.classifiers.models import Classifier from warehouse.errors import BasicAuthTwoFactorEnabled @@ -3734,6 +3739,56 @@ def test_egg_upload_sends_pep_715_notice( pretend.call(db_request, user, project_name=project.name) ] + def test_egg_upload_sends_pep_715_notice_org_roles( + self, pyramid_config, db_request, metrics, monkeypatch + ): + user = UserFactory.create() + EmailFactory.create(user=user) + project = ProjectFactory.create() + RoleFactory.create(user=user, project=project) + + org = OrganizationFactory() + OrganizationProjectFactory(organization=org, project=project) + org_owner = UserFactory.create() + OrganizationRoleFactory.create(user=org_owner, organization=org) + + pyramid_config.testing_securitypolicy(identity=user) + db_request.user = user + db_request.user_agent = "warehouse-tests/6.6.6" + db_request.POST = MultiDict( + { + "metadata_version": "1.2", + "name": project.name, + "version": "1.0.0", + "summary": "This is my summary!", + "filetype": "bdist_egg", + "pyversion": "2.7", + "md5_digest": _EGG_PKG_MD5, + "content": pretend.stub( + filename="{}-{}.egg".format(project.name, "1.0.0"), + file=io.BytesIO(_EGG_PKG_TESTDATA), + type="application/zip", + ), + } + ) + + send_email = pretend.call_recorder(lambda *a, **kw: None) + monkeypatch.setattr(legacy, "send_egg_uploads_deprecated_email", send_email) + + storage_service = pretend.stub(store=lambda path, filepath, meta: None) + db_request.find_service = lambda svc, name=None, context=None: { + IFileStorage: storage_service, + IMetricsService: metrics, + }.get(svc) + + resp = legacy.file_upload(db_request) + + assert resp.status_code == 200 + assert set(send_email.calls) == { + pretend.call(db_request, user, project_name=project.name), + pretend.call(db_request, org_owner, project_name=project.name), + } + @pytest.mark.parametrize("status", [True, False]) def test_legacy_purge(monkeypatch, status): From 3ce92f2864e3fbf7460732feaefd416961f7b92f Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Mon, 26 Jun 2023 07:29:55 -0400 Subject: [PATCH 09/13] email _all_ the maintainers --- tests/unit/forklift/test_legacy.py | 12 ++++++++++++ tests/unit/packaging/test_tasks.py | 14 ++++++++++++++ warehouse/forklift/legacy.py | 2 ++ warehouse/packaging/tasks.py | 2 ++ 4 files changed, 30 insertions(+) diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index deabd12aa1f7..67f1864a6025 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -37,6 +37,9 @@ OrganizationFactory, OrganizationProjectFactory, OrganizationRoleFactory, + TeamFactory, + TeamProjectRoleFactory, + TeamRoleFactory, ) from warehouse.admin.flags import AdminFlag, AdminFlagValue from warehouse.classifiers.models import Classifier @@ -3752,6 +3755,14 @@ def test_egg_upload_sends_pep_715_notice_org_roles( org_owner = UserFactory.create() OrganizationRoleFactory.create(user=org_owner, organization=org) + org_member = UserFactory.create() + OrganizationRoleFactory.create( + user=org_member, organization=org, role_name="Member" + ) + team = TeamFactory.create(organization=org) + TeamRoleFactory.create(team=team, user=org_member) + TeamProjectRoleFactory.create(project=project, team=team) + pyramid_config.testing_securitypolicy(identity=user) db_request.user = user db_request.user_agent = "warehouse-tests/6.6.6" @@ -3787,6 +3798,7 @@ def test_egg_upload_sends_pep_715_notice_org_roles( assert set(send_email.calls) == { pretend.call(db_request, user, project_name=project.name), pretend.call(db_request, org_owner, project_name=project.name), + pretend.call(db_request, org_member, project_name=project.name), } diff --git a/tests/unit/packaging/test_tasks.py b/tests/unit/packaging/test_tasks.py index 134e51d6135e..0cca688d5039 100644 --- a/tests/unit/packaging/test_tasks.py +++ b/tests/unit/packaging/test_tasks.py @@ -27,6 +27,9 @@ OrganizationFactory, OrganizationProjectFactory, OrganizationRoleFactory, + TeamFactory, + TeamProjectRoleFactory, + TeamRoleFactory, ) from warehouse.accounts.models import WebAuthn from warehouse.packaging import tasks @@ -978,6 +981,14 @@ def test_send_pep_715_notices(db_request, monkeypatch): some_egg_org_owner = UserFactory.create() OrganizationRoleFactory.create(user=some_egg_org_owner, organization=some_egg_org) + some_egg_org_member = UserFactory.create() + OrganizationRoleFactory.create( + user=some_egg_org_member, organization=some_egg_org, role_name="Member" + ) + some_egg_team = TeamFactory.create(organization=some_egg_org) + TeamRoleFactory.create(team=some_egg_team, user=some_egg_org_member) + TeamProjectRoleFactory.create(project=some_egg_project, team=some_egg_team) + some_egg_release = ReleaseFactory.create(project=some_egg_project) FileFactory( release=some_egg_release, packagetype="bdist_wheel", upload_time="2022-06-01" @@ -1037,6 +1048,9 @@ def test_send_pep_715_notices(db_request, monkeypatch): pretend.call( db_request, some_egg_org_owner, project_name=some_egg_project.name ), + pretend.call( + db_request, some_egg_org_member, project_name=some_egg_project.name + ), pretend.call( db_request, another_egg_project_owner, project_name=another_egg_project.name ), diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py index c3ccb1e65d0b..724b06f1385c 100644 --- a/warehouse/forklift/legacy.py +++ b/warehouse/forklift/legacy.py @@ -1480,6 +1480,8 @@ def file_upload(request): contributors = project.users if project.organization: contributors += project.organization.owners + for teamrole in project.team_project_roles: + contributors += teamrole.team.members for contributor in sorted(contributors): send_egg_uploads_deprecated_email( diff --git a/warehouse/packaging/tasks.py b/warehouse/packaging/tasks.py index c7984d883f8a..749ee37c1d6c 100644 --- a/warehouse/packaging/tasks.py +++ b/warehouse/packaging/tasks.py @@ -527,6 +527,8 @@ def send_pep_715_notices(request): contributors = project.users if project.organization: contributors += project.organization.owners + for teamrole in project.team_project_roles: + contributors += teamrole.team.members for contributor in sorted(contributors): send_egg_uploads_deprecated_initial_email( From a9a81af320e84db74ef6315f71e5bcaaaa4944a3 Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Mon, 26 Jun 2023 07:51:20 -0400 Subject: [PATCH 10/13] make sure we don't double tap maintainers if someone has a direct Role and a TeamRole we don't want to bother them twice --- tests/unit/forklift/test_legacy.py | 2 ++ warehouse/forklift/legacy.py | 2 +- warehouse/packaging/tasks.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py index 67f1864a6025..b99a4113925b 100644 --- a/tests/unit/forklift/test_legacy.py +++ b/tests/unit/forklift/test_legacy.py @@ -3761,6 +3761,8 @@ def test_egg_upload_sends_pep_715_notice_org_roles( ) team = TeamFactory.create(organization=org) TeamRoleFactory.create(team=team, user=org_member) + # Duplicate the role directly on the project to ensure only one email + RoleFactory.create(user=org_member, project=project, role_name="Maintainer") TeamProjectRoleFactory.create(project=project, team=team) pyramid_config.testing_securitypolicy(identity=user) diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py index 724b06f1385c..f07515f055cd 100644 --- a/warehouse/forklift/legacy.py +++ b/warehouse/forklift/legacy.py @@ -1483,7 +1483,7 @@ def file_upload(request): for teamrole in project.team_project_roles: contributors += teamrole.team.members - for contributor in sorted(contributors): + for contributor in sorted(set(contributors)): send_egg_uploads_deprecated_email( request, contributor, diff --git a/warehouse/packaging/tasks.py b/warehouse/packaging/tasks.py index 749ee37c1d6c..0502c915fa66 100644 --- a/warehouse/packaging/tasks.py +++ b/warehouse/packaging/tasks.py @@ -530,7 +530,7 @@ def send_pep_715_notices(request): for teamrole in project.team_project_roles: contributors += teamrole.team.members - for contributor in sorted(contributors): + for contributor in sorted(set(contributors)): send_egg_uploads_deprecated_initial_email( request, contributor, From 5a2a2bc7c264027ee2187a3ec6d473c3485c0b9e Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Mon, 26 Jun 2023 07:52:15 -0400 Subject: [PATCH 11/13] give mypy its own cache, closes #14013 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d1991a55cc0..4971ff96b201 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,7 +93,7 @@ jobs: with: path: | .mypy_cache - key: ${{ runner.os }}-${{ env.pythonLocation }}-${{ hashFiles('requirements.txt', 'requirements/*.txt') }} + key: ${{ runner.os }}-mypy-${{ env.pythonLocation }}-${{ hashFiles('requirements.txt', 'requirements/*.txt') }} - name: Restore built Python environment from deps uses: actions/cache/restore@v3 with: From 90c1eb29bc2ac584e52093c74622be9b95b22c4c Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Mon, 26 Jun 2023 08:17:11 -0400 Subject: [PATCH 12/13] blog --- .../posts/2023-06-26-deprecate-egg-uploads.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs/blog/posts/2023-06-26-deprecate-egg-uploads.md diff --git a/docs/blog/posts/2023-06-26-deprecate-egg-uploads.md b/docs/blog/posts/2023-06-26-deprecate-egg-uploads.md new file mode 100644 index 000000000000..c124e3412cd3 --- /dev/null +++ b/docs/blog/posts/2023-06-26-deprecate-egg-uploads.md @@ -0,0 +1,37 @@ +--- +title: Deprecation of bdist_egg uploads to PyPI +description: PyPI will stop accepting .egg uploads August 1, 2023. +author: Ee Durbin +publish_date: 2023-06-26 +date: "2023-06-26 00:00" +tags: + - deprecation +--- + +[PEP 715](https://peps.python.org/pep-0715/), deprecating `bdist_egg`/`.egg` +uploads to PyPI has been +[accepted](https://discuss.python.org/t/pep-715-disabling-bdist-egg-distribution-uploads-on-pypi/27610/13). +We'll begin the process of implementing this today. + +Please note that this does **NOT** remove any existing uploaded eggs from PyPI. + +The deprecation timeline is as follows: + +- Today, June 26, 2023: All maintainers of projects which have uploaded one or + more eggs since January 1, 2023 will recieve a one-time email informing them + of this change. +- Today, June 26, 2023: Each upload of an egg to PyPI will result in a notice + being sent to all Owners and Maintainers for the project. +- August 1, 2023: Uploads of eggs will be **rejected** by PyPI. + +You can read more detailed rationale in [PEP 715](https://peps.python.org/pep-0715/#rationale). +Thanks to contributor [William Woodruff](https://blog.yossarian.net) for his +work to author and propose PEP 715, as well as support the rollout of the +implementation. + +--- + +_Ee Durbin is the Director of Infrastructure at +the Python Software Foundation. +They have been contributing to keeping PyPI online, available, and +secure since 2013._ From 8313ee5e8b6cdc0a470afd59ea5bdc7b7b712334 Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Mon, 26 Jun 2023 11:22:38 -0400 Subject: [PATCH 13/13] Update docs/blog/posts/2023-06-26-deprecate-egg-uploads.md Co-authored-by: William Woodruff --- docs/blog/posts/2023-06-26-deprecate-egg-uploads.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/blog/posts/2023-06-26-deprecate-egg-uploads.md b/docs/blog/posts/2023-06-26-deprecate-egg-uploads.md index c124e3412cd3..5ba32e61f919 100644 --- a/docs/blog/posts/2023-06-26-deprecate-egg-uploads.md +++ b/docs/blog/posts/2023-06-26-deprecate-egg-uploads.md @@ -22,7 +22,7 @@ The deprecation timeline is as follows: of this change. - Today, June 26, 2023: Each upload of an egg to PyPI will result in a notice being sent to all Owners and Maintainers for the project. -- August 1, 2023: Uploads of eggs will be **rejected** by PyPI. +- August 1, 2023: Uploads of eggs will be [**rejected**](https://www.youtube.com/watch?v=XNyUALnj8V0) by PyPI. You can read more detailed rationale in [PEP 715](https://peps.python.org/pep-0715/#rationale). Thanks to contributor [William Woodruff](https://blog.yossarian.net) for his