Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable mypy checks #381

Merged
merged 24 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 23 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on: [pull_request]
permissions: read-all

jobs:
black:
invoke_lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
31 changes: 26 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@ dev = [
"black == 23.7.*",
"isort == 5.12.*",
"coverage == 7.3.*",
"mypy == 1.5.*",
"mypy == 1.10.0",
"flask == 3.0.*",
"django-cprofile-middleware==1.0.5",
"django-silk==5.1.0",
"types-requests==2.32.*",
"types-six==1.16.0.*",
"django-stubs[compatible-mypy]==5.0.*",
"djangorestframework-stubs[compatible-mypy]==3.15.*",
"django-filter-stubs==0.1.3",
]

[project.scripts]
Expand All @@ -66,6 +69,7 @@ where = ["src"]


[tool.mypy]
plugins = ["mypy_django_plugin.main", "mypy_drf_plugin.main"]
check_untyped_defs = true
show_error_codes = true
pretty = true
Expand All @@ -74,13 +78,30 @@ disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
warn_unused_configs = true
files = [ "src/" ]
exclude = [
"^src/meshdb/utils/spreadsheet_import/.*",
"^src/meshapi/tests/.*",
"^src/meshapi_hooks/tests/.*",
"^src/meshapi/migrations/.*",
"src/meshapi/tests/.*",
"src/meshapi_hooks/tests/.*",
"src/meshapi/migrations/.*",
"src/meshdb/utils/spreadsheet_import/.*",
"src/meshapi/util/django_pglocks.py",
"src/meshapi/models/util/custom_many_to_many.py",
"src/meshapi/docs.py"
]

[[tool.mypy.overrides]]
module = [
"meshdb.utils.spreadsheet_import.*",
"meshapi.util.django_pglocks",
"meshapi.models.util.custom_many_to_many",
"meshapi.docs",
]
follow_imports = "skip"


[tool.django-stubs]
django_settings_module = "meshdb.settings"


[tool.black]
line-length = 120
Expand Down
35 changes: 15 additions & 20 deletions src/meshapi/admin/building.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from typing import Iterable

from django import forms
from django.contrib import admin
from django.contrib.admin.options import forms
from django.utils.safestring import mark_safe
from django.contrib.admin import ModelAdmin
from django.db.models import QuerySet
from django.http import HttpRequest

from meshapi.admin.inlines import InstallInline
from meshapi.models import Building
Expand All @@ -11,16 +15,16 @@ class BoroughFilter(admin.SimpleListFilter):
title = "Borough"
parameter_name = "borough"

def lookups(self, request, model_admin):
return (
("bronx", ("The Bronx")),
("manhattan", ("Manhattan")),
("brooklyn", ("Brooklyn")),
("queens", ("Queens")),
("staten_island", ("Staten Island")),
)
def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Iterable[tuple[str, str]]:
return [
("bronx", "The Bronx"),
("manhattan", "Manhattan"),
("brooklyn", "Brooklyn"),
("queens", "Queens"),
("staten_island", "Staten Island"),
]

def queryset(self, request, queryset):
def queryset(self, request: HttpRequest, queryset: QuerySet[Building]) -> QuerySet[Building]:
if self.value() == "bronx":
return queryset.filter(city="Bronx")
elif self.value() == "manhattan":
Expand Down Expand Up @@ -116,12 +120,3 @@ class BuildingAdmin(admin.ModelAdmin):
]
inlines = [InstallInline]
filter_horizontal = ("nodes",)

# This is probably a bad idea because you'll have to load a million panos
# and OOM your computer
# Need to find a way to "thumbnail-ize" them on the server side, probably.
@mark_safe
def thumb(self, obj):
return f"<img src='{obj.get_thumb()}' width='50' height='50' />"

thumb.__name__ = "Thumbnail"
8 changes: 5 additions & 3 deletions src/meshapi/admin/device.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django import forms
from django.contrib import admin
from django.contrib.admin.options import forms
from django.db.models import QuerySet
from django.http import HttpRequest

from meshapi.admin.admin import device_fieldsets
from meshapi.admin.inlines import DeviceLinkInline
Expand Down Expand Up @@ -31,10 +33,10 @@ class DeviceAdmin(admin.ModelAdmin):
"install_date",
"model",
]
fieldsets = device_fieldsets
fieldsets = device_fieldsets # type: ignore[assignment]
inlines = [DeviceLinkInline]

