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

Introduce Typing #192

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
193a9d4
add pyright for typing
matmair Mar 24, 2024
9172e42
add helper for plugin config tests
matmair Mar 24, 2024
b265c2d
fix usage of modern annotations
matmair Mar 24, 2024
11f76ed
more types / refactors
matmair Mar 24, 2024
55a5d88
pyright settings
matmair Mar 24, 2024
3618d70
alter type for Response
matmair Mar 24, 2024
9c1bf79
more fixes
matmair Mar 24, 2024
61d7b87
fix annotations style
matmair Mar 24, 2024
ffb9103
fix version
matmair Mar 24, 2024
a2f4041
Revert "alter type for Response"
matmair Mar 24, 2024
66e475c
fix test
matmair Mar 24, 2024
3128878
fix fnc call
matmair Mar 24, 2024
d5085dd
add reqs install
matmair Mar 24, 2024
6f748ad
ignore migrations
matmair Mar 24, 2024
1e0e14b
more asserts
matmair Mar 24, 2024
b35e210
more typing
matmair Apr 2, 2024
36cea35
cover empty states
matmair Apr 2, 2024
78333c5
cover none case
matmair Apr 2, 2024
d79eede
sprikle in a few asserts
matmair Apr 2, 2024
eedb9ac
more asserts
matmair Apr 2, 2024
7bcdcbb
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Apr 2, 2024
65554ff
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Apr 3, 2024
5be09b0
fox reg
matmair Apr 3, 2024
73d0943
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Apr 23, 2024
b2423a9
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair May 27, 2024
f67fbc4
fix merge
matmair May 27, 2024
03202ea
fix path
matmair May 27, 2024
b4e9552
Merge branch 'master' into typing
matmair Jul 8, 2024
acee991
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Oct 20, 2024
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
3 changes: 3 additions & 0 deletions .github/scripts/version_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ def get_existing_release_tags(include_prerelease=True):
tag = release['tag_name'].strip()
match = re.match(r'^.*(\d+)\.(\d+)\.(\d+).*$', tag)

if not match:
continue

if len(match.groups()) != 3:
print(f"Version '{tag}' did not match expected pattern")
continue
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/qc_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ jobs:
run: |
pip install --require-hashes -r contrib/dev_reqs/requirements.txt
python3 .github/scripts/version_check.py
- name: Set up Environment
uses: ./.github/actions/setup
with:
dev-install: true
install: true
- uses: jakebailey/pyright-action@v1
name: Pyright
with:
version: 1.1.355
continue-on-error: true

