Skip to content

Commit

Permalink
Merge pull request #6 from StephanJarisch/main
Browse files Browse the repository at this point in the history
Upgrade to Python 3.13 and Django 5.1
  • Loading branch information
nezhar authored Oct 15, 2024
2 parents ea7fa94 + 2a8b5eb commit 4467dd7
Show file tree
Hide file tree
Showing 19 changed files with 138 additions and 94 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.13'
architecture: 'x64'

- name: Install dependencies and package
Expand All @@ -38,4 +38,4 @@ jobs:
TWINE_REPOSITORY: ${{ secrets.PYPI_REPOSITORY }}
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
TWINE_NON_INTERACTIVE: yes
TWINE_NON_INTERACTIVE: yes
30 changes: 10 additions & 20 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,20 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
django-version:
- '3.2'
- '4.1'
- '4.2'
djangorestframework-version:
- '3.12'
- '3.13'
- '3.14'
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
django-version: ['4.2', '5.0', '5.1']
djangorestframework-version: ['3.15']
exclude:
- django-version: '4.2'
djangorestframework-version: '3.12'
- django-version: '4.2'
djangorestframework-version: '3.13'
- python-version: '3.9'
django-version: '5.0'
- python-version: '3.9'
django-version: '5.1'

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

Expand Down Expand Up @@ -60,4 +50,4 @@ jobs:
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v3
31 changes: 23 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
repos:
- repo: https://github.com/pycqa/isort
rev: 5.12.0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: isort
args: [ "--profile", "black", "--filter-files" ]
- id: check-merge-conflict
- id: end-of-file-fixer
- id: requirements-txt-fixer
- id: trailing-whitespace
args: ["--markdown-linebreak-ext=md"]

- repo: https://github.com/psf/black
rev: 23.3.0 # Replace by any tag/version: https://github.com/psf/black/tags
- repo: https://github.com/asottile/pyupgrade
rev: v3.17.0
hooks:
- id: black
language_version: python3 # Should be a command that runs python3.6.2+
- id: pyupgrade
args: ["--py312-plus"]

- repo: https://github.com/asottile/add-trailing-comma
rev: v3.1.0
hooks:
- id: add-trailing-comma

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.2
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [1.2.0 - Unreleased]

### Added
- Support for Python 3.12 and 3.13
- Support for Django 5.0 and 5.1
- Mixin for User to provide properties `is_anonymous_login` and `anonymous_login`
- Cookie authentication support

### Changed
- pre-commit configuration

### Removed
- Support for Python 3.8
- Support for Django 3.2 and 4.1

## [1.1.0]

### Added
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,32 @@ INSTALLED_APPS = [
There are multiple ways to include the `AnonymousLogin` functionality to your endpoints. We recommend to use one of
the following approaches:

1. Inherit from the `AnonymousLoginAuthenticationModelViewSet` for any model that is supposed to be accessible via
1. Inherit from the `AnonymousLoginAuthenticationModelViewSet` for any model that is supposed to be accessible via
valid token header. You'll find a simple exemplary usage scenario provided the [testapp](tests/testapp/views.py).

OR

2. Directly add the `AnonymousLoginAuthentication` and `IsAuthenticated` to your ViewSet's `authentication_classes` and
`permission_classes` as implemented in the [AnonymousLoginAuthenticationModelViewSet](drf_anonymous_login/views.py).

3. Optionally add the `AnonymousLoginUserMixin` to your app's User model in order to access its `is_anonymous_login`
3. Optionally add the `AnonymousLoginUserMixin` to your app's User model in order to access its `is_anonymous_login`
and `anonymous_login` properties:
```
# myapp.models.py
class User(AnonymousLoginUserMixin, AbstractUser):
pass
```

```
# settings.py
AUTH_USER_MODEL = "myapp.User"
```


#### Configure token expiration
The tokens will not expire by default (expiration_datetime remains `None`). You can configure the
The tokens will not expire by default (expiration_datetime remains `None`). You can configure the
`ANONYMOUS_LOGIN_EXPIRATION` in your application's `settings.py` to define a default expiration in minutes, e.g.
to have any token only valid for 15 minutes, use:
```python
Expand All @@ -80,7 +80,7 @@ See folder [tests/](tests/). The provided tests cover these criteria:
* access private endpoint with expired token

Follow below instructions to run the tests.
You may exchange the installed Django and DRF versions according to your requirements.
You may exchange the installed Django and DRF versions according to your requirements.
:warning: Depending on your local environment settings you might need to explicitly call `python3` instead of `python`.
```bash
# install dependencies
Expand Down
5 changes: 3 additions & 2 deletions drf_anonymous_login/management/commands/cleanup_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def handle(self, *args, **options):

except Exception as exc:
self.stdout.write(
"%s exception occurred ... " % (exc.__class__.__name__,), ending=""
f"{exc.__class__.__name__} exception occurred ... ",
ending="",
)
self.stdout.flush()
self.stdout.write(self.style.ERROR("FATAL"))
Expand Down Expand Up @@ -60,7 +61,7 @@ def add_arguments(self, parser):
)