def get_queryset(self, request):
def get_queryset(self, request: HttpRequest) -> QuerySet[Device]:
# Get the base queryset
queryset = super().get_queryset(request)
# Filter out sectors
Expand Down
29 changes: 15 additions & 14 deletions src/meshapi/admin/inlines.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Any
from typing import Any, Optional

from django.contrib import admin
from django.db.models import Q
from django.db.models import Model, Q, QuerySet
from django.http import HttpRequest
from nonrelated_inlines.admin import NonrelatedTabularInline

from meshapi.models import Building, Device, Install, Link, Sector
from meshapi.models import Building, Device, Install, Link, Node, Sector


# Inline with the typical rules we want + Formatting
Expand All @@ -13,7 +14,7 @@ class BetterInline(admin.TabularInline):
can_delete = False
template = "admin/install_tabular.html"

def has_add_permission(self, request, obj) -> bool:
def has_add_permission(self, request: HttpRequest, obj: Optional[Any]) -> bool: # type: ignore[override]
return False

class Media:
Expand All @@ -27,10 +28,10 @@ class BetterNonrelatedInline(NonrelatedTabularInline):
can_delete = False
template = "admin/install_tabular.html"

def has_add_permission(self, request, obj) -> bool:
def has_add_permission(self, request: HttpRequest, obj: Model) -> bool:
return False

def save_new_instance(self, parent, instance):
def save_new_instance(self, parent: Any, instance: Any) -> None:
pass

class Media:
Expand All @@ -48,7 +49,7 @@ class PanoramaInline(BetterNonrelatedInline):

all_panoramas: dict[str, list[Any]] = {}

def get_form_queryset(self, obj):
def get_form_queryset(self, obj: Node) -> QuerySet[Building]:
buildings = self.model.objects.filter(nodes=obj)
panoramas = []
for b in buildings:
Expand All @@ -73,7 +74,7 @@ class NonrelatedBuildingInline(BetterNonrelatedInline):
# Hack to get the NN
network_number = None

def get_form_queryset(self, obj):
def get_form_queryset(self, obj: Node) -> QuerySet[Building]:
self.network_number = obj.pk
return self.model.objects.filter(nodes=obj)

Expand All @@ -90,9 +91,9 @@ class BuildingMembershipInline(admin.TabularInline):
class DeviceInline(BetterInline):
model = Device
fields = ["status", "type", "model"]
readonly_fields = fields
readonly_fields = fields # type: ignore[assignment]

def get_queryset(self, request):
def get_queryset(self, request: HttpRequest) -> QuerySet[Device]:
# Get the base queryset
queryset = super().get_queryset(request)
# Filter out sectors
Expand All @@ -105,7 +106,7 @@ class NodeLinkInline(BetterNonrelatedInline):
fields = ["status", "type", "from_device", "to_device"]
readonly_fields = fields

def get_form_queryset(self, obj):
def get_form_queryset(self, obj: Node) -> QuerySet[Link]:
from_device_q = Q(from_device__in=obj.devices.all())
to_device_q = Q(to_device__in=obj.devices.all())
all_links = from_device_q | to_device_q
Expand All @@ -117,7 +118,7 @@ class DeviceLinkInline(BetterNonrelatedInline):
fields = ["status", "type", "from_device", "to_device"]
readonly_fields = fields

def get_form_queryset(self, obj):
def get_form_queryset(self, obj: Link) -> QuerySet[Link]:
from_device_q = Q(from_device=obj)
to_device_q = Q(to_device=obj)
all_links = from_device_q | to_device_q
Expand All @@ -127,11 +128,11 @@ def get_form_queryset(self, obj):
class SectorInline(BetterInline):
model = Sector
fields = ["status", "type", "model"]
readonly_fields = fields
readonly_fields = fields # type: ignore[assignment]


# This controls the list of installs reverse FK'd to Buildings and Members
class InstallInline(BetterInline):
model = Install
fields = ["status", "node", "member", "building", "unit"]
readonly_fields = fields
readonly_fields = fields # type: ignore[assignment]
10 changes: 8 additions & 2 deletions src/meshapi/admin/install.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from typing import Tuple

from django import forms
from django.contrib import admin
from django.contrib.admin.options import forms
from django.db.models import QuerySet
from django.http import HttpRequest

from meshapi.models import Install

Expand Down Expand Up @@ -94,7 +98,9 @@ class InstallAdmin(admin.ModelAdmin):
),
]

