Giter VIP home page Giter VIP logo

djapy's Introduction

Hi ๐Ÿ‘‹, I'm Bishwas Bhandari

Just a simple passionate Full-Stack developer from Nepal

  • ๐Ÿ”ญ Iโ€™m currently working on BlogStorm.AI as a Senior Full-Stack Developer

  • ๐Ÿ‘ฏ Iโ€™m looking to collaborate on Djapy and Tipex. (Open Source)

๐Ÿ’ฌ Ask me about SvelteKit, Python, Django and Tailwind CSS

My Resume | My Portfolio | Mail Me | Djapy.IO

djapy's People

Contributors

bishwas-py avatar tejmagar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

djapy's Issues

Optional[str] accepted not accepted as queriable type (accepted as body)

search_title: Optional[str] is supposed to be an optional query type, not request body type, but djapy is make is request body type.

from datetime import datetime
from typing import Optional

from djapy import djapify, Schema


class TodoItemSchema(Schema):
    title: str
    description: str
    completed_at: Optional[datetime]
    created_at: Optional[datetime]
    updated_at: Optional[datetime]


@djapify
def get_todo_items(request, search_title: Optional[str]) -> {200: list[TodoItemSchema]}:
    from .models import TodoItem
    todo_items = TodoItem.objects.all()
    return todo_items

Add `validation_info` and `context` in `@computed_field` within `SourceAble`

Basically getting context in @computed_field is not doable right now, if the condition is similar to below:

class AssignLikeSchema(SourceAble):

    @computed_field
    def assign_likes(self) -> SimpleLikeSchema:
        likes = PolymorphicLike.get_object_likes(self._source_obj).alive()
        return {
            'like_count': likes.count(),
            'have_self': likes.filter(user=self._context['request'].user).exists() if self._context[
                'request'].user.is_authenticated else False,
            'last_like': likes.last() if likes.exists() else None,
        }

this can be achieved with:

class SourceAble(BaseModel):
    """
    Allows the model to have a source object.
    """
    _source_obj: Any | None = None
    _validation_info: ValidationInfo | None = None
    _context: dict | None = None

    @model_validator(mode="wrap")
    def __validator__(cls, val: Any, next_: typing.Callable[[Any], typing.Self],
                      validation_info: ValidationInfo) -> typing.Self:
        obj = next_(val)
        obj._source_obj = val
        obj._validation_info = validation_info
        obj._context = validation_info.context

        return obj

Add tests to the project

Currently we lack some tests in the djapy project. Adding some would definitely be a good step.

Auth mechanism added

core/auth contains the mechanism for auth.

class BaseAuthMechanism:
    def __init__(self, permissions: list[str] = None, message_response: dict = None, *args, **kwargs):
        self.message_response = message_response or {"message": "Unauthorized"}
        self.permissions = permissions

    def authenticate(self, request: HttpRequest, *args, **kwargs):
        pass

    def authorize(self, request: HttpRequest, *args, **kwargs):
        pass

    def schema(self):
        return {}

    def app_schema(self):
        return {}

    def set_message_response(self, message_response: dict):
        self.message_response = message_response

BaseAuthMechanism can be inherited and new auth mechanism can be written.

In path params hamper the object level, and converts the objects into dict, rather than objects.

path('<int:todo_id>/update', views.update_todo_item, name='update-todo-item'),

class TodoItemUpdateSchema(Schema):
    title: Optional[str] = None
    description: Optional[str] = None
    will_be_completed_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None

@djapify(allowed_method="PUT")
@djapy_auth(AUTH_MECHANISM, permissions=["todo.change_todoitem"])
def update_todo_item(request, todo_id: int, data: TodoItemUpdateSchema) -> TodoItemSchema:
    todo_item = TodoItem.objects.get(id=todo_id)
    if data.title:
        todo_item.title = data.title
    if data.description:
        todo_item.description = data.description
    if data.completed_at:
        todo_item.completed_at = data.completed_at
    if data.will_be_completed_at:
        todo_item.will_be_completed_at = data.will_be_completed_at
    todo_item.save()
    return todo_item

In this situation, todo_id works well, but data gets accepted only with a second level child:

"data": {
   "title": "XYZ"
    ...
}

Also, data acts as dict rather than object after being parsed. data.title is not possible, but data['title'] is.

# this is the input parsed:
{'title': 'Django testing #2', 'description': None, 'will_be_completed_at': datetime.datetime(2024, 3, 11, 23, 17, tzinfo=TzInfo(UTC)), 'completed_at': datetime.datetime(2024, 3, 2, 5, 35, tzinfo=TzInfo(UTC))}

Nested Schema Support

I am new to Python, and I'm facing the following nested Schema issue:. I don't know if this is my issue or Djapy's parser.

