forked from pypi/warehouse
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sync PSF sponsors with logo placement API (pypi#10766)
* Add new config variables to integrate with pythondotorg * Add new fields with data from pythondotorg logo placement API * Register task to update sponsors table * Code linter * Fix typo * Minimal working code to integrate with logo palcement endpoint * First unit test to figure out how to mock requests * Create a new sponsor * Add logic to update existing sponsors * Reformat code * Leave HTTP vs HTTPS configuration to be handle by env variable * Fix linter errors * Create sponsor directly from the exception handler * Run task every 10 minutes * There's no need for the if condition * Only schedule cron job if there's a token in the env * Remove pythondotorg env variables from dev * Run linter Co-authored-by: Ee Durbin <ewdurbin@gmail.com>
- Loading branch information
1 parent
04a0c46
commit 12e3337
Showing
9 changed files
with
393 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
# 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. | ||
from urllib.parse import urlencode | ||
|
||
import pretend | ||
import pytest | ||
|
||
from requests.exceptions import HTTPError | ||
|
||
from warehouse.sponsors import tasks | ||
from warehouse.sponsors.models import Sponsor | ||
|
||
from ...common.db.sponsors import SponsorFactory | ||
|
||
|
||
@pytest.fixture | ||
def fake_task_request(): | ||
cfg = { | ||
"pythondotorg.host": "https://API_HOST", | ||
"pythondotorg.api_token": "API_TOKEN", | ||
} | ||
request = pretend.stub(registry=pretend.stub(settings=cfg)) | ||
return request | ||
|
||
|
||
@pytest.fixture | ||
def sponsor_api_data(): | ||
return [ | ||
{ | ||
"publisher": "pypi", | ||
"flight": "sponsors", | ||
"sponsor": "Sponsor Name", | ||
"sponsor_slug": "sponsor-name", | ||
"description": "Sponsor description", | ||
"logo": "https://logourl.com", | ||
"start_date": "2021-02-17", | ||
"end_date": "2022-02-17", | ||
"sponsor_url": "https://sponsor.example.com/", | ||
"level_name": "Partner", | ||
"level_order": 5, | ||
} | ||
] | ||
|
||
|
||
def test_raise_error_if_invalid_response(monkeypatch, db_request, fake_task_request): | ||
response = pretend.stub( | ||
status_code=418, | ||
text="I'm a teapot", | ||
raise_for_status=pretend.raiser(HTTPError), | ||
) | ||
requests = pretend.stub(get=pretend.call_recorder(lambda url, headers: response)) | ||
monkeypatch.setattr(tasks, "requests", requests) | ||
|
||
with pytest.raises(HTTPError): | ||
tasks.update_pypi_sponsors(fake_task_request) | ||
|
||
qs = urlencode({"publisher": "pypi", "flight": "sponsors"}) | ||
headers = {"Authorization": "Token API_TOKEN"} | ||
expected_url = f"https://API_HOST/api/v2/sponsors/logo-placement/?{qs}" | ||
assert requests.get.calls == [pretend.call(expected_url, headers=headers)] | ||
|
||
|
||
def test_create_new_sponsor_if_no_matching( | ||
monkeypatch, db_request, fake_task_request, sponsor_api_data | ||
): | ||
response = pretend.stub( | ||
raise_for_status=lambda: None, json=lambda: sponsor_api_data | ||
) | ||
requests = pretend.stub(get=pretend.call_recorder(lambda url, headers: response)) | ||
monkeypatch.setattr(tasks, "requests", requests) | ||
assert 0 == len(db_request.db.query(Sponsor).all()) | ||
|
||
fake_task_request.db = db_request.db | ||
tasks.update_pypi_sponsors(fake_task_request) | ||
|
||
db_sponsor = db_request.db.query(Sponsor).one() | ||
assert "sponsor-name" == db_sponsor.slug | ||
assert "Sponsor Name" == db_sponsor.name | ||
assert "Sponsor description" == db_sponsor.service | ||
assert "https://sponsor.example.com/" == db_sponsor.link_url | ||
assert "https://logourl.com" == db_sponsor.color_logo_url | ||
assert db_sponsor.activity_markdown is None | ||
assert db_sponsor.white_logo_url is None | ||
assert db_sponsor.is_active is True | ||
assert db_sponsor.psf_sponsor is True | ||
assert db_sponsor.footer is False | ||
assert db_sponsor.infra_sponsor is False | ||
assert db_sponsor.one_time is False | ||
assert db_sponsor.sidebar is False | ||
assert "remote" == db_sponsor.origin | ||
assert "Partner" == db_sponsor.level_name | ||
assert 5 == db_sponsor.level_order | ||
|
||
|
||
def test_update_remote_sponsor_with_same_name_with_new_logo( | ||
monkeypatch, db_request, fake_task_request, sponsor_api_data | ||
): | ||
response = pretend.stub( | ||
raise_for_status=lambda: None, json=lambda: sponsor_api_data | ||
) | ||
requests = pretend.stub(get=pretend.call_recorder(lambda url, headers: response)) | ||
monkeypatch.setattr(tasks, "requests", requests) | ||
created_sponsor = SponsorFactory.create( | ||
name=sponsor_api_data[0]["sponsor"], | ||
psf_sponsor=True, | ||
footer=False, | ||
sidebar=False, | ||
one_time=False, | ||
origin="manual", | ||
) | ||
|
||
fake_task_request.db = db_request.db | ||
tasks.update_pypi_sponsors(fake_task_request) | ||
|
||
assert 1 == len(db_request.db.query(Sponsor).all()) | ||
db_sponsor = db_request.db.query(Sponsor).one() | ||
assert db_sponsor.id == created_sponsor.id | ||
assert "sponsor-name" == db_sponsor.slug | ||
assert "Sponsor description" == db_sponsor.service | ||
assert "https://sponsor.example.com/" == db_sponsor.link_url | ||
assert "https://logourl.com" == db_sponsor.color_logo_url | ||
assert db_sponsor.activity_markdown is created_sponsor.activity_markdown | ||
assert db_sponsor.white_logo_url is created_sponsor.white_logo_url | ||
assert db_sponsor.is_active is True | ||
assert db_sponsor.psf_sponsor is True | ||
assert db_sponsor.footer is False | ||
assert db_sponsor.infra_sponsor is False | ||
assert db_sponsor.one_time is False | ||
assert db_sponsor.sidebar is False | ||
assert "remote" == db_sponsor.origin | ||
assert "Partner" == db_sponsor.level_name | ||
assert 5 == db_sponsor.level_order | ||
|
||
|
||
def test_do_not_update_if_not_psf_sponsor( | ||
monkeypatch, db_request, fake_task_request, sponsor_api_data | ||
): | ||
response = pretend.stub( | ||
raise_for_status=lambda: None, json=lambda: sponsor_api_data | ||
) | ||
requests = pretend.stub(get=pretend.call_recorder(lambda url, headers: response)) | ||
monkeypatch.setattr(tasks, "requests", requests) | ||
infra_sponsor = SponsorFactory.create( | ||
name=sponsor_api_data[0]["sponsor"], | ||
psf_sponsor=False, | ||
infra_sponsor=True, | ||
one_time=False, | ||
origin="manual", | ||
) | ||
|
||
fake_task_request.db = db_request.db | ||
tasks.update_pypi_sponsors(fake_task_request) | ||
|
||
assert 1 == len(db_request.db.query(Sponsor).all()) | ||
db_sponsor = db_request.db.query(Sponsor).one() | ||
assert db_sponsor.id == infra_sponsor.id | ||
assert "manual" == db_sponsor.origin | ||
assert "sponsor-name" != db_sponsor.slug | ||
|
||
|
||
def test_update_remote_sponsor_with_same_slug_with_new_logo( | ||
monkeypatch, db_request, fake_task_request, sponsor_api_data | ||
): | ||
response = pretend.stub( | ||
raise_for_status=lambda: None, json=lambda: sponsor_api_data | ||
) | ||
requests = pretend.stub(get=pretend.call_recorder(lambda url, headers: response)) | ||
monkeypatch.setattr(tasks, "requests", requests) | ||
created_sponsor = SponsorFactory.create( | ||
slug=sponsor_api_data[0]["sponsor_slug"], | ||
psf_sponsor=True, | ||
footer=False, | ||
sidebar=False, | ||
one_time=False, | ||
origin="manual", | ||
) | ||
|
||
fake_task_request.db = db_request.db | ||
tasks.update_pypi_sponsors(fake_task_request) | ||
|
||
assert 1 == len(db_request.db.query(Sponsor).all()) | ||
db_sponsor = db_request.db.query(Sponsor).one() | ||
assert db_sponsor.id == created_sponsor.id | ||
assert "Sponsor Name" == db_sponsor.name | ||
assert "Sponsor description" == db_sponsor.service | ||
|
||
|
||
def test_flag_existing_psf_sponsor_to_false_if_not_present_in_api_response( | ||
monkeypatch, db_request, fake_task_request, sponsor_api_data | ||
): | ||
response = pretend.stub( | ||
raise_for_status=lambda: None, json=lambda: sponsor_api_data | ||
) | ||
requests = pretend.stub(get=pretend.call_recorder(lambda url, headers: response)) | ||
monkeypatch.setattr(tasks, "requests", requests) | ||
created_sponsor = SponsorFactory.create( | ||
slug="other-slug", | ||
name="Other Sponsor", | ||
psf_sponsor=True, | ||
footer=True, | ||
sidebar=True, | ||
origin="manual", | ||
) | ||
|
||
fake_task_request.db = db_request.db | ||
tasks.update_pypi_sponsors(fake_task_request) | ||
|
||
assert 2 == len(db_request.db.query(Sponsor).all()) | ||
created_sponsor = ( | ||
db_request.db.query(Sponsor).filter(Sponsor.id == created_sponsor.id).one() | ||
) | ||
# no longer PSF sponsor but stay active as sidebar/footer sponsor | ||
assert created_sponsor.psf_sponsor is False | ||
assert created_sponsor.sidebar is True | ||
assert created_sponsor.footer is True | ||
assert created_sponsor.is_active is True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
warehouse/migrations/versions/19cf76d2d459_new_sponsor_columns_to_save_data_from_.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# 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. | ||
""" | ||
New Sponsor columns to save data from pythondotorg API | ||
Revision ID: 19cf76d2d459 | ||
Revises: 29a8901a4635 | ||
Create Date: 2022-02-13 14:31:18.366248 | ||
""" | ||
|
||
import sqlalchemy as sa | ||
|
||
from alembic import op | ||
|
||
revision = "19cf76d2d459" | ||
down_revision = "29a8901a4635" | ||
|
||
# Note: It is VERY important to ensure that a migration does not lock for a | ||
# long period of time and to ensure that each individual migration does | ||
# not break compatibility with the *previous* version of the code base. | ||
# This is because the migrations will be ran automatically as part of the | ||
# deployment process, but while the previous version of the code is still | ||
# up and running. Thus backwards incompatible changes must be broken up | ||
# over multiple migrations inside of multiple pull requests in order to | ||
# phase them in over multiple deploys. | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.add_column("sponsors", sa.Column("origin", sa.String(), nullable=True)) | ||
op.add_column("sponsors", sa.Column("level_name", sa.String(), nullable=True)) | ||
op.add_column("sponsors", sa.Column("level_order", sa.Integer(), nullable=True)) | ||
op.add_column("sponsors", sa.Column("slug", sa.String(), nullable=True)) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_column("sponsors", "slug") | ||
op.drop_column("sponsors", "level_order") | ||
op.drop_column("sponsors", "level_name") | ||
op.drop_column("sponsors", "origin") | ||
# ### end Alembic commands ### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.