# 🚀 ADjango
> Sometimes I use this in different projects, so I decided to put it on pypi
`ADjango` is a convenient library for simplifying work with Django DRF and other,
which offers various useful `managers`, `services`, `serializers`, `decorators`, utilities
for `asynchronous` programming, a task scheduler for Celery, working
with `transactions` and much more.
- [Installation](#installation-%EF%B8%8F)
- [Settings](#settings-%EF%B8%8F)
- [Overview](#overview)
- [Manager & Services](#manager--services-%EF%B8%8F)
- [Utils](#utils-)
- [Decorators](#decorators-)
- [Serializers](#serializers-)
- [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.polymorphic_models import APolymorphicModel
class User(AbstractUser, ABaseService):
objects = AManager()
# Its equal with...
class User(AbstractUser, AModel): pass
class Product(APolymorphicModel):
# APolymorphicManager() of course here already exists
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.related('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`, и так далее доступны как отдельные функции
```python
from adjango.utils.funcs import aall, agetorn, afilter, aset, aadd, arelated
```
### 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.
* `atomic`
An asynchronous decorator that wraps
function into a transactional context. If an exception occurs, all changes are rolled back.
* `acontroller/controller`
An asynchronous decorator that wraps
function into a transactional context. If an exception occurs, all changes are rolled back.
```python
from adjango.adecorators import acontroller
@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
```
* These decorators automatically catch uncaught exceptions and log if the logger is configured
`ADJANGO_CONTROLLERS_LOGGER_NAME` `ADJANGO_CONTROLLERS_LOGGING`.
* 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
```
### 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 create_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 = create_dynamic_serializer(
ConsultationPublicSerializer, ('id', 'date',)
)
ConsultationSerializerTier2 = create_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:
...
@property
def completed_consultations(self: 'User') -> 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) | Q(psychologists=self),
status=Consultation.Status.PAID,
date__isnull=False,
date__lt=now_,
consultations_feedbacks__user=self,
) |
Q(
Q(clients=self) | Q(psychologists=self),
status=Consultation.Status.CANCELLED,
date__isnull=False,
)
).distinct().order_by('-updated_at')
...
```
### 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():
...
```
* `Tasker`📋
The Tasker class provides methods for scheduling tasks in `Celery` and `Celery Beat`.
```python
from adjango.utils.tasks import Tasker
task_id = Tasker.put(
task=my_celery_task,
param1='value1',
param2='value2',
countdown=60 # The task will be completed in 60 seconds
)
```
```python
from adjango.utils.tasks import Tasker
from datetime import datetime
# One-time task via Celery Beat
Tasker.beat(
task=my_celery_task,
name='one_time_task',
schedule_time=datetime(2024, 10, 10, 14, 30), # Start the task on October 10, 2024 at 14:30
param1='value1',
param2='value2'
)
# Periodic task via Celery Beat (every hour)
Tasker.beat(
task=my_celery_task,
name='hourly_task',
interval=3600, # The task runs every hour
param1='value1',
param2='value2'
)
```
* `send_emails`
Allows you to send emails using templates and context rendering.
```python
from adjango.utils.mail import send_emails
send_emails(
subject='Welcome!',
emails=('user1@example.com', 'user2@example.com'),
template='emails/welcome.html',
context={'user': 'John Doe'}
)
```
```python
from adjango.tasks import send_emails_task
from adjango.utils.tasks import Tasker
send_emails_task.delay(
subject='Hello!',
emails=('user@example.com',),
template='emails/hello.html',
context={'message': 'Welcome to our service!'}
)
# or
Tasker.put(
task=send_emails_task,
subject='Hello!',
emails=('user@example.com',),
template='emails/hello.html',
context={'message': 'Welcome to our service!'},
countdown=60 # The task will be completed in 5 seconds
)
```
Raw data
{
"_id": null,
"home_page": "https://github.com/Artasov/adjango",
"name": "adjango",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"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/bb/7e/e4bab6ea6cc2e72d00e992e486807cdb27d89f986b7831900b2c5b6eadfd/adjango-0.3.5.tar.gz",
"platform": null,
"description": "# \ud83d\ude80 ADjango \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 convenient library for simplifying work with Django DRF and other,\r\nwhich offers various useful `managers`, `services`, `serializers`, `decorators`, utilities \r\nfor `asynchronous` programming, a task scheduler for Celery, working \r\nwith `transactions` and much more.\r\n- [Installation](#installation-%EF%B8%8F)\r\n- [Settings](#settings-%EF%B8%8F)\r\n- [Overview](#overview)\r\n - [Manager & Services](#manager--services-%EF%B8%8F)\r\n - [Utils](#utils-)\r\n - [Decorators](#decorators-)\r\n - [Serializers](#serializers-)\r\n - [Other](#other)\r\n\r\n## Installation \ud83d\udee0\ufe0f\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 ```python\r\n INSTALLED_APPS = [\r\n #...\r\n 'adjango',\r\n ]\r\n ```\r\n* ### In `settings.py` set the params\r\n ```python\r\n # settings.py\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 ```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## Overview\r\nMost functions, if available in asynchronous form, are also available in synchronous form.\r\n\r\n### Manager & Services \ud83d\udece\ufe0f\r\nA simple example and everything is immediately clear...\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.polymorphic_models import APolymorphicModel\r\n\r\nclass User(AbstractUser, ABaseService):\r\n objects = AManager()\r\n# Its equal with...\r\nclass User(AbstractUser, AModel): pass\r\n \r\n\r\nclass Product(APolymorphicModel):\r\n # APolymorphicManager() of course here already exists\r\n name = CharField(max_length=100)\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.related('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### Utils \ud83d\udd27\r\n `aall`, `afilter`, `arelated`, \u0438 \u0442\u0430\u043a \u0434\u0430\u043b\u0435\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u043a\u0430\u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438\r\n ```python\r\n from adjango.utils.funcs import aall, agetorn, afilter, aset, aadd, arelated\r\n ```\r\n### Decorators \ud83c\udf80\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* `atomic`\r\n\r\n An asynchronous decorator that wraps \r\n function into a transactional context. If an exception occurs, all changes are rolled back.\r\n\r\n* `acontroller/controller`\r\n\r\n An asynchronous decorator that wraps \r\n function into a transactional context. If an exception occurs, all changes are rolled back.\r\n ```python\r\n from adjango.adecorators import acontroller\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 * These decorators automatically catch uncaught exceptions and log if the logger is configured \r\n `ADJANGO_CONTROLLERS_LOGGER_NAME` `ADJANGO_CONTROLLERS_LOGGING`. \r\n * You can also implement the interface:\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 and use `handle` to get an uncaught exception:\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### Serializers \ud83d\udd27\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```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 create_dynamic_serializer\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# From the complete serializer we cut off the pieces into smaller ones\r\nConsultationSerializerTier1 = create_dynamic_serializer(\r\n ConsultationPublicSerializer, ('id', 'date',)\r\n)\r\nConsultationSerializerTier2 = create_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# 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\nclass UserService:\r\n ...\r\n @property\r\n def completed_consultations(self: 'User') -> 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) | Q(psychologists=self),\r\n status=Consultation.Status.PAID,\r\n date__isnull=False,\r\n date__lt=now_,\r\n consultations_feedbacks__user=self,\r\n ) |\r\n Q(\r\n Q(clients=self) | Q(psychologists=self),\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### 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 ```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\r\n* `Tasker`\ud83d\udccb\r\n\r\n The Tasker class provides methods for scheduling tasks in `Celery` and `Celery Beat`.\r\n ```python\r\n from adjango.utils.tasks import Tasker\r\n \r\n task_id = Tasker.put(\r\n task=my_celery_task,\r\n param1='value1',\r\n param2='value2',\r\n countdown=60 # The task will be completed in 60 seconds\r\n )\r\n ```\r\n ```python\r\n from adjango.utils.tasks import Tasker\r\n from datetime import datetime\r\n \r\n # One-time task via Celery Beat\r\n Tasker.beat(\r\n task=my_celery_task,\r\n name='one_time_task',\r\n schedule_time=datetime(2024, 10, 10, 14, 30), # Start the task on October 10, 2024 at 14:30\r\n param1='value1',\r\n param2='value2'\r\n )\r\n \r\n # Periodic task via Celery Beat (every hour)\r\n Tasker.beat(\r\n task=my_celery_task,\r\n name='hourly_task',\r\n interval=3600, # The task runs every hour\r\n param1='value1',\r\n param2='value2'\r\n )\r\n ```\r\n\r\n* `send_emails`\r\n\r\n Allows you to send emails using templates and context rendering.\r\n ```python\r\n from adjango.utils.mail import send_emails\r\n \r\n send_emails(\r\n subject='Welcome!',\r\n emails=('user1@example.com', 'user2@example.com'),\r\n template='emails/welcome.html',\r\n context={'user': 'John Doe'}\r\n )\r\n ```\r\n ```python\r\n from adjango.tasks import send_emails_task\r\n from adjango.utils.tasks import Tasker\r\n \r\n send_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 # or\r\n Tasker.put(\r\n task=send_emails_task,\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 countdown=60 # The task will be completed in 5 seconds\r\n )\r\n ```\r\n",
"bugtrack_url": null,
"license": null,
"summary": "A library with many features for interacting with Django",
"version": "0.3.5",
"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": "",
"digests": {
"blake2b_256": "acfb7bbd729e0bc51803a5c24bd0421ce5d239f83c9ef6a392daa8ebd8234c9d",
"md5": "47f03f1a0b38dc1ae3eabe546941ba2d",
"sha256": "d6c47c766c03c5e44746b22cf01c8fa968fd08d6c64c79be161168c15b69ef25"
},
"downloads": -1,
"filename": "adjango-0.3.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "47f03f1a0b38dc1ae3eabe546941ba2d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 42623,
"upload_time": "2024-12-27T23:33:58",
"upload_time_iso_8601": "2024-12-27T23:33:58.470643Z",
"url": "https://files.pythonhosted.org/packages/ac/fb/7bbd729e0bc51803a5c24bd0421ce5d239f83c9ef6a392daa8ebd8234c9d/adjango-0.3.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "bb7ee4bab6ea6cc2e72d00e992e486807cdb27d89f986b7831900b2c5b6eadfd",
"md5": "3b08feed4f7c731551950cdb7a55ec99",
"sha256": "9e9397dfcbd3c6bffd42ec5d0cd1af26881bb7703b77907ff49634d6cad81053"
},
"downloads": -1,
"filename": "adjango-0.3.5.tar.gz",
"has_sig": false,
"md5_digest": "3b08feed4f7c731551950cdb7a55ec99",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 33955,
"upload_time": "2024-12-27T23:34:01",
"upload_time_iso_8601": "2024-12-27T23:34:01.022493Z",
"url": "https://files.pythonhosted.org/packages/bb/7e/e4bab6ea6cc2e72d00e992e486807cdb27d89f986b7831900b2c5b6eadfd/adjango-0.3.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-27 23:34:01",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Artasov",
"github_project": "adjango",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [],
"lcname": "adjango"
}