adjango


Nameadjango JSON
Version 0.8.0 PyPI version JSON
download
home_pagehttps://github.com/Artasov/adjango
SummaryA library with many features for interacting with Django
upload_time2025-08-27 02:05:53
maintainerNone
docs_urlNone
authorxlartas
requires_python>=3.12
licenseNone
keywords adjango django utils funcs features async managers services
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            # 🚀 ADjango

📊 **Coverage 70%**

> Sometimes I use this in different projects, so I decided to put it on pypi

`ADjango` is a comprehensive library that enhances Django development with Django REST Framework (DRF) and Celery
integration. It provides essential tools including
asynchronous `managers`, `services`, `serializers`, `decorators`, `exceptions` and more utilities for `async`
programming, Celery task scheduling, `transaction` management, and much more to streamline your Django DRF Celery
development workflow.

- [Installation 🛠️](#installation-️)
- [Settings ⚙️](#settings-️)
- [Overview](#overview)
    - [Manager \& Services 🛎️](#manager--services-️)
    - [Utils 🔧](#utils-)
    - [Mixins 🎨](#mixins-)
    - [Decorators 🎀](#decorators-)
    - [Exceptions 🚨](#exceptions-)
    - [Serializers 🔧](#serializers-)
    - [Management](#management)
    - [Celery 🔥](#celery-)
        - [Management Commands](#management-commands)
        - [@task Decorator](#task-decorator)
        - [Tasker - Task Scheduler](#tasker---task-scheduler)
        - [Email Sending via Celery](#email-sending-via-celery)
    - [Other](#other)

## Installation 🛠️

```bash
pip install adjango
```

## Settings ⚙️

- ### Add the application to the project

    ```python
    INSTALLED_APPS = [
        # ...
        'adjango',
    ]
    ```

- ### In `settings.py` set the params

    ```python
    # settings.py
  
    # NONE OF THE PARAMETERS ARE REQUIRED  
  
    # For usage @a/controller decorators
    LOGIN_URL = '/login/' 
  
    # optional
    ADJANGO_BACKENDS_APPS = BASE_DIR / 'apps' # for management commands
    ADJANGO_FRONTEND_APPS = BASE_DIR.parent / 'frontend' / 'src' / 'apps' # for management commands
    ADJANGO_APPS_PREPATH = 'apps.'  # if apps in BASE_DIR/apps/app1,app2...
    ADJANGO_UNCAUGHT_EXCEPTION_HANDLING_FUNCTION = ... # Read about @acontroller, @controller
    ADJANGO_CONTROLLERS_LOGGER_NAME = 'global' # only for usage @a/controller decorators
    ADJANGO_CONTROLLERS_LOGGING = True # only for usage @a/controller decorators
    ADJANGO_EMAIL_LOGGER_NAME = 'email' # for send_emails_task logging
    ```

    ```python
    MIDDLEWARE = [
        ...
        # add request.ip in views if u need
        'adjango.middleware.IPAddressMiddleware',  
        ...
    ]
    ```

## Overview

Most functions, if available in asynchronous form, are also available in synchronous form.

### Manager & Services 🛎️

A simple example and everything is immediately clear...

```python
from adjango.fields import AManyToManyField
from adjango.managers.base import AManager
from adjango.services.base import ABaseService
from adjango.models import AModel
from adjango.models.base import AAbstractUser
from adjango.models.polymorphic import APolymorphicModel

...
...  # Service layer usage
...

# services/user.py
if TYPE_CHECKING:
    from apps.core.models import User


class UserService(ABaseService):
    def __init__(self, obj: 'User') -> None:
        super().__init__(obj)
        self.user = obj

    def get_full_name(self) -> str:
        return f"{self.user.first_name} {self.user.last_name}"


# models/user.py (User redefinition)
class User(AAbstractUser):
    ...

    @property
    def service(self) -> UserService:
        return UserService(self)


# and u can use with nice type hints:
user = await User.objects.aget(id=1)
full_name = user.service.get_full_name()

...
...  # Other best features
...


# models/commerce.py
class Product(APolymorphicModel):
    name = CharField(max_length=100)


class Order(AModel):
    user = ForeignKey(User, CASCADE)
    products = AManyToManyField(Product)


# The following is now possible...
products = await Product.objects.aall()
products = await Product.objects.afilter(name='name')
# Returns an object or None if not found
order = await Order.objects.agetorn(id=69)  # aget or none
if not order: raise

# We install products in the order
await order.products.aset(products)
# Or queryset right away...
await order.products.aset(
    Product.objects.filter(name='name')
)
await order.products.aadd(products[0])

# We get the order again without associated objects
order: Order = await Order.objects.aget(id=69)
# Retrieve related objects asynchronously.
order.user = await order.arelated('user')
products = await order.products.aall()
# Works the same with intermediate processing/query filters
orders = await Order.objects.prefetch_related('products').aall()
for o in orders:
    for p in o.products.all():
        print(p.id)
# thk u
```

### Utils 🔧

`aall`, `afilter`,  `arelated`, and so on are available as individual functions

  ```python
  from adjango.utils.funcs import (
    aall, getorn, agetorn,
    afilter, aset, aadd, arelated
)
  ```

### Mixins 🎨

```python
from adjango.models.mixins import (
    ACreatedAtMixin, ACreatedAtIndexedMixin, ACreatedAtEditableMixin,
    AUpdatedAtMixin, AUpdatedAtIndexedMixin,
    ACreatedUpdatedAtMixin, ACreatedUpdatedAtIndexedMixin
)


class EventProfile(ACreatedUpdatedAtIndexedMixin):
    event = ForeignKey('events.Event', CASCADE, 'members', verbose_name=_('Event'))

    @property
    def service(self) -> EventProfileService:
        return EventProfileService(self)
```

### Decorators 🎀

- `aforce_data`

  The `aforce_data` decorator combines data from the `GET`, `POST` and `JSON` body
  request in `request.data`. This makes it easy to access all request data in one place.

- `aatomic`

  An asynchronous decorator that wraps function into a transactional context using `AsyncAtomicContextManager`. If an
  exception occurs, all database changes are rolled back.

- `acontroller/controller`

  Decorators that provide automatic logging and exception handling for views. The `acontroller` is for async
  views, `controller` is for sync views. They do NOT wrap functions in transactions (use `@aatomic` for that).

    ```python
    from adjango.adecorators import acontroller
    from adjango.decorators import controller

    @acontroller(name='My View', logger='custom_logger', log_name=True, log_time=True)
    async def my_view(request):
        pass
  
    @acontroller('One More View')
    async def my_view_one_more(request):
        pass

    @controller(name='Sync View', auth_required=True, log_time=True)
    def my_sync_view(request):
        pass
    ```

    - These decorators automatically catch uncaught exceptions and log them if the logger is configured
      via `ADJANGO_CONTROLLERS_LOGGER_NAME` and `ADJANGO_CONTROLLERS_LOGGING`.
    - The `controller` decorator also supports authentication checking with `auth_required` parameter.
    - You can also implement the interface:

      ```python
      class IHandlerControllerException(ABC):
          @staticmethod
          @abstractmethod
          def handle(fn_name: str, request: WSGIRequest | ASGIRequest, e: Exception, *args, **kwargs) -> None:
              """
              An example of an exception handling function.
      
              :param fn_name: The name of the function where the exception occurred.
              :param request: The request object (WSGIRequest or ASGIRequest).
              :param e: The exception to be handled.
              :param args: Positional arguments passed to the function.
              :param kwargs: Named arguments passed to the function.
      
              :return: None
              """
              pass
      ```

      and use `handle` to get an uncaught exception:

      ```python
      # settings.py
      from adjango.handlers import HCE # use my example if u need
      ADJANGO_UNCAUGHT_EXCEPTION_HANDLING_FUNCTION = HCE.handle
      ```

### Exceptions 🚨

`ADjango` provides convenient classes for generating API exceptions with proper HTTP statuses and structured error
messages.

```python
from adjango.exceptions.base import (
    ApiExceptionGenerator,
    ModelApiExceptionGenerator,
    ModelApiExceptionBaseVariant as MAEBV
)

# General API exceptions
raise ApiExceptionGenerator('Специальная ошибка', 500)
raise ApiExceptionGenerator('Специальная ошибка', 500, 'special_error')
raise ApiExceptionGenerator(
    'Неверные данные',
    400,
    extra={'field': 'email'}
)

# Model exceptions
from apps.commerce.models import Order

raise ModelApiExceptionGenerator(Order, MAEBV.DoesNotExist)
raise ModelApiExceptionGenerator(
    Order
MAEBV.AlreadyExists,
code = "order_exists",
extra = {"id": 123}
)

# Available exception variants for models:
# DoesNotExist, AlreadyExists, InvalidData, AccessDenied,
# NotAcceptable, Expired, InternalServerError, AlreadyUsed,
# NotUsed, NotAvailable, TemporarilyUnavailable, 
# ConflictDetected, LimitExceeded, DependencyMissing, Deprecated
```

### Serializers 🔧

`ADjango` extends `Django REST Framework` serializers to support asynchronous
operations, making it easier to handle data in async views.
Support methods like `adata`, `avalid_data`, `ais_valid`, and `asave`.

```python
from adjango.querysets.base import AQuerySet
from adjango.aserializers import (
    AModelSerializer, ASerializer, AListSerializer
)
from adjango.serializers import dynamic_serializer

...


class ConsultationPublicSerializer(AModelSerializer):
    clients = UserPublicSerializer(many=True, read_only=True)
    psychologists = UserPsyPublicSerializer(many=True, read_only=True)
    config = ConsultationConfigSerializer(read_only=True)

    class Meta:
        model = Consultation
        fields = '__all__'


# From the complete serializer we cut off the pieces into smaller ones
ConsultationSerializerTier1 = dynamic_serializer(
    ConsultationPublicSerializer, ('id', 'date',)
)
ConsultationSerializerTier2 = dynamic_serializer(
    ConsultationPublicSerializer, (
        'id', 'date', 'psychologists', 'clients', 'config'
    ), {
        'psychologists': UserPublicSerializer(many=True),  # overridden
    }
)


# Use it, in compact format
@acontroller('Completed Consultations')
@api_view(('GET',))
@permission_classes((IsAuthenticated,))
async def consultations_completed(request):
    page = int(request.query_params.get('page', 1))
    page_size = int(request.query_params.get('page_size', 10))
    return Response({
        'results': await ConsultationSerializerTier2(
            await request.user.completed_consultations[
                  (page - 1) * page_size:page * page_size
                  ].aall(),
            many=True,
            context={'request': request}
        ).adata
    }, status=200)


...


class UserService(ABaseService['User']):
    ...

    @property
    def completed_consultations(self) -> AQuerySet['Consultation']:
        """
        Returns an optimized AQuerySet of all completed consultations of the user
        (both psychologist and client).
        """
        from apps.psychology.models import Consultation
        now_ = now()
        return Consultation.objects.defer(
            'communication_type',
            'language',
            'reserved_by',
            'notifies',
            'cancel_initiator',
            'original_consultation',
            'consultations_feedbacks',
        ).select_related(
            'config',
            'conference',
        ).prefetch_related(
            'clients',
            'psychologists',
        ).filter(
            Q(
                Q(clients=self.user) | Q(psychologists=self.user),
                status=Consultation.Status.PAID,
                date__isnull=False,
                date__lt=now_,
                consultations_feedbacks__user=self.user,
            ) |
            Q(
                Q(clients=self) | Q(psychologists=self.user),
                status=Consultation.Status.CANCELLED,
                date__isnull=False,
            )
        ).distinct().order_by('-updated_at')

    ...
```

### Management

- `copy_project`
  Documentation in the _py_ module itself - **[copy_project](adjango/management/commands/copy_project.py)**

ADjango ships with extra management commands to speed up project scaffolding.

- `astartproject` — clones the [adjango-template](https://github.com/Artasov/adjango-template)
  into the given directory and strips its Git history.

  ```bash
  django-admin astartproject myproject
  ```

- `astartup` — creates an app skeleton inside `apps/` and registers it in
  `INSTALLED_APPS`.

  ```bash
  python manage.py astartup blog
  ```

  After running the command you will have the following structure:

  ```sh
  apps/
      blog/
          controllers/base.py
          models/base.py
          services/base.py
          serializers/base.py
          tests/base.py
  ```

- `newentities` — generates empty exception, model, service, serializer and
  test stubs for the specified models in the target app.

  ```bash
  python manage.py newentities order apps.commerce Order,Product,Price
  ```

  Or create a single model:

  ```bash
  python manage.py newentities order apps.commerce Order
  ```

### Celery 🔥

ADjango provides convenient tools for working with Celery: management commands, decorators, and task scheduler.

For Celery configuration in Django, refer to
the [official Celery documentation](https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html).

#### Management Commands

- `celeryworker` — starts Celery Worker with default settings

  ```bash
  python manage.py celeryworker
  python manage.py celeryworker --pool=solo --loglevel=info -E
  python manage.py celeryworker --concurrency=4 --queues=high_priority,default
  ```

- `celerybeat` — starts Celery Beat scheduler for periodic tasks

  ```bash
  python manage.py celerybeat
  python manage.py celerybeat --loglevel=debug
  ```

- `celerypurge` — clears Celery queues from unfinished tasks

  ```bash
  python manage.py celerypurge               # clear all queues
  python manage.py celerypurge --queue=high  # clear specific queue
  ```

#### @task Decorator

The `@task` decorator automatically logs Celery task execution, including errors:

```python
from celery import shared_task
from adjango.decorators import task


@shared_task
@task(logger="global")
def my_background_task(param1: str, param2: int) -> bool:
    """
    Task with automatic execution logging.
    """
    # your code here
    return True
```

**What the decorator provides:**

- ✅ Automatic logging of task start and completion
- ✅ Logging of task parameters
- ✅ Detailed error logging with stack trace
- ✅ Flexible logger configuration for different tasks

#### Tasker - Task Scheduler

The `Tasker` class provides convenient methods for scheduling and managing Celery tasks:

```python
from adjango.utils.celery.tasker import Tasker

# Immediate execution
task_id = Tasker.put(task=my_task, param1='value')

# Delayed execution (in 60 seconds)
task_id = Tasker.put(task=my_task, countdown=60, param1='value')

# Execution at specific time
from datetime import datetime

task_id = Tasker.put(
    task=my_task,
    eta=datetime(2024, 12, 31, 23, 59),
    param1='value'
)

# Cancel task by ID
Tasker.cancel_task(task_id)

# One-time task via Celery Beat (sync)
Tasker.beat(
    task=my_task,
    name='one_time_task',
    schedule_time=datetime(2024, 10, 10, 14, 30),
    param1='value'
)

# Periodic task via Celery Beat (sync)
Tasker.beat(
    task=my_task,
    name='hourly_cleanup',
    interval=3600,  # every hour in seconds
    param1='value'
)

# Crontab schedule via Celery Beat (sync)
Tasker.beat(
    task=my_task,
    name='daily_report',
    crontab={'hour': 7, 'minute': 30},  # every day at 7:30 AM
    param1='value'
)

# Async version of beat is also available
await Tasker.abeat(
    task=my_task,
    name='async_task',
    interval=1800,  # every 30 minutes
    param1='value'
)
```

#### Email Sending via Celery

ADjango includes a ready-to-use task for sending emails with templates:

```python
from adjango.tasks import send_emails_task
from adjango.utils.mail import send_emails

# Synchronous sending
send_emails(
    subject='Welcome!',
    emails=('user@example.com',),
    template='emails/welcome.html',
    context={'user': 'John Doe'}
)

# Asynchronous sending via Celery
send_emails_task.delay(
    subject='Hello!',
    emails=('user@example.com',),
    template='emails/hello.html',
    context={'message': 'Welcome to our service!'}
)

# Via Tasker with delayed execution
Tasker.put(
    task=send_emails_task,
    subject='Reminder',
    emails=('user@example.com',),
    template='emails/reminder.html',
    context={'deadline': '2024-12-31'},
    countdown=3600  # send in an hour
)
```

### Other

- `AsyncAtomicContextManager`🧘

  An asynchronous context manager for working with transactions, which ensures the atomicity of operations.

    ```python
    from adjango.utils.base import AsyncAtomicContextManager
    
    async def some_function():
        async with AsyncAtomicContextManager():
            ...  
    ```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/Artasov/adjango",
    "name": "adjango",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.12",
    "maintainer_email": null,
    "keywords": "adjango django utils funcs features async managers services",
    "author": "xlartas",
    "author_email": "ivanhvalevskey@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/96/5e/b157dd6229c16324788c101a675f4674571e9c1e2e79d660d0122e55e748/adjango-0.8.0.tar.gz",
    "platform": null,
    "description": "# \ud83d\ude80 ADjango\r\n\r\n\ud83d\udcca **Coverage 70%**\r\n\r\n> Sometimes I use this in different projects, so I decided to put it on pypi\r\n\r\n`ADjango` is a comprehensive library that enhances Django development with Django REST Framework (DRF) and Celery\r\nintegration. It provides essential tools including\r\nasynchronous `managers`, `services`, `serializers`, `decorators`, `exceptions` and more utilities for `async`\r\nprogramming, Celery task scheduling, `transaction` management, and much more to streamline your Django DRF Celery\r\ndevelopment workflow.\r\n\r\n- [Installation \ud83d\udee0\ufe0f](#installation-\ufe0f)\r\n- [Settings \u2699\ufe0f](#settings-\ufe0f)\r\n- [Overview](#overview)\r\n    - [Manager \\& Services \ud83d\udece\ufe0f](#manager--services-\ufe0f)\r\n    - [Utils \ud83d\udd27](#utils-)\r\n    - [Mixins \ud83c\udfa8](#mixins-)\r\n    - [Decorators \ud83c\udf80](#decorators-)\r\n    - [Exceptions \ud83d\udea8](#exceptions-)\r\n    - [Serializers \ud83d\udd27](#serializers-)\r\n    - [Management](#management)\r\n    - [Celery \ud83d\udd25](#celery-)\r\n        - [Management Commands](#management-commands)\r\n        - [@task Decorator](#task-decorator)\r\n        - [Tasker - Task Scheduler](#tasker---task-scheduler)\r\n        - [Email Sending via Celery](#email-sending-via-celery)\r\n    - [Other](#other)\r\n\r\n## Installation \ud83d\udee0\ufe0f\r\n\r\n```bash\r\npip install adjango\r\n```\r\n\r\n## Settings \u2699\ufe0f\r\n\r\n- ### Add the application to the project\r\n\r\n    ```python\r\n    INSTALLED_APPS = [\r\n        # ...\r\n        'adjango',\r\n    ]\r\n    ```\r\n\r\n- ### In `settings.py` set the params\r\n\r\n    ```python\r\n    # settings.py\r\n  \r\n    # NONE OF THE PARAMETERS ARE REQUIRED  \r\n  \r\n    # For usage @a/controller decorators\r\n    LOGIN_URL = '/login/' \r\n  \r\n    # optional\r\n    ADJANGO_BACKENDS_APPS = BASE_DIR / 'apps' # for management commands\r\n    ADJANGO_FRONTEND_APPS = BASE_DIR.parent / 'frontend' / 'src' / 'apps' # for management commands\r\n    ADJANGO_APPS_PREPATH = 'apps.'  # if apps in BASE_DIR/apps/app1,app2...\r\n    ADJANGO_UNCAUGHT_EXCEPTION_HANDLING_FUNCTION = ... # Read about @acontroller, @controller\r\n    ADJANGO_CONTROLLERS_LOGGER_NAME = 'global' # only for usage @a/controller decorators\r\n    ADJANGO_CONTROLLERS_LOGGING = True # only for usage @a/controller decorators\r\n    ADJANGO_EMAIL_LOGGER_NAME = 'email' # for send_emails_task logging\r\n    ```\r\n\r\n    ```python\r\n    MIDDLEWARE = [\r\n        ...\r\n        # add request.ip in views if u need\r\n        'adjango.middleware.IPAddressMiddleware',  \r\n        ...\r\n    ]\r\n    ```\r\n\r\n## Overview\r\n\r\nMost functions, if available in asynchronous form, are also available in synchronous form.\r\n\r\n### Manager & Services \ud83d\udece\ufe0f\r\n\r\nA simple example and everything is immediately clear...\r\n\r\n```python\r\nfrom adjango.fields import AManyToManyField\r\nfrom adjango.managers.base import AManager\r\nfrom adjango.services.base import ABaseService\r\nfrom adjango.models import AModel\r\nfrom adjango.models.base import AAbstractUser\r\nfrom adjango.models.polymorphic import APolymorphicModel\r\n\r\n...\r\n...  # Service layer usage\r\n...\r\n\r\n# services/user.py\r\nif TYPE_CHECKING:\r\n    from apps.core.models import User\r\n\r\n\r\nclass UserService(ABaseService):\r\n    def __init__(self, obj: 'User') -> None:\r\n        super().__init__(obj)\r\n        self.user = obj\r\n\r\n    def get_full_name(self) -> str:\r\n        return f\"{self.user.first_name} {self.user.last_name}\"\r\n\r\n\r\n# models/user.py (User redefinition)\r\nclass User(AAbstractUser):\r\n    ...\r\n\r\n    @property\r\n    def service(self) -> UserService:\r\n        return UserService(self)\r\n\r\n\r\n# and u can use with nice type hints:\r\nuser = await User.objects.aget(id=1)\r\nfull_name = user.service.get_full_name()\r\n\r\n...\r\n...  # Other best features\r\n...\r\n\r\n\r\n# models/commerce.py\r\nclass Product(APolymorphicModel):\r\n    name = CharField(max_length=100)\r\n\r\n\r\nclass Order(AModel):\r\n    user = ForeignKey(User, CASCADE)\r\n    products = AManyToManyField(Product)\r\n\r\n\r\n# The following is now possible...\r\nproducts = await Product.objects.aall()\r\nproducts = await Product.objects.afilter(name='name')\r\n# Returns an object or None if not found\r\norder = await Order.objects.agetorn(id=69)  # aget or none\r\nif not order: raise\r\n\r\n# We install products in the order\r\nawait order.products.aset(products)\r\n# Or queryset right away...\r\nawait order.products.aset(\r\n    Product.objects.filter(name='name')\r\n)\r\nawait order.products.aadd(products[0])\r\n\r\n# We get the order again without associated objects\r\norder: Order = await Order.objects.aget(id=69)\r\n# Retrieve related objects asynchronously.\r\norder.user = await order.arelated('user')\r\nproducts = await order.products.aall()\r\n# Works the same with intermediate processing/query filters\r\norders = await Order.objects.prefetch_related('products').aall()\r\nfor o in orders:\r\n    for p in o.products.all():\r\n        print(p.id)\r\n# thk u\r\n```\r\n\r\n### Utils \ud83d\udd27\r\n\r\n`aall`, `afilter`,  `arelated`, and so on are available as individual functions\r\n\r\n  ```python\r\n  from adjango.utils.funcs import (\r\n    aall, getorn, agetorn,\r\n    afilter, aset, aadd, arelated\r\n)\r\n  ```\r\n\r\n### Mixins \ud83c\udfa8\r\n\r\n```python\r\nfrom adjango.models.mixins import (\r\n    ACreatedAtMixin, ACreatedAtIndexedMixin, ACreatedAtEditableMixin,\r\n    AUpdatedAtMixin, AUpdatedAtIndexedMixin,\r\n    ACreatedUpdatedAtMixin, ACreatedUpdatedAtIndexedMixin\r\n)\r\n\r\n\r\nclass EventProfile(ACreatedUpdatedAtIndexedMixin):\r\n    event = ForeignKey('events.Event', CASCADE, 'members', verbose_name=_('Event'))\r\n\r\n    @property\r\n    def service(self) -> EventProfileService:\r\n        return EventProfileService(self)\r\n```\r\n\r\n### Decorators \ud83c\udf80\r\n\r\n- `aforce_data`\r\n\r\n  The `aforce_data` decorator combines data from the `GET`, `POST` and `JSON` body\r\n  request in `request.data`. This makes it easy to access all request data in one place.\r\n\r\n- `aatomic`\r\n\r\n  An asynchronous decorator that wraps function into a transactional context using `AsyncAtomicContextManager`. If an\r\n  exception occurs, all database changes are rolled back.\r\n\r\n- `acontroller/controller`\r\n\r\n  Decorators that provide automatic logging and exception handling for views. The `acontroller` is for async\r\n  views, `controller` is for sync views. They do NOT wrap functions in transactions (use `@aatomic` for that).\r\n\r\n    ```python\r\n    from adjango.adecorators import acontroller\r\n    from adjango.decorators import controller\r\n\r\n    @acontroller(name='My View', logger='custom_logger', log_name=True, log_time=True)\r\n    async def my_view(request):\r\n        pass\r\n  \r\n    @acontroller('One More View')\r\n    async def my_view_one_more(request):\r\n        pass\r\n\r\n    @controller(name='Sync View', auth_required=True, log_time=True)\r\n    def my_sync_view(request):\r\n        pass\r\n    ```\r\n\r\n    - These decorators automatically catch uncaught exceptions and log them if the logger is configured\r\n      via `ADJANGO_CONTROLLERS_LOGGER_NAME` and `ADJANGO_CONTROLLERS_LOGGING`.\r\n    - The `controller` decorator also supports authentication checking with `auth_required` parameter.\r\n    - You can also implement the interface:\r\n\r\n      ```python\r\n      class IHandlerControllerException(ABC):\r\n          @staticmethod\r\n          @abstractmethod\r\n          def handle(fn_name: str, request: WSGIRequest | ASGIRequest, e: Exception, *args, **kwargs) -> None:\r\n              \"\"\"\r\n              An example of an exception handling function.\r\n      \r\n              :param fn_name: The name of the function where the exception occurred.\r\n              :param request: The request object (WSGIRequest or ASGIRequest).\r\n              :param e: The exception to be handled.\r\n              :param args: Positional arguments passed to the function.\r\n              :param kwargs: Named arguments passed to the function.\r\n      \r\n              :return: None\r\n              \"\"\"\r\n              pass\r\n      ```\r\n\r\n      and use `handle` to get an uncaught exception:\r\n\r\n      ```python\r\n      # settings.py\r\n      from adjango.handlers import HCE # use my example if u need\r\n      ADJANGO_UNCAUGHT_EXCEPTION_HANDLING_FUNCTION = HCE.handle\r\n      ```\r\n\r\n### Exceptions \ud83d\udea8\r\n\r\n`ADjango` provides convenient classes for generating API exceptions with proper HTTP statuses and structured error\r\nmessages.\r\n\r\n```python\r\nfrom adjango.exceptions.base import (\r\n    ApiExceptionGenerator,\r\n    ModelApiExceptionGenerator,\r\n    ModelApiExceptionBaseVariant as MAEBV\r\n)\r\n\r\n# General API exceptions\r\nraise ApiExceptionGenerator('\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430', 500)\r\nraise ApiExceptionGenerator('\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430', 500, 'special_error')\r\nraise ApiExceptionGenerator(\r\n    '\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435',\r\n    400,\r\n    extra={'field': 'email'}\r\n)\r\n\r\n# Model exceptions\r\nfrom apps.commerce.models import Order\r\n\r\nraise ModelApiExceptionGenerator(Order, MAEBV.DoesNotExist)\r\nraise ModelApiExceptionGenerator(\r\n    Order\r\nMAEBV.AlreadyExists,\r\ncode = \"order_exists\",\r\nextra = {\"id\": 123}\r\n)\r\n\r\n# Available exception variants for models:\r\n# DoesNotExist, AlreadyExists, InvalidData, AccessDenied,\r\n# NotAcceptable, Expired, InternalServerError, AlreadyUsed,\r\n# NotUsed, NotAvailable, TemporarilyUnavailable, \r\n# ConflictDetected, LimitExceeded, DependencyMissing, Deprecated\r\n```\r\n\r\n### Serializers \ud83d\udd27\r\n\r\n`ADjango` extends `Django REST Framework` serializers to support asynchronous\r\noperations, making it easier to handle data in async views.\r\nSupport methods like `adata`, `avalid_data`, `ais_valid`, and `asave`.\r\n\r\n```python\r\nfrom adjango.querysets.base import AQuerySet\r\nfrom adjango.aserializers import (\r\n    AModelSerializer, ASerializer, AListSerializer\r\n)\r\nfrom adjango.serializers import dynamic_serializer\r\n\r\n...\r\n\r\n\r\nclass ConsultationPublicSerializer(AModelSerializer):\r\n    clients = UserPublicSerializer(many=True, read_only=True)\r\n    psychologists = UserPsyPublicSerializer(many=True, read_only=True)\r\n    config = ConsultationConfigSerializer(read_only=True)\r\n\r\n    class Meta:\r\n        model = Consultation\r\n        fields = '__all__'\r\n\r\n\r\n# From the complete serializer we cut off the pieces into smaller ones\r\nConsultationSerializerTier1 = dynamic_serializer(\r\n    ConsultationPublicSerializer, ('id', 'date',)\r\n)\r\nConsultationSerializerTier2 = dynamic_serializer(\r\n    ConsultationPublicSerializer, (\r\n        'id', 'date', 'psychologists', 'clients', 'config'\r\n    ), {\r\n        'psychologists': UserPublicSerializer(many=True),  # overridden\r\n    }\r\n)\r\n\r\n\r\n# Use it, in compact format\r\n@acontroller('Completed Consultations')\r\n@api_view(('GET',))\r\n@permission_classes((IsAuthenticated,))\r\nasync def consultations_completed(request):\r\n    page = int(request.query_params.get('page', 1))\r\n    page_size = int(request.query_params.get('page_size', 10))\r\n    return Response({\r\n        'results': await ConsultationSerializerTier2(\r\n            await request.user.completed_consultations[\r\n                  (page - 1) * page_size:page * page_size\r\n                  ].aall(),\r\n            many=True,\r\n            context={'request': request}\r\n        ).adata\r\n    }, status=200)\r\n\r\n\r\n...\r\n\r\n\r\nclass UserService(ABaseService['User']):\r\n    ...\r\n\r\n    @property\r\n    def completed_consultations(self) -> AQuerySet['Consultation']:\r\n        \"\"\"\r\n        Returns an optimized AQuerySet of all completed consultations of the user\r\n        (both psychologist and client).\r\n        \"\"\"\r\n        from apps.psychology.models import Consultation\r\n        now_ = now()\r\n        return Consultation.objects.defer(\r\n            'communication_type',\r\n            'language',\r\n            'reserved_by',\r\n            'notifies',\r\n            'cancel_initiator',\r\n            'original_consultation',\r\n            'consultations_feedbacks',\r\n        ).select_related(\r\n            'config',\r\n            'conference',\r\n        ).prefetch_related(\r\n            'clients',\r\n            'psychologists',\r\n        ).filter(\r\n            Q(\r\n                Q(clients=self.user) | Q(psychologists=self.user),\r\n                status=Consultation.Status.PAID,\r\n                date__isnull=False,\r\n                date__lt=now_,\r\n                consultations_feedbacks__user=self.user,\r\n            ) |\r\n            Q(\r\n                Q(clients=self) | Q(psychologists=self.user),\r\n                status=Consultation.Status.CANCELLED,\r\n                date__isnull=False,\r\n            )\r\n        ).distinct().order_by('-updated_at')\r\n\r\n    ...\r\n```\r\n\r\n### Management\r\n\r\n- `copy_project`\r\n  Documentation in the _py_ module itself - **[copy_project](adjango/management/commands/copy_project.py)**\r\n\r\nADjango ships with extra management commands to speed up project scaffolding.\r\n\r\n- `astartproject` \u2014 clones the [adjango-template](https://github.com/Artasov/adjango-template)\r\n  into the given directory and strips its Git history.\r\n\r\n  ```bash\r\n  django-admin astartproject myproject\r\n  ```\r\n\r\n- `astartup` \u2014 creates an app skeleton inside `apps/` and registers it in\r\n  `INSTALLED_APPS`.\r\n\r\n  ```bash\r\n  python manage.py astartup blog\r\n  ```\r\n\r\n  After running the command you will have the following structure:\r\n\r\n  ```sh\r\n  apps/\r\n      blog/\r\n          controllers/base.py\r\n          models/base.py\r\n          services/base.py\r\n          serializers/base.py\r\n          tests/base.py\r\n  ```\r\n\r\n- `newentities` \u2014 generates empty exception, model, service, serializer and\r\n  test stubs for the specified models in the target app.\r\n\r\n  ```bash\r\n  python manage.py newentities order apps.commerce Order,Product,Price\r\n  ```\r\n\r\n  Or create a single model:\r\n\r\n  ```bash\r\n  python manage.py newentities order apps.commerce Order\r\n  ```\r\n\r\n### Celery \ud83d\udd25\r\n\r\nADjango provides convenient tools for working with Celery: management commands, decorators, and task scheduler.\r\n\r\nFor Celery configuration in Django, refer to\r\nthe [official Celery documentation](https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html).\r\n\r\n#### Management Commands\r\n\r\n- `celeryworker` \u2014 starts Celery Worker with default settings\r\n\r\n  ```bash\r\n  python manage.py celeryworker\r\n  python manage.py celeryworker --pool=solo --loglevel=info -E\r\n  python manage.py celeryworker --concurrency=4 --queues=high_priority,default\r\n  ```\r\n\r\n- `celerybeat` \u2014 starts Celery Beat scheduler for periodic tasks\r\n\r\n  ```bash\r\n  python manage.py celerybeat\r\n  python manage.py celerybeat --loglevel=debug\r\n  ```\r\n\r\n- `celerypurge` \u2014 clears Celery queues from unfinished tasks\r\n\r\n  ```bash\r\n  python manage.py celerypurge               # clear all queues\r\n  python manage.py celerypurge --queue=high  # clear specific queue\r\n  ```\r\n\r\n#### @task Decorator\r\n\r\nThe `@task` decorator automatically logs Celery task execution, including errors:\r\n\r\n```python\r\nfrom celery import shared_task\r\nfrom adjango.decorators import task\r\n\r\n\r\n@shared_task\r\n@task(logger=\"global\")\r\ndef my_background_task(param1: str, param2: int) -> bool:\r\n    \"\"\"\r\n    Task with automatic execution logging.\r\n    \"\"\"\r\n    # your code here\r\n    return True\r\n```\r\n\r\n**What the decorator provides:**\r\n\r\n- \u2705 Automatic logging of task start and completion\r\n- \u2705 Logging of task parameters\r\n- \u2705 Detailed error logging with stack trace\r\n- \u2705 Flexible logger configuration for different tasks\r\n\r\n#### Tasker - Task Scheduler\r\n\r\nThe `Tasker` class provides convenient methods for scheduling and managing Celery tasks:\r\n\r\n```python\r\nfrom adjango.utils.celery.tasker import Tasker\r\n\r\n# Immediate execution\r\ntask_id = Tasker.put(task=my_task, param1='value')\r\n\r\n# Delayed execution (in 60 seconds)\r\ntask_id = Tasker.put(task=my_task, countdown=60, param1='value')\r\n\r\n# Execution at specific time\r\nfrom datetime import datetime\r\n\r\ntask_id = Tasker.put(\r\n    task=my_task,\r\n    eta=datetime(2024, 12, 31, 23, 59),\r\n    param1='value'\r\n)\r\n\r\n# Cancel task by ID\r\nTasker.cancel_task(task_id)\r\n\r\n# One-time task via Celery Beat (sync)\r\nTasker.beat(\r\n    task=my_task,\r\n    name='one_time_task',\r\n    schedule_time=datetime(2024, 10, 10, 14, 30),\r\n    param1='value'\r\n)\r\n\r\n# Periodic task via Celery Beat (sync)\r\nTasker.beat(\r\n    task=my_task,\r\n    name='hourly_cleanup',\r\n    interval=3600,  # every hour in seconds\r\n    param1='value'\r\n)\r\n\r\n# Crontab schedule via Celery Beat (sync)\r\nTasker.beat(\r\n    task=my_task,\r\n    name='daily_report',\r\n    crontab={'hour': 7, 'minute': 30},  # every day at 7:30 AM\r\n    param1='value'\r\n)\r\n\r\n# Async version of beat is also available\r\nawait Tasker.abeat(\r\n    task=my_task,\r\n    name='async_task',\r\n    interval=1800,  # every 30 minutes\r\n    param1='value'\r\n)\r\n```\r\n\r\n#### Email Sending via Celery\r\n\r\nADjango includes a ready-to-use task for sending emails with templates:\r\n\r\n```python\r\nfrom adjango.tasks import send_emails_task\r\nfrom adjango.utils.mail import send_emails\r\n\r\n# Synchronous sending\r\nsend_emails(\r\n    subject='Welcome!',\r\n    emails=('user@example.com',),\r\n    template='emails/welcome.html',\r\n    context={'user': 'John Doe'}\r\n)\r\n\r\n# Asynchronous sending via Celery\r\nsend_emails_task.delay(\r\n    subject='Hello!',\r\n    emails=('user@example.com',),\r\n    template='emails/hello.html',\r\n    context={'message': 'Welcome to our service!'}\r\n)\r\n\r\n# Via Tasker with delayed execution\r\nTasker.put(\r\n    task=send_emails_task,\r\n    subject='Reminder',\r\n    emails=('user@example.com',),\r\n    template='emails/reminder.html',\r\n    context={'deadline': '2024-12-31'},\r\n    countdown=3600  # send in an hour\r\n)\r\n```\r\n\r\n### Other\r\n\r\n- `AsyncAtomicContextManager`\ud83e\uddd8\r\n\r\n  An asynchronous context manager for working with transactions, which ensures the atomicity of operations.\r\n\r\n    ```python\r\n    from adjango.utils.base import AsyncAtomicContextManager\r\n    \r\n    async def some_function():\r\n        async with AsyncAtomicContextManager():\r\n            ...  \r\n    ```\r\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A library with many features for interacting with Django",
    "version": "0.8.0",
    "project_urls": {
        "Homepage": "https://github.com/Artasov/adjango",
        "Source": "https://github.com/Artasov/adjango",
        "Tracker": "https://github.com/Artasov/adjango/issues"
    },
    "split_keywords": [
        "adjango",
        "django",
        "utils",
        "funcs",
        "features",
        "async",
        "managers",
        "services"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6db9cda9bbfe9253fabd769318f27c253768a9afd524efea2200081730e4bf34",
                "md5": "3af90d6aaf15bf89ec98269a9f98afa2",
                "sha256": "23a6c0879b583c807e83df7e1fadea7571b87f30b713c4e07541a2affe2c05ab"
            },
            "downloads": -1,
            "filename": "adjango-0.8.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3af90d6aaf15bf89ec98269a9f98afa2",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.12",
            "size": 61622,
            "upload_time": "2025-08-27T02:05:51",
            "upload_time_iso_8601": "2025-08-27T02:05:51.860038Z",
            "url": "https://files.pythonhosted.org/packages/6d/b9/cda9bbfe9253fabd769318f27c253768a9afd524efea2200081730e4bf34/adjango-0.8.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "965eb157dd6229c16324788c101a675f4674571e9c1e2e79d660d0122e55e748",
                "md5": "5293e05fb793caf42393c7f3ad4507ce",
                "sha256": "6dcca0f5111585d31c65b65d51fd672ba69f8a25aa044ca5309be709738e81ca"
            },
            "downloads": -1,
            "filename": "adjango-0.8.0.tar.gz",
            "has_sig": false,
            "md5_digest": "5293e05fb793caf42393c7f3ad4507ce",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.12",
            "size": 67996,
            "upload_time": "2025-08-27T02:05:53",
            "upload_time_iso_8601": "2025-08-27T02:05:53.579669Z",
            "url": "https://files.pythonhosted.org/packages/96/5e/b157dd6229c16324788c101a675f4674571e9c1e2e79d660d0122e55e748/adjango-0.8.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-27 02:05:53",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Artasov",
    "github_project": "adjango",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": false,
    "requirements": [],
    "lcname": "adjango"
}
        
Elapsed time: 2.67316s