Skip to content

Commit

Permalink
uv (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
domdinicola authored Dec 16, 2024
1 parent 76fa803 commit 718214b
Show file tree
Hide file tree
Showing 24 changed files with 586 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .github/file-filters.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This is used by the action https://github.com/dorny/paths-filter
dependencies: &dependencies
- 'pdm.lock'
- 'uv.lock'
- 'pyproject.toml'

python: &python
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:

- name: Build release distributions
run: |
python -m pip install pdm
pdm build
python -m pip install uv
uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
Expand Down
14 changes: 8 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,16 @@ jobs:
- name: Cache virtualenv
uses: actions/cache@v3
with:
key: venv-${{ runner.os }}-${{ steps.setup_python.outputs.python-version}}-${{ hashFiles('pdm.lock') }}
key: venv-${{ runner.os }}-${{ steps.setup_python.outputs.python-version}}-${{ hashFiles('uv.lock') }}
path: .venv

- name: Test
continue-on-error: true
run: |
pip install uv
pdm sync
- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Install the project
run: |
uv sync --all-extras --dev
uv pip install .
- name: lint
if: needs.changes.outputs.lint
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ config.json
.env
.envrc
.venv/
.pdm-python
# Sphinx documentation
docs/_build/

Expand Down
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Release 1.7
-----------
* removed UNICEFAzureADB2COAuth2


Release 1.6.3
-------------
* admin tweaks for users
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"

[project]
name = "unicef-security"
version = "1.6.3"
Expand Down Expand Up @@ -39,6 +35,7 @@ dependencies = [

[tool.uv]
dev-dependencies = [
"django_regex",
"django-webtest",
"factory-boy",
"flake8",
Expand All @@ -49,7 +46,10 @@ dev-dependencies = [
"pytest-cov",
"pytest-django",
"pytest-echo",
"pytest-factoryboy",
"requests-mock",
"responses",
"tox",
"unittest2",
"vcrpy",
]
Expand Down
11 changes: 10 additions & 1 deletion src/unicef_security/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,19 @@ class UserAdminPlus(ExtraButtonsMixin, UserAdmin):
),
("email", "display_name"),
("job_title",),
("is_active",),
)
},
),
(
_("Permissions"),
{
"fields": (
"is_active",
"groups",
"user_permissions",
),
},
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
Expand Down
7 changes: 0 additions & 7 deletions src/unicef_security/backends.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os

from jwt import decode, DecodeError, ExpiredSignatureError, get_unverified_header
from social_core.backends.azuread_b2c import AzureADB2COAuth2
from social_core.backends.azuread_tenant import AzureADTenantOAuth2
from social_core.exceptions import AuthTokenError

Expand Down Expand Up @@ -33,9 +32,3 @@ def user_data(self, access_token, *args, **kwargs):
)
except (DecodeError, ExpiredSignatureError) as error:
raise AuthTokenError(self, error)


class UNICEFAzureADB2COAuth2(AzureADB2COAuth2):
"""UNICEF Azure ADB2C Custom Backend"""

name = "unicef-azuread-b2c-oauth2"
5 changes: 2 additions & 3 deletions src/unicef_security/middleware.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from django.conf import settings
from django.http import HttpResponseRedirect

from social_core.backends.azuread_b2c import AzureADB2COAuth2
from social_core.exceptions import AuthCanceled, AuthMissingParameter
from social_django.middleware import SocialAuthExceptionMiddleware

from unicef_security.backends import UNICEFAzureADB2COAuth2

from . import config


Expand All @@ -29,7 +28,7 @@ def get_redirect_uri(self, request, exception):
error_description = request.GET.get("error_description", None)
if error == "access_denied" and error_description is not None:
if "AADB2C90118" in error_description:
auth_class = UNICEFAzureADB2COAuth2()
auth_class = AzureADB2COAuth2()
redirect_home = auth_class.get_redirect_uri()
reset_policy = config.AZURE_RESET_POLICY
redirect_url = "".join(
Expand Down
10 changes: 9 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import responses

import pytest

from tests.demoproject.factories import UserFactory
from .factories import UserFactory


@pytest.fixture()
def mocked_responses():
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
yield rsps


@pytest.fixture()
Expand Down
3 changes: 1 addition & 2 deletions tests/demoproject/demo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
]

AUTHENTICATION_BACKENDS = (
"unicef_security.backends.UNICEFAzureADB2COAuth2",
"social_core.backends.azuread_b2c.AzureADB2COAuth2",
"django.contrib.auth.backends.ModelBackend",
)

Expand Down Expand Up @@ -86,7 +86,6 @@
STATIC_URL = "/static/"

MEDIA_ROOT = "/tmp/"
VISION_LOGGER_MODEL = "vision.VisionLog"

AUTH_USER_MODEL = "demo.User"

Expand Down
19 changes: 0 additions & 19 deletions tests/demoproject/factories.py

