From 3a1609f3c8fa8b2513e55baebf08f21b3ef1a1e2 Mon Sep 17 00:00:00 2001 From: Florimond Manca Date: Wed, 13 Sep 2023 00:25:35 +0200 Subject: [PATCH] Upgrade tooling (#247) --- .coveragerc_dj22 | 4 ++ .gitignore | 1 + Makefile | 23 ++++---- ci/azure-pipelines.yml | 24 ++++---- docs/guide.md | 6 +- pyproject.toml | 45 +++++++++++++++ requirements.txt | 21 +++---- setup.cfg | 11 +--- setup.py | 56 +------------------ src/rest_framework_api_key/__init__.py | 13 +++-- src/rest_framework_api_key/__version__.py | 1 - src/rest_framework_api_key/crypto.py | 1 - .../migrations/0001_initial.py | 1 - .../migrations/0002_auto_20190529_2243.py | 1 - .../migrations/0003_auto_20190623_1952.py | 1 - .../migrations/0004_prefix_hashed_key.py | 5 +- .../migrations/0005_auto_20220110_1102.py | 1 - .../heroes/migrations/0001_initial.py | 1 - .../migrations/0002_prefix_hashed_key.py | 1 - .../heroes/migrations/0003_alter_hero_id.py | 1 - .../migrations/0004_auto_20220110_1102.py | 1 - test_project/project/settings.py | 3 +- test_project/project/views.py | 1 + tests/test_migrations.py | 12 +++- tests/test_permissions.py | 12 ---- tools/install_django.sh | 4 +- 26 files changed, 115 insertions(+), 136 deletions(-) create mode 100644 .coveragerc_dj22 create mode 100644 pyproject.toml delete mode 100644 src/rest_framework_api_key/__version__.py diff --git a/.coveragerc_dj22 b/.coveragerc_dj22 new file mode 100644 index 0000000..967e146 --- /dev/null +++ b/.coveragerc_dj22 @@ -0,0 +1,4 @@ +[report] +exclude_lines = + pragma: no cover + pragma: nodj22 diff --git a/.gitignore b/.gitignore index 9aeb664..954fb6a 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ venv.bak/ *.sqlite3 .idea/ +.ruff_cache/ diff --git a/Makefile b/Makefile index eaadce5..7ac6adf 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,16 @@ +.PHONY: docs + venv = venv bin = ${venv}/bin/ pysources = src/ test_project/ tests/ build: - ${bin}python setup.py sdist bdist_wheel - ${bin}twine check dist/* - rm -r build + ${bin}python -m build check: - ${bin}black --check --diff --target-version=py36 ${pysources} - ${bin}flake8 ${pysources} + ${bin}ruff check ${pysources} + ${bin}black --check --diff ${pysources} ${bin}mypy ${pysources} - ${bin}isort --check --diff ${pysources} make migrations-check docs: @@ -23,16 +22,20 @@ docs-serve: docs-deploy: ${bin}mkdocs gh-deploy -install: +install: install-python + +venv: python3 -m venv ${venv} + +install-python: venv ${bin}pip install -U pip wheel + ${bin}pip install -U build ${bin}pip install -r requirements.txt ./tools/install_django.sh ${bin}pip format: - ${bin}autoflake --in-place --recursive ${pysources} - ${bin}isort ${pysources} - ${bin}black --target-version=py36 ${pysources} + ${bin}ruff check --fix ${pysources} + ${bin}black ${pysources} migrations: ${bin}python -m tools.makemigrations diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 9e5f8da..4d138c0 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -4,10 +4,10 @@ resources: type: github endpoint: github name: florimondmanca/azure-pipelines-templates - ref: refs/tags/5.0 + ref: refs/tags/6.0 containers: - - container: pg11 - image: postgres:11 + - container: pg15 + image: postgres:15-alpine ports: - 5432:5432 env: @@ -34,36 +34,34 @@ stages: jobs: - template: job--python-check.yml@templates parameters: - pythonVersion: "3.10" + pythonVersion: "3.11" - template: job--python-docs-build.yml@templates parameters: - pythonVersion: "3.10" + pythonVersion: "3.11" - template: job--python-test.yml@templates parameters: jobs: - py37_dj22: + py38_dj22: variables: DJANGO_VERSION: "2.2.*" + PYTEST_ADDOPTS: "--cov-config=.coveragerc_dj22" py38_dj32: - variables: - DJANGO_VERSION: "3.2.*" - - py310_dj32: coverage: true variables: DJANGO_VERSION: "3.2.*" - py310_dj42: + py311_dj42: coverage: true variables: DJANGO_VERSION: "4.2.*" + PYTEST_ADDOPTS: "--cov=tests" - py310_postgres: + py311_postgres: services: - postgres: pg11 + postgres: pg15 variables: DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/default" diff --git a/docs/guide.md b/docs/guide.md index 6c4c1f2..e12434d 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -4,13 +4,13 @@ ### Installation -Install from PyPI: +Install with `pip`: ```bash -pip install djangorestframework-api-key +pip install "djangorestframework-api-key==2.*" ``` -**Note**: this package requires Python 3.7+, Django 2.0+ and Django REST Framework 3.8+. +_**Note**: It is highly recommended to **pin your dependency** to the latest major version (as depicted above), as breaking changes may and will happen between major releases._ ### Project setup diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6bbbfe3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[build-system] +requires = ["setuptools", "setuptools-scm", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "djangorestframework-api-key" +description = "API key permissions for the Django REST Framework" +requires-python = ">=3.8" +license = { text = "MIT" } +authors = [ + { name = "Florimond Manca", email = "florimond.manca@protonmail.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Environment :: Web Environment", + "Topic :: Software Development :: Libraries :: Python Modules", + "Framework :: Django", + "Framework :: Django :: 2.2", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.2", +] +dependencies = [ + "packaging", +] +dynamic = ["version", "readme"] + +[project.urls] +"Homepage" = "https://github.com/florimondmanca/djangorestframework-api-key" +"Documentation" = "https://florimondmanca.github.io/djangorestframework-api-key/" + +[tool.setuptools.dynamic] +version = { attr = "rest_framework_api_key.__version__" } +readme = { file = ["README.md", "CHANGELOG.md"], content-type = "text/markdown" } + +[tool.ruff] +select = ["E", "F", "I"] +line-length = 88 +src = ["src", "test_project", "tests"] diff --git a/requirements.txt b/requirements.txt index 4922bb2..08cad7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,18 +14,13 @@ twine wheel # Tooling. -autoflake -black==22.3.0 -flake8 -flake8-bugbear -flake8-comprehensions -django-test-migrations==1.2.0 -isort==5.* -mkdocs==1.3.0 -mkdocs-material==8.3.1 -pymdown-extensions==10.0 -mypy -pytest==7.1.2 +black==23.9.1 +django-test-migrations==1.3.0 +mkdocs==1.5.2 +mkdocs-material==9.3.1 +pymdown-extensions==10.3 +mypy==1.5.1 +pytest==7.4.2 pytest-django==4.5.2 pytest-cov -seed-isort-config +ruff==0.0.289 diff --git a/setup.cfg b/setup.cfg index cec0ebb..8de175e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,20 +1,11 @@ -[flake8] -ignore = W503, E203, B305 -max-line-length = 88 - [mypy] disallow_untyped_defs = True ignore_missing_imports = True -[tool:isort] -profile = black -known_third_party = test_project - [tool:pytest] testpaths = tests addopts = -rxXs - --cov=rest_framework_api_key - --cov=tests + --cov=src --cov-report=term-missing --cov-fail-under=100 diff --git a/setup.py b/setup.py index 8873d8b..5580482 100644 --- a/setup.py +++ b/setup.py @@ -1,55 +1,3 @@ -import re -from pathlib import Path +from setuptools import setup -from setuptools import find_packages, setup - - -def get_version(package: str) -> str: - version = (Path("src") / package / "__version__.py").read_text() - match = re.search("__version__ = ['\"]([^'\"]+)['\"]", version) - assert match is not None - return match.group(1) - - -def get_long_description() -> str: - with open("README.md", encoding="utf8") as readme: - with open("CHANGELOG.md", encoding="utf8") as changelog: - return readme.read() + "\n\n" + changelog.read() - - -setup( - name="djangorestframework-api-key", - version=get_version("rest_framework_api_key"), - description="API key permissions for the Django REST Framework", - long_description=get_long_description(), - long_description_content_type="text/markdown", - url="http://github.com/florimondmanca/djangorestframework-api-key", - project_urls={ - "Documentation": "https://florimondmanca.github.io/djangorestframework-api-key/" - }, - author="Florimond Manca", - author_email="florimond.manca@gmail.com", - packages=find_packages("src"), - package_dir={"": "src"}, - include_package_data=True, - zip_safe=False, - install_requires=["packaging"], - python_requires=">=3.7", - license="MIT", - classifiers=[ - "Development Status :: 4 - Beta", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Environment :: Web Environment", - "Topic :: Software Development :: Libraries :: Python Modules", - "Framework :: Django", - "Framework :: Django :: 2.2", - "Framework :: Django :: 3.2", - "Framework :: Django :: 4.2", - ], -) +setup() # Editable installs. diff --git a/src/rest_framework_api_key/__init__.py b/src/rest_framework_api_key/__init__.py index aa26930..732566a 100644 --- a/src/rest_framework_api_key/__init__.py +++ b/src/rest_framework_api_key/__init__.py @@ -1,8 +1,11 @@ -import django +try: + import django +except ImportError: # pragma: no cover + pass +else: + if django.VERSION < (3, 2): # pragma: no cover + default_app_config = "rest_framework_api_key.apps.RestFrameworkApiKeyConfig" -from .__version__ import __version__ - -if django.VERSION < (3, 2): # pragma: no cover - default_app_config = "rest_framework_api_key.apps.RestFrameworkApiKeyConfig" +__version__ = "2.3.0" __all__ = ["__version__", "default_app_config"] diff --git a/src/rest_framework_api_key/__version__.py b/src/rest_framework_api_key/__version__.py deleted file mode 100644 index 55e4709..0000000 --- a/src/rest_framework_api_key/__version__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "2.3.0" diff --git a/src/rest_framework_api_key/crypto.py b/src/rest_framework_api_key/crypto.py index 00490ea..6289b09 100644 --- a/src/rest_framework_api_key/crypto.py +++ b/src/rest_framework_api_key/crypto.py @@ -45,7 +45,6 @@ def verify(self, password: str, encoded: str) -> bool: class KeyGenerator: - preferred_hasher = Sha512ApiKeyHasher() def __init__(self, prefix_length: int = 8, secret_key_length: int = 32): diff --git a/src/rest_framework_api_key/migrations/0001_initial.py b/src/rest_framework_api_key/migrations/0001_initial.py index 8f610df..6ebd53d 100644 --- a/src/rest_framework_api_key/migrations/0001_initial.py +++ b/src/rest_framework_api_key/migrations/0001_initial.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] # type: ignore diff --git a/src/rest_framework_api_key/migrations/0002_auto_20190529_2243.py b/src/rest_framework_api_key/migrations/0002_auto_20190529_2243.py index 7aefb04..8c95653 100644 --- a/src/rest_framework_api_key/migrations/0002_auto_20190529_2243.py +++ b/src/rest_framework_api_key/migrations/0002_auto_20190529_2243.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [("rest_framework_api_key", "0001_initial")] operations = [ diff --git a/src/rest_framework_api_key/migrations/0003_auto_20190623_1952.py b/src/rest_framework_api_key/migrations/0003_auto_20190623_1952.py index e3da7c7..a8baa9c 100644 --- a/src/rest_framework_api_key/migrations/0003_auto_20190623_1952.py +++ b/src/rest_framework_api_key/migrations/0003_auto_20190623_1952.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [("rest_framework_api_key", "0002_auto_20190529_2243")] operations = [ diff --git a/src/rest_framework_api_key/migrations/0004_prefix_hashed_key.py b/src/rest_framework_api_key/migrations/0004_prefix_hashed_key.py index 3d40f89..73cf95d 100644 --- a/src/rest_framework_api_key/migrations/0004_prefix_hashed_key.py +++ b/src/rest_framework_api_key/migrations/0004_prefix_hashed_key.py @@ -10,7 +10,9 @@ def populate_prefix_hashed_key(apps, schema_editor) -> None: # type: ignore model = apps.get_model(APP_NAME, MODEL_NAME) - for api_key in model.objects.using(schema_editor.connection.alias).all(): + for api_key in model.objects.using( + schema_editor.connection.alias + ).all(): # pragma: nodj22 prefix, _, hashed_key = api_key.id.partition(".") api_key.prefix = prefix api_key.hashed_key = hashed_key @@ -18,7 +20,6 @@ def populate_prefix_hashed_key(apps, schema_editor) -> None: # type: ignore class Migration(migrations.Migration): - dependencies = DEPENDENCIES operations = [ diff --git a/src/rest_framework_api_key/migrations/0005_auto_20220110_1102.py b/src/rest_framework_api_key/migrations/0005_auto_20220110_1102.py index 6ccd860..dc49aab 100644 --- a/src/rest_framework_api_key/migrations/0005_auto_20220110_1102.py +++ b/src/rest_framework_api_key/migrations/0005_auto_20220110_1102.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("rest_framework_api_key", "0004_prefix_hashed_key"), ] diff --git a/test_project/heroes/migrations/0001_initial.py b/test_project/heroes/migrations/0001_initial.py index c3a5488..7e142d5 100644 --- a/test_project/heroes/migrations/0001_initial.py +++ b/test_project/heroes/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True operations = [ diff --git a/test_project/heroes/migrations/0002_prefix_hashed_key.py b/test_project/heroes/migrations/0002_prefix_hashed_key.py index 3e5d977..4043cfd 100644 --- a/test_project/heroes/migrations/0002_prefix_hashed_key.py +++ b/test_project/heroes/migrations/0002_prefix_hashed_key.py @@ -18,7 +18,6 @@ def populate_prefix_hashed_key(apps, schema_editor): # type: ignore class Migration(migrations.Migration): - dependencies = DEPENDENCIES operations = [ diff --git a/test_project/heroes/migrations/0003_alter_hero_id.py b/test_project/heroes/migrations/0003_alter_hero_id.py index 148877b..f82b658 100644 --- a/test_project/heroes/migrations/0003_alter_hero_id.py +++ b/test_project/heroes/migrations/0003_alter_hero_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("heroes", "0002_prefix_hashed_key"), ] diff --git a/test_project/heroes/migrations/0004_auto_20220110_1102.py b/test_project/heroes/migrations/0004_auto_20220110_1102.py index 7341a35..7275031 100644 --- a/test_project/heroes/migrations/0004_auto_20220110_1102.py +++ b/test_project/heroes/migrations/0004_auto_20220110_1102.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("heroes", "0003_alter_hero_id"), ] diff --git a/test_project/project/settings.py b/test_project/project/settings.py index 6d9af35..9494004 100644 --- a/test_project/project/settings.py +++ b/test_project/project/settings.py @@ -77,10 +77,11 @@ # Password hashers PASSWORD_HASHERS = [ - "django.contrib.auth.hashers.Argon2PasswordHasher", "django.contrib.auth.hashers.PBKDF2PasswordHasher", "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.Argon2PasswordHasher", "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", + "django.contrib.auth.hashers.ScryptPasswordHasher", ] diff --git a/test_project/project/views.py b/test_project/project/views.py index ad89cc3..32bc99e 100644 --- a/test_project/project/views.py +++ b/test_project/project/views.py @@ -1,6 +1,7 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView + from test_project.heroes.permissions import HasHeroAPIKey diff --git a/tests/test_migrations.py b/tests/test_migrations.py index ebe9b00..dc40038 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -1,7 +1,17 @@ from typing import Any, Tuple import pytest -from django_test_migrations.migrator import Migrator + +try: + from django_test_migrations.migrator import Migrator +except ImportError: # pragma: no cover + # Most likely Django < 3.2 + Migrator = None # type: ignore + +pytestmark = pytest.mark.skipif( + Migrator is None, + reason="django-test-migrations is not available", +) @pytest.mark.django_db diff --git a/tests/test_permissions.py b/tests/test_permissions.py index bceb353..14d7318 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -2,7 +2,6 @@ from typing import Callable import pytest -from django.conf.global_settings import PASSWORD_HASHERS from django.test import RequestFactory, override_settings from rest_framework.decorators import api_view, permission_classes from rest_framework.request import Request @@ -40,17 +39,6 @@ def test_if_valid_api_key_custom_header_then_permission_granted( assert response.status_code == 200 -@pytest.mark.parametrize("hasher", PASSWORD_HASHERS) -def test_hashers(rf: RequestFactory, hasher: str) -> None: - with override_settings(PASSWORD_HASHERS=[hasher]): - _, key = APIKey.objects.create_key(name="test") - authorization = f"Api-Key {key}" - request = rf.get("/test/", HTTP_AUTHORIZATION=authorization) - - response = view(request) - assert response.status_code == 200 - - def test_if_no_api_key_then_permission_denied(rf: RequestFactory) -> None: request = rf.get("/test/") diff --git a/tools/install_django.sh b/tools/install_django.sh index 655b517..4679602 100755 --- a/tools/install_django.sh +++ b/tools/install_django.sh @@ -2,6 +2,6 @@ PIP="$1" -DJANGO_VERSION=${DJANGO_VERSION:-4.0.5} +DJANGO_VERSION=${DJANGO_VERSION:-4.2.5} -exec ${PIP} install django[argon2,bcrypt]==$DJANGO_VERSION +exec ${PIP} install django==$DJANGO_VERSION