Schema

class Gender(str, Enum):
    male = "male"
    female = "female"


class PersonSchema(Schema):
    id: int
    name: str
    gender: Gender
    place_of_birth: Optional[str]
    date_of_birth: date


class FamilySchema(Schema):
    id: int
    name: str
    level: int
    members: Optional[PersonSchema]

Model

class Family(models.Model):
    class Meta:
        ordering = ["name"]
        verbose_name_plural = "Families"

    name = models.CharField(max_length=100)
    members = models.ManyToManyField(Person, related_name="families")
    level = models.PositiveIntegerField()

    deleted = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return f"{self.name} - Level {self.level}"

View code

@djapify(allowed_method="GET", tags=["Family Tree"])
def get_all_families(request) -> {200: List[FamilySchema], 404: MessageOut, 500: MessageOut}:  # type: ignore
    """
    Retrieve all families that are not deleted.
    """
    try:
        families: List[FamilySchema] = Family.objects.all()
        # Print first family details
        logIt(
            "๐Ÿš€ family_tree/views.py ~ get_all_families ~ families[0]",
            families[0]
        )
        logIt(
            "๐Ÿš€ family_tree/views.py ~ get_all_families ~ families.count",
            families.count(),
        )
        if families.count() > 0:
            return 200, families
        else:
            logIt(
                "๐Ÿš€ family_tree/views.py ~ get_all_families ~ No families found",
                " No families found",
            )
            return 404, MessageOut(
                "No families found",
                "no_families_found",
                "error",
            )
    except Exception as e:
        return 500, MessageOut(
            str(e),
            "database_error",
            "error",
        )

Output on hitting the endpoint

๐Ÿš€ family_tree/views.py ~ get_all_families ~ families[0] ~ Test - 55 - Level 55
๐Ÿš€ family_tree/views.py ~ get_all_families ~ families.count ~ 2
ERROR:root:Unable to serialize unknown type: <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
Traceback (most recent call last):
  File "/run/media/nirdesh/Nirdesh/python/blog-django/benv/lib64/python3.12/site-packages/djapy/core/dec.py", line 242, in _wrapped_view
    parsed_data = parser.parse_response_data()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/run/media/nirdesh/Nirdesh/python/blog-django/benv/lib64/python3.12/site-packages/djapy/core/parser.py", line 105, in parse_response_data
    destructured_object_data = validated_obj.model_dump(mode="json", by_alias=True)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/run/media/nirdesh/Nirdesh/python/blog-django/benv/lib64/python3.12/site-packages/pydantic/main.py", line 314, in model_dump
    return self.__pydantic_serializer__.to_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
Internal Server Error: /family/families
ERROR:django.request:Internal Server Error: /family/families

Returning empty query set results `Input should be a valid dictionary or object to extract fields from`

Let's assume below view_func returns empty data:

@djapify
@paginate(OffsetLimitPagination)
def get_post_comments(request, source_id: int, **kwargs) -> list[CommentSchema]:
    post = Post.objects.get(id=source_id)
    comments_and_replies = PolymorphicComments.objects.filter(
        parent_type=ContentType.objects.get_for_model(post),
        parent_id=post.id
    )
    return comments_and_replies.alive()
{"error": [{"type": "model_attributes_type", "loc": ["response"], "msg": "Input should be a valid dictionary or object to extract fields from", "input": [], "url": "https://errors.pydantic.dev/2.5/v/model_attributes_type"}], "error_count": 1, "title": "output"}

Can be fixed by:

In pagination/offset_pagination.py, line 46, replace return [] with:

return {
                    "items": [],
                    "offset": offset,
                    "limit": limit,
                    "has_next": False,
                    "has_previous": False,
                    "total_pages": 0,
                }

Add `raise MessageOut` schemas in OpenAPI schemas.

Current it's

class MessageOut(Exception):
    message = None
    alias = None
    message_type = None

    def __init__(self, message, alias, message_type):
        self.message = message
        self.alias = alias
        self.message_type = message_type

so we want a feature that would add the attributable schemas of MessageOut to OpenAPI, and it should be able to detect schemas from every_inside_funtion.

def get_by_id(todo_id):
    try:
        todo_item = TodoItem.objects.get(id=todo_id)
        return todo_item
    except:
        raise MessageOut(
            "Todo item not found",
            "todo_item_not_found",
            "error"
        )


@djapify
def get_todo_item(request, todo_id: int) -> TodoItemSchema:
    todo_item = get_by_id(todo_id)

    if todo_item.is_owner(request.user):
        return todo_item
    raise MessageOut(
        "You are not allowed to view this item",
        "todo_item_not_found",
        "error"
    )

possible approach, using inspect API to get all raises and comparing them with a base class, and assigning it to the schema.