def get_search_results(self, request, queryset, search_term):
def get_search_results(
self, request: HttpRequest, queryset: QuerySet[Install], search_term: str
) -> Tuple[QuerySet[Install], bool]:
queryset, may_have_duplicates = super().get_search_results(
request,
queryset,
Expand Down
2 changes: 1 addition & 1 deletion src/meshapi/admin/link.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django import forms
from django.contrib import admin
from django.contrib.admin.options import forms

from meshapi.models import Link

Expand Down
2 changes: 1 addition & 1 deletion src/meshapi/admin/member.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django import forms
from django.contrib import admin
from django.contrib.admin.options import forms

from meshapi.admin.inlines import InstallInline
from meshapi.models import Member
Expand Down
8 changes: 5 additions & 3 deletions src/meshapi/admin/node.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import Optional

from django import forms
from django.contrib import admin
from django.contrib.admin.options import forms

from meshapi.admin.inlines import (
BuildingMembershipInline,
Expand All @@ -10,7 +12,7 @@
PanoramaInline,
SectorInline,
)
from meshapi.models import Node
from meshapi.models import Building, Node

admin.site.site_header = "MeshDB Admin"
admin.site.site_title = "MeshDB Admin Portal"
Expand Down Expand Up @@ -83,5 +85,5 @@ class NodeAdmin(admin.ModelAdmin):
NodeLinkInline,
]

def address(self, obj):
def address(self, obj: Node) -> Optional[Building]:
return obj.buildings.first()
4 changes: 2 additions & 2 deletions src/meshapi/admin/sector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django import forms
from django.contrib import admin
from django.contrib.admin.options import forms

from meshapi.admin.admin import device_fieldsets
from meshapi.admin.inlines import DeviceLinkInline
Expand Down Expand Up @@ -39,4 +39,4 @@ class SectorAdmin(admin.ModelAdmin):
]
},
),
]
] # type: ignore[assignment]
5 changes: 3 additions & 2 deletions src/meshapi/docs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import json
from textwrap import dedent
from typing import Dict, List, Optional
from typing import Any, Dict, List, Optional

from django.http import HttpRequest, HttpResponse
from django.urls import reverse
from drf_spectacular.authentication import SessionScheme
from drf_spectacular.settings import spectacular_settings
Expand All @@ -12,7 +13,7 @@

class SpectacularSwaggerInjectVarsView(SpectacularSwaggerView):
@extend_schema(exclude=True)
def get(self, request, *args, **kwargs):
def get(self, request: HttpRequest, *args: List[Any], **kwargs: Dict[str, Any]) -> HttpResponse:
spectacular_settings.DESCRIPTION = (
"Programmatic access to mesh core data, detailing our installs, members, etc. "
'\n\nTo use an authorization token, use the "Authorize" button, and under "tokenAuth" enter '
Expand Down
18 changes: 13 additions & 5 deletions src/meshapi/management/commands/scramble_members.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from argparse import ArgumentParser
from datetime import date, timedelta
from random import randint, randrange
from typing import Any, Tuple
from typing import Any, Optional, Tuple

from django.core.management.base import BaseCommand
from django.db import transaction
Expand Down Expand Up @@ -84,28 +84,36 @@ def handle(self, *args: Any, **options: Any) -> None:
for device in devices:
device.notes = fake.text()
_, device.install_date, device.abandon_date = self.fuzz_dates(
None, device.install_date, device.abandon_date
date.today(), device.install_date, device.abandon_date
)
device.save()

print("Scrambling links...")
links = Link.objects.all()
for link in links:
link.notes = fake.text()
_, link.install_date, link.abandon_date = self.fuzz_dates(None, link.install_date, link.abandon_date)
_, link.install_date, link.abandon_date = self.fuzz_dates(
date.today(), link.install_date, link.abandon_date
)
link.save()

print("Scrambling nodes...")
nodes = Node.objects.all()
for node in nodes:
node.notes = fake.text()
_, node.install_date, node.abandon_date = self.fuzz_dates(None, node.install_date, node.abandon_date)
_, node.install_date, node.abandon_date = self.fuzz_dates(
date.today(), node.install_date, node.abandon_date
)
node.save()

print("Done")

@staticmethod
def fuzz_dates(request_date: date | None, install_date: date, abandon_date: date) -> Tuple[date | None, date, date]:
def fuzz_dates(
request_date: date,
install_date: Optional[date],
abandon_date: Optional[date],
) -> Tuple[date, Optional[date], Optional[date]]:
WillNilges marked this conversation as resolved.
Show resolved Hide resolved
if request_date:
# Make it happen sooner so that there's no way the request date is
# now beyond the install/abandon date.
Expand Down
Loading
Loading