mkdocs:
name: Style [Documentation]
Expand Down
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@
"django": true,
"justMyCode": false
},
{
"name": "InvenTree Server - spec",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/InvenTree/manage.py",
"args": ["spectacular", "--fail-on-warn", "--validate", "--color"],
"django": true,
"justMyCode": false
},
{
"name": "InvenTree Frontend - Vite",
"type": "chrome",
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,20 @@ skip_glob ="*/migrations/*.py"
known_django="django"
sections=["FUTURE","STDLIB","DJANGO","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]

[tool.pyright]
include = ["src/backend/InvenTree"]
exclude = ["**/node_modules","**/__pycache__","**/migrations/**"]
defineConstant = { DEBUG = true }

reportMissingImports = true
reportMissingTypeStubs = false
reportIncompatibleMethodOverride = false
reportAttributeAccessIssue = false
reportIndexIssue = false
reportIncompatibleVariableOverride = false

pythonVersion = "3.9"
pythonPlatform = "Linux"

[tool.codespell]
ignore-words-list = ["assertIn","SME","intoto"]
4 changes: 3 additions & 1 deletion src/backend/InvenTree/InvenTree/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Admin classes."""

from typing import List

from django.contrib import admin
from django.db.models.fields import CharField
from django.http.request import HttpRequest
Expand All @@ -21,7 +23,7 @@ class InvenTreeResource(ModelResource):
MAX_IMPORT_COLS = 100

# List of fields which should be converted to empty strings if they are null
CONVERT_NULL_FIELDS = []
CONVERT_NULL_FIELDS: List[str] = []

def import_data_inner(
self,
Expand Down
7 changes: 4 additions & 3 deletions src/backend/InvenTree/InvenTree/format.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Custom string formatting functions and helpers."""

from __future__ import annotations

import re
import string
from typing import Optional

from django.conf import settings
from django.utils import translation
Expand Down Expand Up @@ -180,8 +181,8 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str:

def format_money(
money: Money,
decimal_places: Optional[int] = None,
fmt: Optional[str] = None,
decimal_places: int | None = None,
fmt: str | None = None,
include_symbol: bool = True,
) -> str:
"""Format money object according to the currently set local.
Expand Down
2 changes: 2 additions & 0 deletions src/backend/InvenTree/InvenTree/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,7 @@ def test_instance_name(self):

# The site should also be changed
site_obj = Site.objects.all().order_by('id').first()
assert site_obj
self.assertEqual(site_obj.name, 'Testing title')

@override_settings(SITE_URL=None)
Expand All @@ -1301,6 +1302,7 @@ def test_instance_url(self):
from django.contrib.sites.models import Site

site_obj = Site.objects.all().order_by('id').first()
assert site_obj
self.assertEqual(site_obj.domain, 'http://127.1.2.3')
except Exception:
pass
Expand Down
9 changes: 9 additions & 0 deletions src/backend/InvenTree/InvenTree/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ def getNewestMigrationFile(app, exclude_extension=True):
return newest_file


def get_plugin_config(plugin_slug):
"""Return the plugin config for the specified plugin."""
plg = registry.get_plugin(plugin_slug)
assert plg
cfg = plg.plugin_config()
assert cfg
return cfg


class UserMixin:
"""Mixin to setup a user and login for tests.

Expand Down
9 changes: 9 additions & 0 deletions src/backend/InvenTree/build/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,8 @@ def test_invalid_bom_item(self):
wrong_line = line
break

assert wrong_line

data = self.post(
self.url,
{
Expand Down Expand Up @@ -844,6 +846,7 @@ def test_valid_data(self):
if line.bom_item.sub_part.pk == si.part.pk:
right_line = line
break
assert right_line

self.post(
self.url,
Expand All @@ -863,6 +866,8 @@ def test_valid_data(self):
self.assertEqual(self.n + 1, BuildItem.objects.count())

allocation = BuildItem.objects.last()
assert allocation
assert allocation.bom_item

self.assertEqual(allocation.quantity, 5000)
self.assertEqual(allocation.bom_item.pk, 1)
Expand All @@ -881,6 +886,7 @@ def test_reallocate(self):
if line.bom_item.sub_part.pk == si.part.pk:
right_line = line
break
assert right_line

self.post(
self.url,
Expand All @@ -900,6 +906,8 @@ def test_reallocate(self):
self.assertEqual(self.n + 1, BuildItem.objects.count())

allocation = BuildItem.objects.last()
assert allocation
assert allocation.bom_item

self.assertEqual(allocation.quantity, 3000)
self.assertEqual(allocation.bom_item.pk, 1)
Expand Down Expand Up @@ -1082,6 +1090,7 @@ def setUpTestData(cls):
required = build_line.quantity + idx + 1
sub_part = build_line.bom_item.sub_part
si = StockItem.objects.filter(part=sub_part, quantity__gte=required).first()
assert si

cls.state[sub_part] = (si, si.quantity, required)

Expand Down
2 changes: 2 additions & 0 deletions src/backend/InvenTree/build/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def test_items_exist(self):

# Check that the part object now has an assembly field
part = Part.objects.all().first()
assert part
part.assembly = True
part.save()
part.assembly = False
Expand Down Expand Up @@ -260,6 +261,7 @@ def test_build_line_creation(self):
)

item = BuildItem.objects.first()
assert item

# Check that the "build" field has been removed
with self.assertRaises(AttributeError):
Expand Down
10 changes: 9 additions & 1 deletion src/backend/InvenTree/common/notifications.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Base classes and functions for notifications."""

from __future__ import annotations

import logging
from dataclasses import dataclass
from datetime import timedelta
Expand Down Expand Up @@ -29,6 +31,8 @@ class NotificationMethod:
GLOBAL_SETTING = None
USER_SETTING = None

targets: list | None = None

def __init__(self, obj, category, targets, context) -> None:
"""Check that the method is read.

Expand Down Expand Up @@ -181,7 +185,7 @@ class MethodStorageClass:
Is initialized on startup as one instance named `storage` in this file.
"""

liste = None
liste: list | None = None
user_settings = {}

def collect(self, selected_classes=None):
Expand Down Expand Up @@ -230,6 +234,10 @@ def get_usersettings(self, user) -> list:
list: All applicablae notification settings.
"""
methods = []

if storage.liste is None:
return methods

for item in storage.liste:
if item.USER_SETTING:
new_key = f'NOTIFICATION_METHOD_{item.METHOD_NAME.upper()}'
Expand Down
1 change: 1 addition & 0 deletions src/backend/InvenTree/common/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def send_bulk(self):
# make sure the array fits
array = storage.get_usersettings(self.user)
setting = NotificationUserSetting.objects.all().first()
assert setting

# assertions for settings
self.assertEqual(setting.name, 'Enable test notifications')
Expand Down
12 changes: 12 additions & 0 deletions src/backend/InvenTree/common/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ def test_settings_functions(self):
report_size_obj = InvenTreeSetting.get_setting_object(
'REPORT_DEFAULT_PAGE_SIZE'
)
report_test_obj = InvenTreeSetting.get_setting_object(
'REPORT_ENABLE_TEST_REPORT'
)
assert stale_days and instance_obj and report_size_obj and report_test_obj

# check settings base fields
self.assertEqual(instance_obj.name, 'Server Instance Name')
Expand Down Expand Up @@ -442,6 +446,7 @@ def test_defaults(self):

# Any fields marked as 'boolean' must have a default value specified
setting = InvenTreeSetting.get_setting_object(key)
assert setting

if setting.is_bool():
if setting.default_value not in [True, False]:
Expand Down Expand Up @@ -551,6 +556,7 @@ def test_currency_settings(self):
def test_company_name(self):
"""Test a settings object lifecycle e2e."""
setting = InvenTreeSetting.get_setting_object('INVENTREE_COMPANY_NAME')
assert setting

# Check default value
self.assertEqual(setting.value, 'My company name')
Expand Down Expand Up @@ -651,6 +657,7 @@ def test_user_setting_boolean(self):
setting = InvenTreeUserSetting.get_setting_object(
'SEARCH_PREVIEW_SHOW_PARTS', user=self.user
)
assert setting

# Check default values
self.assertEqual(setting.to_native_value(), True)
Expand Down Expand Up @@ -697,6 +704,7 @@ def test_user_setting_choice(self):
setting = InvenTreeUserSetting.get_setting_object(
'DATE_DISPLAY_FORMAT', user=self.user
)
assert setting

url = reverse('api-user-setting-detail', kwargs={'key': setting.key})

Expand All @@ -721,6 +729,7 @@ def test_user_setting_integer(self):
setting = InvenTreeUserSetting.get_setting_object(
'SEARCH_PREVIEW_RESULTS', user=self.user, cache=False
)
assert setting

url = reverse('api-user-setting-detail', kwargs={'key': setting.key})

Expand Down Expand Up @@ -1364,6 +1373,7 @@ def test_delete(self):

# Get the first project code
code = ProjectCode.objects.first()
assert code

# Delete it
self.delete(
Expand Down Expand Up @@ -1461,6 +1471,7 @@ def test_list(self):
def test_edit(self):
"""Test edit permissions for CustomUnit model."""
unit = CustomUnit.objects.first()
assert unit

# Try to edit without permission
self.user.is_staff = False
Expand Down Expand Up @@ -1488,6 +1499,7 @@ def test_edit(self):
def test_validation(self):
"""Test that validation works as expected."""
unit = CustomUnit.objects.first()
assert unit

self.user.is_staff = True
self.user.save()
Expand Down
11 changes: 11 additions & 0 deletions src/backend/InvenTree/company/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def test_create(self):
n = Contact.objects.count()

company = Company.objects.first()
assert company

# Without required permissions, creation should fail
self.post(
Expand All @@ -266,6 +267,8 @@ def test_edit(self):
"""Test that we can edit a Contact via the API."""
# Get the first contact
contact = Contact.objects.first()
assert contact

# Use this contact in the tests
url = reverse('api-contact-detail', kwargs={'pk': contact.pk})

Expand All @@ -283,12 +286,15 @@ def test_edit(self):

# Get the contact again
contact = Contact.objects.first()
assert contact
self.assertEqual(contact.role, 'x')

def test_delete(self):
"""Tests that we can delete a Contact via the API."""
# Get the last contact
contact = Contact.objects.first()
assert contact

url = reverse('api-contact-detail', kwargs={'pk': contact.pk})

# Delete (without required permissions)
Expand Down Expand Up @@ -343,6 +349,7 @@ def test_list(self):
def test_filter_list(self):
"""Test listing addresses filtered on company."""
company = Company.objects.first()
assert company

response = self.get(self.url, {'company': company.pk}, expected_code=200)

Expand All @@ -351,6 +358,7 @@ def test_filter_list(self):
def test_create(self):
"""Test creating a new address."""
company = Company.objects.first()
assert company

self.post(self.url, {'company': company.pk, 'title': 'HQ'}, expected_code=403)

Expand All @@ -361,6 +369,7 @@ def test_create(self):
def test_get(self):
"""Test that objects are properly returned from a get."""
addr = Address.objects.first()
assert addr

url = reverse('api-address-detail', kwargs={'pk': addr.pk})
response = self.get(url, expected_code=200)
Expand All @@ -381,6 +390,7 @@ def test_get(self):
def test_edit(self):
"""Test editing an object."""
addr = Address.objects.first()
assert addr

url = reverse('api-address-detail', kwargs={'pk': addr.pk})

Expand All @@ -397,6 +407,7 @@ def test_edit(self):
def test_delete(self):
"""Test deleting an object."""
addr = Address.objects.first()
assert addr

url = reverse('api-address-detail', kwargs={'pk': addr.pk})

Expand Down
Loading
Loading