Skip to content

Commit 9a52460

Browse files
authored
Merge pull request #209 from maykinmedia/chore/208-type-checking
🏷️ [#208] Configure type checking with pyright
2 parents f54907d + 7c5dedc commit 9a52460

File tree

9 files changed

+66
-27
lines changed

9 files changed

+66
-27
lines changed

.github/workflows/code_quality.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,23 @@ jobs:
4040
python-version: '3.12'
4141
- name: Check environment variables
4242
run: python bin/check_envvar_usage.py
43+
44+
type-checking:
45+
name: Type checking (Pyright)
46+
runs-on: ubuntu-latest
47+
48+
steps:
49+
- uses: actions/checkout@v6
50+
- uses: actions/setup-python@v6
51+
with:
52+
python-version: '3.12'
53+
- name: Install additional dependencies
54+
run: |
55+
pip install uv
56+
uv pip install \
57+
--system \
58+
-e .[type-checking,tests,cors,csp,structlog]
59+
- uses: jakebailey/pyright-action@v1
60+
with:
61+
version: 1.1.407
62+
project: pyright.pyproject.toml

open_api_framework/admin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ def has_change_permission(self, request, obj=None):
2525

2626
@admin.display(boolean=True)
2727
def exists(self, obj):
28-
return self.SessionStore().exists(obj.session_key)
28+
return self.SessionStore().exists(obj.session_key) # pyright: ignore
2929

3030
def delete_model(self, request, obj):
31-
self.SessionStore(obj.session_key).flush()
31+
self.SessionStore(obj.session_key).flush() # pyright: ignore
3232
super().delete_model(request, obj)
3333

3434
def delete_queryset(self, request, queryset):
3535
for session_profile in queryset.iterator():
36-
self.SessionStore(session_profile.session_key).flush()
36+
self.SessionStore(session_profile.session_key).flush() # pyright: ignore
3737

3838
super().delete_queryset(request, queryset)

open_api_framework/conf/base.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import warnings
44
from contextlib import suppress
55
from importlib.util import find_spec
6+
from typing import TYPE_CHECKING, Any
67

78
from django.urls import reverse_lazy
89

@@ -18,6 +19,10 @@
1819
strip_protocol_from_origin,
1920
)
2021

22+
if TYPE_CHECKING:
23+
from psycopg import Connection
24+
from psycopg.rows import TupleRow
25+
2126
# optional requirements
2227
default_cors_headers = []
2328
with suppress(ImportError):
@@ -293,8 +298,8 @@ class WrapperConnectionClass(psycopg.Connection):
293298
def connect(
294299
cls,
295300
conninfo: str = "",
296-
**kwargs,
297-
) -> "psycopg.Connection[psycopg.rows.TupleRow]":
301+
**kwargs: Any,
302+
) -> Connection[TupleRow]:
298303
return psycopg.connect(conninfo, **kwargs)
299304