QuerySet to QueryList easy validation

Till now, we had to convert many to many field to an queryset

    @field_validator('feedback_options', mode='before')
    def validate_feedback_options(cls, feedback_options: QuerySet[FeedbackOption]):
        return [i for i in feedback_options.all()]

Automatically, validate Django's all fields with QueryList. But a QueryList converter would be easy.

def query_list_validator(value):
    """
    Validator to ensure the Django model queryset is not empty.
    """
    return value.all()


QueryList = Annotated[List[G_TYPE], BeforeValidator(query_list_validator)]

Accept `constr` as query type

@djapify
@djapy_method("POST")
@csrf_exempt
def long_tail_keyword(_r, data: TagsInput, app: constr(min_length=1)) -> dict:
    return {
        'keywords': get_serp_data(data.keyword)
    }

app: constr(min_length=1) places the string to data/payload type, it should be query.

Add custom computed fields, not related to the model object

Creation of custom data should be allowed:

If posts = Post.objects.all().alive() is passed to List[PostSchema]:

class PostSchema(Schema, ):  # , AssignLikeSchema, AssignCommentSchema):
    id: int
    updated_at: datetime
    soft_deleted_at: datetime | None = None
    id: int
    title: str
    slug: str
    body: str
    tags: list[TagsSchema]
    # comments: SimpleCommentSchema # comments is not an attribute of post object

    @computed_field
    def comments(self, obj: 'model.Post') -> SimpleCommentSchema: # currently @computed_field do not allow objects to be passed
        comments = PolymorphicComments.objects.filter(
            parent_id=obj.id,
            parent_type=ContentType.objects.get_for_model(obj)).alive()
        return {
            'comment_count': comments.count(),
            'last_comment': comments.last() if comments.exists() else None,
        }
        return {
            'comment_count': 0,
            'last_comment': None
        }

Accept multiple `list/set` data for form request

Right now we are converting the QueryDict to a plain python dict using

            if self.request.POST:
                self.data.update(self.request.POST.dict())

and RequestDataParser>parse_request_data is used to assign request data. But in this process multiple data sent from client is lost.

<form method="post">
<select name="participant_ids">...</select>
<select name="participant_ids">...</select>
</form>

only one participants is accepted from the form request.

If schema is or similar:

participant_ids: conset(int, max_length=2)

then it should have the both ids on it.

ImportError: cannot import name 'Unpack' from 'typing'

I am getting for Type issue in Djapy. It probably be issue because of python version or so.

File "/home/ubuntu/my-app/backend/api/generator/urls.py", line 3, in <module>
    from . import views
  File "/home/ubuntu/my-app/backend/api/generator/views.py", line 9, in <module>
    from djapy.wrappers.dec import node_to_json_response, object_to_json_node
  File "/home/ubuntu/.local/lib/python3.10/site-packages/djapy/wrappers/dec.py", line 4, in <module>
    import djapy.utils.types
  File "/home/ubuntu/.local/lib/python3.10/site-packages/djapy/utils/types.py", line 1, in <module>
    from typing import TypedDict, Unpack
ImportError: cannot import name 'Unpack' from 'typing' (/usr/lib/python3.10/typing.py)

Python version:

Python 3.10.12

`@computed_field`'s schema should be visible in Swagger/OpenAPI

Currently,


class SimpleCommentSchema(TypedDict):
    comment_count: int | None
    last_comment: CommentSchema | None

@computed_field
    def comments(self, *args, **kwargs) -> SimpleCommentSchema:
        comments = PolymorphicComments.objects.filter(
            parent_id=self._source_obj.id,
            parent_type=ContentType.objects.get_for_model(self._source_obj)).alive()
        print(comments.count())
        return {
            'comment_count': comments.count(),
            'last_comment': comments.last() if comments.exists() else None,
        }

like fields' schema is not being address in Swagger.

FIx: pydantic/pydantic#6298

Pagination mechanism needed

Something of this syntax. And it could be able to apply pagination details in swagger/OpenAPI too.

@djapify
@djapily_paginated(OffsetLimitPagination)
def related_post(request, search_topic: str) -> {200: list[UserSchema], 401: ErrorMessage}:
    posts = Post.objects.filter(topic__icontains=search_topic)
    return 200, posts

[Swagger/OpenAPI] Nested urls not being concadinated.

Swagger not showing nested urls.

image

from django.contrib import admin
from django.urls import path, include
from djapy import openapi

urlpatterns = [
    path('todos/', include('todo.urls'), name="todos"),
    path('admin/', admin.site.urls),
    path('', openapi.urls),
]
# todo/urls.py
from django.urls import path

from todo import views

urlpatterns = [
    path('todo', views.get_todo_items, name='get-todo-items'),
]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.