Metadata-Version: 2.1
Name: djkit
Version: 1.0.9
Summary: A library for handling common stuff in Django
License: MIT
Author: leondaz
Author-email: ahmeddark369@gmail.com
Requires-Python: >=3.9,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Provides-Extra: django
Provides-Extra: drf
Provides-Extra: pandas
Provides-Extra: polars
Requires-Dist: django (>=4.0,<5.0) ; extra == "django"
Requires-Dist: djangorestframework (>=3.10.0,<4.0.0) ; extra == "drf"
Requires-Dist: openpyxl (>=3.0.0,<4.0.0) ; extra == "pandas"
Requires-Dist: pandas (>=2.0.0,<3.0.0) ; extra == "pandas"
Requires-Dist: polars (>=0.20.2,<0.21.0) ; extra == "polars"
Requires-Dist: xlsx2csv (==0.8.2) ; extra == "polars"
Requires-Dist: xlsxwriter (>=3.1.0,<4.0.0) ; extra == "pandas"
Description-Content-Type: text/markdown

# DjKit

## Installation

- `pip install djkit`

## Examples

#### Obfuscation

```python
from djkit.utils import Obfuscator

Obfuscator.email("abcdqweqwe@test.com")  # abcdq****@test.com'
Obfuscator.obfuscate('my_super_confidential_secret')  # 'my_super_confidential_s*****'

```


#### Django rest framework related examples
```python
# models.py
from django.db import models


class Human(models.Model):
    class Level(models.IntegerChoices):
        BEGINNER = 0
        INTERMEDIATE = 1
        ADVANCED = 2

    class MilitaryStatus(models.TextChoices):
        EXEMPTED = "exempted", "Exempted"
        SERVED = "served", "Served"
        POSTPONED = "postponed", "Postponed"

    name = models.CharField(max_length=128)
    level = models.IntegerField(choices=Level.choices)
    military_status = models.CharField(
        choices=MilitaryStatus.choices,
        max_length=128
    )
```

```python
# serializers.py
from rest_framework import serializers
from djkit.rest_framework.serializers import EnumSerializer
from example.core import models


class HumanSerializer(serializers.ModelSerializer):
    level = EnumSerializer(enum=models.Human.Level)
    military_status = EnumSerializer(enum=models.Human.MilitaryStatus)

    class Meta:
        model = models.Human
        fields = [
            'id',
            'level',
            'military_status',
        ]
```

#### Example serialization

```python
# wherever.py
from example.core.serializers import HumanSerializer
from example.core.models import Human

human = Human.objects.find(id=1)
serializer = HumanSerializer(human)  # level = BEGINNER, military_status = EXEMPTED
```

#### Example deserialization

```python
# wherever.py
from example.core.serializers import HumanSerializer

data = {
    "level": "BEGINNER",
    "MILITARY_STATUS": "EXEMPTED",
}

instance = HumanSerializer(data=data)
instance.is_valid()
instance.save()
```

#### Got some endpoints that accept files in table formats? got you

1. Assuming Pandas

```python
import pandas as pd
from djkit.rest_framework.pandas import TableUploadField  # or PandasTableUploadField
from rest_framework import serializers


class MySerializer(serializers.ModelSerializer):
    file = TableUploadField()

    def create(self, validated_data):
        file: pd.DataFrame = validated_data['file']
        # do logic, don't do row based validation here
        return validated_data

    # optional method
    def validate_file_row(self, row, index, table_df):
        if row.iloc[0] == "X":
            row.iloc[0] = "Y"

        # return new row, or None and changes won't be reflected
        return row
```

2. Assuming Pola.rs

```python
import polars as pl
from djkit.rest_framework.polars import TableUploadField  # or PolarsTableUploadField
from rest_framework import serializers


class MySerializer(serializers.ModelSerializer):
    file = TableUploadField()

    def create(self, validated_data):
        file: pl.DataFrame = validated_data['file']
        # do logic, don't do row based validation here
        return validated_data

    # optional method
    def validate_file_row(self, row, index, table_df):
        # do polars logic, return new row or None
        return row
```

- `TableUploadField` is `write_only`
- TableUploadField is easily extended, use your own library if you want

```python
from djkit.rest_framework.serializers import TableUploadField as BaseTableUploadField


def read_csv(source):
    return ...


def read_excel(source):
    return ...


class TableUploadField(BaseTableUploadField):
    handlers = {
        "csv": read_csv,
        "xlsx": read_excel,
        "xls": read_excel,
        "xlsm": read_excel,
        "xlsb": read_excel,
        "odf": read_excel,
        "ods": read_excel,
        "odt": read_excel,
    }

    def update_row(self, table_object: "YourLibraryDataFrame", index, new_row):
        """how your library updates the row"""


```

#### Supported formats are the keys in handlers

```python
can_parse_csv = "csv" in field.allowed_upload_formats
```

#### Or
```python
can_parse_csv = field.is_allowed_format('obj') # False
```

#### Custom kwargs for handlers in TableFieldUpload

```python
from djkit.rest_framework.pandas import PandasTableUploadField
import pandas as pd


class Serializer(...):
    pandas_file = PandasTableUploadField(handler_kwargs={
        # by format
        "xlsx": {
            # args for read_excel would be here
            "engine": "openpyxl"
        },
        # or by reference
        pd.read_csv: {
            "delimiter": "\t"
        }
    })
```

- The same logic applies to all `TableUploadField` subclasses.

- djkit provides easily methods for overriding most of the logic.
- If you want a field that does aggregation or something, override `process_table` on `TableUploadField`


#### Obfuscation

```python
from djkit.rest_framework.serializers import ObfuscatedCharField, ObfuscatedEmailField, ObfuscatedFieldMixin


class MySerializer(...):
    email = ObfuscatedEmailField()  # in the API, it's obfuscated
    name = ObfuscatedCharField()  # same applies here


class MyCustomObfuscatedApiKeyField(ObfuscatedFieldMixin, MyApiKeyField):
    pass

```


#### Better error handling
```python

REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES": [
        "djkit.rest_framework.renderers.JSONRenderer"
    ]
}
```

This will change you response schema in case of errors to

```python
response = {
    "field_errors": [],
    "non_field_errors": []
}
```

## Contributions

To run the project locally

- `pip install poetry`
- `poetry install`
- `poetry shell`
- `pre-commit install`

To build the docs

- `make html`

To run the tests

- `pytest .`