This file was deleted.

37 changes: 37 additions & 0 deletions tests/factories/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import importlib
import pkgutil
from pathlib import Path

from pytest_factoryboy import register

from factory.django import DjangoModelFactory

from .base import AutoRegisterModelFactory, factories_registry, TAutoRegisterModelFactory
from .social import SocialAuthUserFactory # noqa
from .user import GroupFactory, SuperUserFactory, UserFactory # noqa

for _, name, _ in pkgutil.iter_modules([str(Path(__file__).parent)]):
importlib.import_module(f".{name}", __package__)


django_model_factories = {
factory._meta.model: factory for factory in DjangoModelFactory.__subclasses__()
}


def get_factory_for_model(
_model,
) -> type[TAutoRegisterModelFactory] | type[DjangoModelFactory]:
class Meta:
model = _model

bases = (AutoRegisterModelFactory,)
if _model in factories_registry:
return factories_registry[_model] # noqa

if _model in django_model_factories:
return django_model_factories[_model]

return register(
type(f"{_model._meta.model_name}AutoCreatedFactory", bases, {"Meta": Meta})
) # noqa
23 changes: 23 additions & 0 deletions tests/factories/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import typing

import factory
from factory.base import FactoryMetaClass

TAutoRegisterModelFactory = typing.TypeVar(
"TAutoRegisterModelFactory", bound="AutoRegisterModelFactory"
)

factories_registry: dict[str, TAutoRegisterModelFactory] = {}


class AutoRegisterFactoryMetaClass(FactoryMetaClass):
def __new__(mcs, class_name, bases, attrs):
new_class = super().__new__(mcs, class_name, bases, attrs)
factories_registry[new_class._meta.model] = new_class
return new_class


class AutoRegisterModelFactory(
factory.django.DjangoModelFactory, metaclass=AutoRegisterFactoryMetaClass
):
pass
12 changes: 12 additions & 0 deletions tests/factories/contenttypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.contrib.contenttypes.models import ContentType

from .base import AutoRegisterModelFactory


class ContentTypeFactory(AutoRegisterModelFactory):
app_label = "auth"
model = "user"

class Meta:
model = ContentType
django_get_or_create = ("app_label", "model")
13 changes: 13 additions & 0 deletions tests/factories/django_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.contrib.auth.models import Permission

import factory

from .base import AutoRegisterModelFactory
from .contenttypes import ContentTypeFactory


class PermissionFactory(AutoRegisterModelFactory):
content_type = factory.SubFactory(ContentTypeFactory)

class Meta:
model = Permission
11 changes: 11 additions & 0 deletions tests/factories/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.contrib.admin.models import LogEntry

from .base import AutoRegisterModelFactory


class LogEntryFactory(AutoRegisterModelFactory):
level = "INFO"
message = "Message for {{ event.name }} on channel {{channel.name}}"

class Meta:
model = LogEntry
13 changes: 13 additions & 0 deletions tests/factories/social.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from uuid import uuid4

from social_django.models import UserSocialAuth

import factory

from .user import UserFactory


class SocialAuthUserFactory(UserFactory):
@factory.post_generation
def sso(obj, create, extracted, **kwargs):
UserSocialAuth.objects.get_or_create(user=obj, provider="test", uid=uuid4())
62 changes: 62 additions & 0 deletions tests/factories/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.db.models import signals

import factory.fuzzy

from .base import AutoRegisterModelFactory


@factory.django.mute_signals(signals.post_save)
class UserFactory(AutoRegisterModelFactory):
_password = "password"
username = factory.Sequence(lambda n: "m%[email protected]" % n)
password = factory.django.Password(_password)
email = factory.Sequence(lambda n: "m%[email protected]" % n)
last_name = factory.Faker("last_name")
first_name = factory.Faker("first_name")
is_superuser = False
is_active = True

class Meta:
model = get_user_model()
django_get_or_create = ("username",)

@classmethod
def _create(cls, model_class, *args, **kwargs):
ret = super()._create(model_class, *args, **kwargs)
ret._password = cls._password
return ret


class AdminFactory(UserFactory):
is_superuser = True


class AnonUserFactory(UserFactory):
username = "anonymous"


class SuperUserFactory(UserFactory):
username = factory.Sequence(lambda n: "superuser%[email protected]" % n)
email = factory.Sequence(lambda n: "superuser%[email protected]" % n)
is_superuser = True
is_staff = True
is_active = True


class GroupFactory(factory.django.DjangoModelFactory):
name = factory.Sequence(lambda n: "name%03d" % n)

@factory.post_generation
def permissions(self, create, extracted, **kwargs):
if not create:
return # Simple build, do nothing.

if extracted:
for permission in extracted: # A list of groups were passed in, use them
self.permissions.add(permission)

class Meta:
model = Group
django_get_or_create = ("name",)
Loading

0 comments on commit 718214b

Please sign in to comment.