Skip to content

Commit

Permalink
add more docs, fix ObfuscatedFieldMixin
Browse files Browse the repository at this point in the history
  • Loading branch information
leondaz committed Dec 13, 2023
1 parent 202ea14 commit a1730b0
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 7 deletions.
181 changes: 175 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
# drf-common


## Installation

- `pip install commonkit`

### Examples
## Examples

#### Obfuscation

```python
from commonkit.utils import Obfuscator

Obfuscator.email("[email protected]") # 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
Expand Down Expand Up @@ -51,7 +62,7 @@ class HumanSerializer(serializers.ModelSerializer):
]
```

## Example serialization
#### Example serialization

```python
# wherever.py
Expand All @@ -62,7 +73,7 @@ human = Human.objects.find(id=1)
serializer = HumanSerializer(human) # level = BEGINNER, military_status = EXEMPTED
```

## Example deserialization
#### Example deserialization

```python
# wherever.py
Expand All @@ -78,10 +89,168 @@ 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 commonkit.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 commonkit.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 commonkit.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 commonkit.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.

- commonkit 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 commonkit.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": [
"commonkit.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
To run the project locally

- `pip install -r requirements.txt`
- `pip install poetry`
- `poetry install`
- `poetry shell`
- `pre-commit install`

To build the docs
Expand Down
1 change: 1 addition & 0 deletions commonkit/rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)

def to_representation(self, value):
value = super().to_representation(value)
return self.obfuscator.obfuscate(
value, cutoff=self.cutoff, from_end=self.from_end, char=self.char
)
Expand Down
3 changes: 2 additions & 1 deletion commonkit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ def obfuscate(cls, s: str, char="*", cutoff=4, from_end=True):
return char * len(s)

if from_end:
# note that reverse indices need to be incremented in slicing
index = -cutoff - 1
else:
index = cutoff

after_cutoff, before_cutoff = s[index:], s[:index]
return (
(before_cutoff + cutoff * char)
(before_cutoff + (cutoff + 1) * char)
if from_end
else (char * cutoff + after_cutoff)
)
Expand Down
Empty file.

0 comments on commit a1730b0

Please sign in to comment.