Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/lint
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ sphinx-lint --enable=all --disable=line-too-long README.rst CONTRIBUTING.rst doc
python -m djlint --check --lint ./warehouse/templates ./docs/blog
python -m mypy -p warehouse
./bin/flushes
codespell
2 changes: 2 additions & 0 deletions bin/reformat
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -ex

# Fix typos before other changes, as they may affect formatting.
codespell --write
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am -1 on this. seems it could be a footgun if an undesirable change to spelling is automatically written.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to remove, whenever there is an ambiguity of spelling, codespell won't change it, rather continue to error out during a lint phase.

Happy to remove if you think this is a serious footgun, but otherwise it felt appropriate to help the user similar to other reformat directives.

find . -name '*.py' -exec python -m pyupgrade --py313-plus {} +
python -m isort *.py warehouse/ tests/
python -m black *.py warehouse/ tests/
Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/2023-09-18-inbound-malware-reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Again, since the nature of email isn't 100% accurate in this case,
we'll rely on calculating the duration of time (in minutes)
between the first message of a thread and the last message of a thread.
This doesn't account for the occasional behavior of a reporter
re-using the same thread to report more packages,
reusing the same thread to report more packages,
nor does it reflect any other back-and-forth communication
between admins and reporters.
As such, removing any threads that have more than 4 total messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ with the added benefit of making it easier for others to contribute.
As well as "routine" work - handling inbound reports, analysis, and response.