300305
DATABASES["default"]["OPTIONS"] = {
@@ -365,7 +370,7 @@ def connect(
365370
#
366371
# APPLICATIONS enabled for this project
367372
#
368-
INSTALLED_APPS = importable(
373+
INSTALLED_APPS: list[str] = importable(
369374
# Note: contenttypes should be first, see Django ticket #10827
370375
"django.contrib.contenttypes",
371376
"django.contrib.auth",
@@ -1423,7 +1428,7 @@ def connect(
14231428
# NOTE: make sure values are a tuple or list, and to quote special values like 'self'
14241429

14251430

1426-
def get_content_security_policy():
1431+
def get_content_security_policy() -> dict[str, dict[str, list[str] | int]]:
14271432
# ideally we'd use BASE_URI but it'd have to be lazy or cause issues
14281433
extra_default_src = config(
14291434
"CSP_EXTRA_DEFAULT_SRC",
@@ -1483,7 +1488,7 @@ def get_content_security_policy():
14831488
if not csp_installed:
14841489
return {}
14851490

1486-
csp_default_src = [SELF] + extra_default_src
1491+
csp_default_src = [SELF] + extra_default_src # pyright: ignore
14871492
return {
14881493
"DIRECTIVES": {
14891494
"default-src": csp_default_src,
@@ -1492,13 +1497,13 @@ def get_content_security_policy():
14921497
"img-src": csp_default_src + ["data:", "cdn.redoc.ly"] + extra_img_src,
14931498
"object-src": object_src,
14941499
"style-src": csp_default_src
1495-
+ [NONCE, "'unsafe-inline'", "fonts.googleapis.com"],
1496-
"script-src": csp_default_src + [NONCE, "'unsafe-inline'"],
1497-
"font-src": [SELF, "fonts.gstatic.com"],
1498-
"worker-src": [SELF, "blob:"],
1499-
"base-uri": [SELF],
1500-
"frame-ancestors": [NONE],
1501-
"frame-src": [SELF],
1500+
+ [NONCE, "'unsafe-inline'", "fonts.googleapis.com"], # pyright: ignore
1501+
"script-src": csp_default_src + [NONCE, "'unsafe-inline'"], # pyright: ignore
1502+
"font-src": [SELF, "fonts.gstatic.com"], # pyright: ignore
1503+
"worker-src": [SELF, "blob:"], # pyright: ignore
1504+
"base-uri": [SELF], # pyright: ignore
1505+
"frame-ancestors": [NONE], # pyright: ignore
1506+
"frame-src": [SELF], # pyright: ignore
15021507
"upgrade-insecure-requests": False, # Enable only in production
15031508
"report-uri": report_uri,
15041509
},

open_api_framework/conf/utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,18 @@ def __eq__(self, other):
3131
return isinstance(other, EnvironmentVariable) and self.name == other.name
3232

3333

34-
ENVVAR_REGISTRY = []
34+
ENVVAR_REGISTRY: list[EnvironmentVariable] = []
3535

3636
_T = TypeVar("_T")
3737

3838

3939
def config(
4040
option: str,
4141
default: _T = undefined,
42-
help_text="",
43-
group=None,
42+
help_text: str = "",
43+
group: str | None = None,
4444
add_to_docs: str | bool = True,
45-
auto_display_default=True,
45+
auto_display_default: bool = True,
4646
*args,
4747
**kwargs,
4848
) -> _T:
@@ -158,7 +158,7 @@ def strip_protocol_from_origin(origin: str) -> str:
158158

159159

160160
def get_project_dirname() -> str:
161-
return config("DJANGO_SETTINGS_MODULE", add_to_docs=False).split(".")[0]
161+
return config("DJANGO_SETTINGS_MODULE", add_to_docs=False).split(".")[0] # pyright: ignore
162162

163163

164164
def get_django_project_dir() -> Path:

open_api_framework/context_processors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
from django.http.request import HttpRequest
33

44

5-
def admin_settings(request: HttpRequest) -> dict:
6-
show_version = request.user.is_staff
5+
def admin_settings(request: HttpRequest) -> dict[str, str | bool | None]:
6+
show_version = request.user.is_staff # pyright: ignore
77

88
return {
99
"show_environment": getattr(settings, "ENVIRONMENT_SHOWN_IN_ADMIN", None),

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ tests = [
6363
"django-rosetta",
6464
"django-webtest"
6565
]
66+
type-checking = [
67+
"decouple-types",
68+
"django-stubs",
69+
"django-stubs-ext",
70+
"gitpython",
71+
]
6672
coverage = [
6773
"pytest-cov",
6874
]
@@ -146,3 +152,6 @@ exclude_also = [
146152
"\\.\\.\\.",
147153
"pass",
148154
]
155+
156+
[tool.setuptools.package-data]
157+
open_api_framework = ["py.typed"]

pyright.pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[tool.pyright]
2+
pythonVersion = "3.12"
3+
pythonPlatform = "Linux"
4+
reportUnnecessaryTypeIgnoreComment = "error"

tests/factories.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import factory.fuzzy
2+
from factory.helpers import post_generation
23
from sessionprofile.models import SessionProfile
34

45
from open_api_framework.utils import get_session_store
56

67

7-
class SessionProfileFactory(factory.django.DjangoModelFactory):
8+
class SessionProfileFactory(factory.django.DjangoModelFactory[SessionProfile]):
89
session_key = factory.fuzzy.FuzzyText(length=40)
910

10-
class Meta:
11+
class Meta: # pyright: ignore
1112
model = SessionProfile
1213

13-
@factory.post_generation
14+
@post_generation
1415
def session(self, create, extracted, **kwargs):
1516
SessionStore = get_session_store()
16-
SessionStore(self.session_key).save(True)
17+
SessionStore(self.session_key).save(True) # pyright: ignore

tests/test_admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def test_cant_delete_other_users_session(client, admin_user, django_user_model):
8787

8888
SessionStore = get_session_store()
8989

90-
assert SessionStore().exists(other_user_session.session_key)
90+
assert SessionStore().exists(other_user_session.session_key) # pyright: ignore
9191

9292

9393
def test_delete_with_session_db_backend(client, admin_user, session_changelist_url):

0 commit comments

Comments
 (0)