def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)

# The command will run as long as the `_running` attribute is
# set to `True`. To safely quit the command, just set this attribute to `False` and the
Expand Down
5 changes: 4 additions & 1 deletion drf_anonymous_login/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ class Migration(migrations.Migration):
(
"token",
models.CharField(
db_index=True, max_length=64, unique=True, verbose_name="Token"
db_index=True,
max_length=64,
unique=True,
verbose_name="Token",
),
),
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ class Migration(migrations.Migration):
model_name="anonymouslogin",
name="expiration_datetime",
field=models.DateTimeField(
default=None, null=True, verbose_name="expiration datetime"
default=None,
null=True,
verbose_name="expiration datetime",
),
),
migrations.AlterField(
Expand All @@ -25,7 +27,10 @@ class Migration(migrations.Migration):
model_name="anonymouslogin",
name="token",
field=models.CharField(
db_index=True, max_length=64, unique=True, verbose_name="token"
db_index=True,
max_length=64,
unique=True,
verbose_name="token",
),
),
]
6 changes: 4 additions & 2 deletions drf_anonymous_login/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ class AnonymousLogin(models.Model):
created = models.DateTimeField(_("created"), auto_now_add=True)
request_data = models.JSONField(default=dict)
expiration_datetime = models.DateTimeField(
_("expiration datetime"), null=True, default=None
_("expiration datetime"),
null=True,
default=None,
)

def save(self, *args, **kwargs):
Expand All @@ -35,7 +37,7 @@ def set_default_expiration_datetime():
return timezone.now() + timedelta(minutes=default_expiration)


class AnonymousLoginUserMixin(object):
class AnonymousLoginUserMixin:
@property
def is_anonymous_login(self):
return AnonymousLogin.objects.filter(token=self.username).exists()
Expand Down
8 changes: 4 additions & 4 deletions drf_anonymous_login/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ class CreateAnonymousLoginViewSet(mixins.CreateModelMixin, viewsets.GenericViewS
@staticmethod
def extract_request_headers(request):
regex = re.compile("^HTTP_")
return dict(
(regex.sub("", header), value)
return {
regex.sub("", header): value
for (header, value) in request.META.items()
if header.startswith("HTTP_")
)
}

def create(self, request, *args, **kwargs):
user = AnonymousLogin.objects.create(
request_data={
"data": request.data,
"headers": self.extract_request_headers(request),
}
},
)
response = Response({"token": user.token}, status=status.HTTP_201_CREATED)
response.set_cookie("anonymous_token", f"Token {user.token}")
Expand Down
18 changes: 9 additions & 9 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Package and package dependencies
-e .
coverage>=7.6.2,<7.7

# Development dependencies
setuptools>=67.6.1,<67.7
wheel>=0.40.0,<0.41
twine>=4.0.2,<4.1
coverage>=7.2.3,<7.3
# TestApp dependencies
django>=4.2,<5.2
djangorestframework>=3.15.0,<3.16

# Linters and formatters
pre-commit>=3.2.2,<3.3
pre-commit>=4.0.1,<4.1

# TestApp dependencies
django>=3.2,<4.3
djangorestframework>=3.14.0,<4
# Development dependencies
setuptools>=75.1.0,<76.0
twine>=5.1.1,<5.2
wheel>=0.44.0,<0.45
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@
classifiers=[
"Development Status :: 5 - Production/Stable",
"Framework :: Django",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.1",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
],
)
2 changes: 1 addition & 1 deletion tests/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
},
}

# Password validation
Expand Down
4 changes: 3 additions & 1 deletion tests/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

# anonymous login route
router.register(
r"auth_anonymous", CreateAnonymousLoginViewSet, basename="auth_anonymous"
r"auth_anonymous",
CreateAnonymousLoginViewSet,
basename="auth_anonymous",
)

urlpatterns = [path("admin/", admin.site.urls), path("api/", include(router.urls))]
30 changes: 16 additions & 14 deletions tests/manage.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""

import os
import sys

if __name__ == "__main__":

def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")

try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?",
) from exc
execute_from_command_line(sys.argv)


if __name__ == "__main__":
main()
Loading

0 comments on commit 4467dd7

Please sign in to comment.