I also attended some conferences, round tables, and meetings, including
Fastly Altitude 2023, AWS re:Invent 2023, PyCon US 2024, and AWS re:Inforce 2024,
Fastly Altitude 2023, AWS re:Invent 2023, PyCon US 2024, and AWS re:Inforce 2024, <!-- codespell:ignore -->
amongst some.
I've also been getting more involved with the
[OpenSSF](https://openssf.org/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ tools was performed by [Trail of Bits], with special thanks to contributors
Thanks to the the [Sigstore project] for their work popularizing identity-based signing, hosting a public-good transparency log, and continued support of the [Python client for Sigstore].

Many thanks to [Sviatoslav Sydorenko] as well for his support and ongoing
maintenence of the [pypa/gh-action-pypi-publish] action, as well his support
maintenance of the [pypa/gh-action-pypi-publish] action, as well his support
for implementing PEP 740 in the action.

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ If you are publishing software to PyPI then you can harden your build and publis
In addition to the specific recommendations above, we strongly recommend general account security best practices such as:

* **Use 2FA / MFA, preferably using a hardware key or authenticator app for all accounts associated with open source contributions.** This includes your email address and accounts for source forge(s) like GitHub or GitLab. Avoid SMS and text-message-based 2FA schemes if possible, as they are susceptible to SIM-swapping. PyPI already requires the use of 2FA to publish packages.
* **Don’t reuse passwords, use a password manager.** Re-using passwords for services means that a compromise to one service will compromise your account(s) elsewhere.
* **Don’t reuse passwords, use a password manager.** Reusing passwords for services means that a compromise to one service will compromise your account(s) elsewhere.

Prevention is important, but just as important is preparedness. Here’s what to do if your own project is compromised:

Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/2025-08-14-project-status-markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ which have both index-side and installer-side semantics:
will not offer it for installation, and installers are encouraged to
produce a warning when users attempt to install it[^warning].
* **deprecated**: Indicates that the project is considered obsolete,
and may have been superceded by another project. Unlike archived projects,
and may have been superseded by another project. Unlike archived projects,
deprecated projects can still be uploaded to, but installers are encouraged
to inform users about the project's deprecation.

Expand Down
2 changes: 1 addition & 1 deletion docs/dev/development/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ we have a common Key set in the database for those users:
This will emit a 6-digit code you can paste into the 2FA form.

For other accounts, you'll need to preserve the Key used
to genreate the TOTP code the next time you need to log in.
to generate the TOTP code the next time you need to log in.

To be able to "forget" the initial Key, and use it like a TOTP app,
create a storage and set a password, like so:
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/development/token-scanning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ How to test it manually
^^^^^^^^^^^^^^^^^^^^^^^

A fake token reporting service is launched by Docker Compose. Head your browser to
``http://localhost:8964``. Create/reorder/... one ore more public keys, make
``http://localhost:8964``. Create/reorder/... one or more public keys, make
sure one key is marked as current, then write your payload, using the following
format:

Expand Down
2 changes: 1 addition & 1 deletion docs/user/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def wheel_url(name, version, build_tag, python_tag, abi_tag, platform_tag):
return f'{host}/packages/{python_tag}/{name[0]}/{name}/{filename}'
```

Example predicable URL use:
Example predictable URL use:

```bash
$ curl -I https://files.pythonhosted.org/packages/source/v/virtualenv/virtualenv-15.2.0.tar.gz
Expand Down
2 changes: 1 addition & 1 deletion docs/user/trusted-publishers/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ endpoint:
as configured when the publisher was configured on PyPI.
* `invalid-publisher` for a previously-working project: this usually indicates
a typo or that something has changed on either side. One example we've seen
is when a source repository is renamed, and the configration on PyPI
is when a source repository is renamed, and the configuration on PyPI
continues to use the old repository name. For GitHub, check that the
`repository_owner`, `repository` and workflow filename values are the same on
both sides.
Expand Down
17 changes: 17 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,20 @@ filterwarnings = [
# TODO: This can be removed once it becomes the default.
# See: https://github.com/jazzband/pip-tools/issues/989
allow-unsafe = true

[tool.codespell]
ignore-words-list = "deriver"
skip = [
'*.po',
'./dev/.mypy_cache/*',
'./dev/.pytest_cache/*',
'./docs/blog-site/*',
'./docs/dev/_build/*',
'./docs/user-site/*',
'./htmlcov/*',
'./node_modules/**',
'./package-lock.json',
'./warehouse/admin/static/dist/*',
'./warehouse/static/dist/*',
'./warehouse/static/js/vendor/zxcvbn.js',
]
1 change: 1 addition & 0 deletions requirements/lint.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
codespell
djlint
flake8
flake8-pytest-style
Expand Down
4 changes: 4 additions & 0 deletions requirements/lint.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ click==8.2.1 \
# via
# black
# djlint
codespell==2.4.1 \
--hash=sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5 \
--hash=sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425
# via -r requirements/lint.in
colorama==0.4.6 \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ def xmlrpc(self, path, method, *args):

@pytest.fixture
def tm():
# Create a new transaction manager for dependant test cases
# Create a new transaction manager for dependent test cases
tm = transaction.TransactionManager(explicit=True)
tm.begin()

Expand Down
2 changes: 1 addition & 1 deletion tests/frontend/delete_confirm_controller_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("DeleteConfirm controller", () => {

describe("functionality", function() {
describe("checking one box", function() {
it("doesnt enable the button", function() {
it("doesn't enable the button", function() {
const inputOne = document.getElementById("input-one");
expect(inputOne).not.toBeChecked();
fireEvent.click(inputOne);
Expand Down
4 changes: 2 additions & 2 deletions tests/frontend/horizontal_tabs_controller_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe("Horizontal tabs controller", () => {
application.register("horizontal-tabs", HorizontalTabsController);
});

describe("on initializtion", () => {
describe("on initialization", () => {
it("the first tab is shown", () => {
const tabs = document.querySelectorAll(".tab");
const content = document.querySelectorAll(".horizontal-tabs__tabcontent");
Expand Down Expand Up @@ -76,7 +76,7 @@ describe("Horizontal tabs controller", () => {
});
});

describe("on initializtion with errors", () => {
describe("on initialization with errors", () => {
beforeEach(() => {
// Add some errors to the second tab
const secondTabPanel = document.querySelectorAll(".horizontal-tabs__tabcontent")[1];
Expand Down
2 changes: 1 addition & 1 deletion tests/frontend/password_breach_controller_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe("Password breach controller", () => {
describe("entering a password with less than 3 characters", () => {
it("does not call the HIBP API", async () => {
const passwordField = document.querySelector("#password");
fireEvent.input(passwordField, { target: { value: "fo" } });
fireEvent.input(passwordField, { target: { value: "of" } });

await delay(25); // arbitrary number of ms, too low may cause failures
expect(fetch.mock.calls.length).toEqual(0);
Expand Down
2 changes: 1 addition & 1 deletion tests/frontend/viewport_toggle_controller_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const viewportContent = `


function startStimulus() {
// set the HTML before satarting the application, as the controller uses the
// set the HTML before starting the application, as the controller uses the
// `connect()` function.
document.body.innerHTML = viewportContent;
const application = Application.start();
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/admin/views/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def test_validate(self):
class TestUserForm:
def test_validate(self):
form = views.UserForm()
assert form.validate(), str(form.erros)
assert form.validate(), str(form.errors)


class TestUserDetail:
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/api/test_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def test_no_files_with_serial(self, db_request, content_type, renderer_override)
db_request.matchdict["name"] = project.normalized_name
user = UserFactory.create()
je = JournalEntryFactory.create(name=project.name, submitted_by=user)
als = [
alts = [
AlternateRepositoryFactory.create(project=project),
AlternateRepositoryFactory.create(project=project),
]
Expand All @@ -243,7 +243,7 @@ def test_no_files_with_serial(self, db_request, content_type, renderer_override)
"project-status": {"status": "active"},
"files": [],
"versions": [],
"alternate-locations": sorted(al.url for al in als),
"alternate-locations": sorted(al.url for al in alts),
}
context = _update_context(context, content_type, renderer_override)
assert simple.simple_detail(project, db_request) == context
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/email/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def test_send(self, sender_class):
service = sender_class(mailer, sender="DevPyPI <[email protected]>")

service.send(
"sombody@example.com",
"somebody@example.com",
EmailMessage(
subject="a subject", body_text="a body", body_html="a html body"
),
Expand All @@ -142,7 +142,7 @@ def test_send(self, sender_class):
assert msg.subject == "a subject"
assert msg.body == "a body"
assert msg.html == "a html body"
assert msg.recipients == ["sombody@example.com"]
assert msg.recipients == ["somebody@example.com"]
assert msg.sender == "DevPyPI <[email protected]>"

def test_last_sent(self, sender_class):
Expand All @@ -160,7 +160,7 @@ def test_send(self, capsys):
)

service.send(
"sombody@example.com",
"somebody@example.com",
EmailMessage(
subject="a subject",
body_text="a body",
Expand All @@ -172,7 +172,7 @@ def test_send(self, capsys):
Email sent
Subject: a subject
From: DevPyPI <[email protected]>
To: sombody@example.com
To: somebody@example.com
HTML: Visualize at http://localhost:1080
Text: a body"""
assert captured.out.strip() == expected.strip()
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/events/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class TestUserAgentInfo:
),
(
(
"Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 "
"Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 " # codespell:ignore # noqa: E501
"(KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36"
),
{
Expand Down
6 changes: 4 additions & 2 deletions tests/unit/forklift/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def test_invalid_classifier(self):
def test_deprecated_classifiers_with_replacement(self, backfill):
data = (
b"Metadata-Version: 2.1\nName: spam\nVersion: 2.0\n"
b"Classifier: Natural Language :: Ukranian\n"
b"Classifier: Natural Language :: Ukranian\n" # codespell:ignore
)

if not backfill:
Expand All @@ -157,7 +157,9 @@ def test_deprecated_classifiers_with_replacement(self, backfill):
_assert_invalid_metadata(excinfo.value, "classifier")
else:
meta = metadata.parse(data, backfill=True)
assert meta.classifiers == ["Natural Language :: Ukranian"]
assert meta.classifiers == [
"Natural Language :: Ukranian" # codespell:ignore
]

@pytest.mark.parametrize("backfill", [True, False])
def test_deprecated_classifiers_no_replacement(self, backfill):
Expand Down
9 changes: 5 additions & 4 deletions tests/unit/integration/secrets/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,27 @@ def test_invalid_token_leak_request():
(
{"type": "not_found", "token": "a", "url": "b"},
"Matcher with code not_found not found. "
"Available codes are: failer, pypi_api_token",
"Available codes are: failure, pypi_api_token",
"invalid_matcher",
),
(
{"type": "failer", "token": "a", "url": "b"},
{"type": "failure", "token": "a", "url": "b"},
"Cannot extract token from received match",
"extraction",
),
],
)
def test_token_leak_disclosure_request_from_api_record_error(record, error, reason):
class MyFailingMatcher(utils.TokenLeakMatcher):
name = "failer"
name = "failure"

def extract(self, text):
raise utils.ExtractionFailedError()

with pytest.raises(utils.InvalidTokenLeakRequestError) as exc:
utils.TokenLeakDisclosureRequest.from_api_record(
record, matchers={"failer": MyFailingMatcher(), **utils.TOKEN_LEAK_MATCHERS}
record,
matchers={"failure": MyFailingMatcher(), **utils.TOKEN_LEAK_MATCHERS},
)

assert str(exc.value) == error
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/integration/vulnerabilities/osv/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ def find_service(self, *a, **k):
assert response.status_int == 400
assert metrics.increment.calls == [
pretend.call(
"warehouse.vulnerabilties.error.payload.json_error", tags=["origin:osv"]
"warehouse.vulnerabilities.error.payload.json_error",
tags=["origin:osv"],
)
]

Expand Down
10 changes: 5 additions & 5 deletions tests/unit/macaroons/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ def test_find_from_raw_oidc(self, macaroon_service):
[
"pypi-aaaa", # Invalid macaroon
# Macaroon properly formatted but not found.
# The string is purposedly cut to avoid triggering the github token
# The string is purposely cut to avoid triggering the github token
# disclosure feature that this very function implements.
"py"
"pi-AgEIcHlwaS5vcmcCJGQ0ZDhhNzA2LTUxYTEtNDg0NC1hNDlmLTEyZDRiYzNkYjZmOQAABi"
"D6hJOpYl9jFI4jBPvA8gvV1mSu1Ic3xMHmxA4CSA2w_g",
# Macaroon that is malformed and has an invaild (non utf-8) identifier
# The string is purposedly cut to avoid triggering the github token
# Macaroon that is malformed and has an invalid (non utf-8) identifier
# The string is purposely cut to avoid triggering the github token
# disclosure feature that this very function implements.
"py"
"pi-MDAwZWxvY2F0aW9uIAowMDM0aWRlbnRpZmllciBhmTAyMWY0YS0xYWQzLTQ3OGEtYjljZi1"
Expand Down Expand Up @@ -145,8 +145,8 @@ def test_find_userid_invalid_macaroon(self, macaroon_service):
"raw_macaroon",
[
"pypi-thiswillnotdeserialize",
# Macaroon that is malformed and has an invaild (non utf-8) identifier
# The string is purposedly cut to avoid triggering the github token
# Macaroon that is malformed and has an invalid (non utf-8) identifier
# The string is purposely cut to avoid triggering the github token
# disclosure feature that this very function implements.
"py"
"pi-MDAwZWxvY2F0aW9uIAowMDM0aWRlbnRpZmllciBhmTAyMWY0YS0xYWQzLTQ3OGEtYjljZi1"
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/oidc/forms/test_activestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def test_lookup_actor_404(self, monkeypatch):
)
requests = pretend.stub(
post=pretend.call_recorder(lambda o, **kw: response),
expception=_requests.exceptions,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems odd to me that this doesn't change the behavior of this test

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for noting. Looks like the assertions never care about this specific property, so I can remove these from being set, if they have no impact. I'll remove them completely in a following commit.

exception=_requests.exceptions,
Timeout=Timeout,
HTTPError=HTTPError,
ConnectionError=ConnectionError,
Expand Down Expand Up @@ -160,7 +160,7 @@ def test_lookup_actor_other_http_error(self, monkeypatch):
)
requests = pretend.stub(
post=pretend.call_recorder(lambda o, **kw: response),
expception=_requests.exceptions,
exception=_requests.exceptions,
Timeout=Timeout,
HTTPError=HTTPError,
ConnectionError=ConnectionError,
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/oidc/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ def test_is_from_reusable_workflow(
# configured when claims contain an environment
(GitHubPublisherFactory, "", "new_env", True),
(GitLabPublisherFactory, "", "new_env", True),
# Should not send if claims don't have an environent
# Should not send if claims don't have an environment
(GitHubPublisherFactory, "", "", False),
(GitLabPublisherFactory, "", "", False),
# Should not send if publishers already have an environment
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/utils/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class TestIsSafeUrl:
"http://example.com",
"http:///example.com",
"https://example.com",
"ftp://exampel.com",
"ftp://example.com",
r"\\example.com",
r"\\\example.com",
r"/\\/example.com",
Expand All @@ -41,7 +41,7 @@ def test_rejects_bad_url(self, url):
[
"/view/?param=http://example.com",
"/view/?param=https://example.com",
"/view?param=ftp://exampel.com",
"/view?param=ftp://example.com",
"https://testserver/",
"HTTPS://testserver/",
"//testserver/",
Expand Down
Loading