Skip to content

Commit 8528aa7

Browse files
committed
first commit
1 parent edaffcc commit 8528aa7

File tree

14 files changed

+667
-1
lines changed

14 files changed

+667
-1
lines changed

.gitignore

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
2+
# Created by https://www.toptal.com/developers/gitignore/api/python
3+
# Edit at https://www.toptal.com/developers/gitignore?templates=python
4+
5+
### Python ###
6+
# Byte-compiled / optimized / DLL files
7+
__pycache__/
8+
*.py[cod]
9+
*$py.class
10+
11+
# C extensions
12+
*.so
13+
14+
# Distribution / packaging
15+
.Python
16+
build/
17+
develop-eggs/
18+
dist/
19+
downloads/
20+
eggs/
21+
.eggs/
22+
lib/
23+
lib64/
24+
parts/
25+
sdist/
26+
var/
27+
wheels/
28+
share/python-wheels/
29+
*.egg-info/
30+
.installed.cfg
31+
*.egg
32+
MANIFEST
33+
34+
# PyInstaller
35+
# Usually these files are written by a python script from a template
36+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
37+
*.manifest
38+
*.spec
39+
40+
# Installer logs
41+
pip-log.txt
42+
pip-delete-this-directory.txt
43+
44+
# Unit test / coverage reports
45+
htmlcov/
46+
.tox/
47+
.nox/
48+
.coverage
49+
.coverage.*
50+
.cache
51+
nosetests.xml
52+
coverage.xml
53+
*.cover
54+
*.py,cover
55+
.hypothesis/
56+
.pytest_cache/
57+
cover/
58+
59+
# Translations
60+
*.mo
61+
*.pot
62+
63+
# Django stuff:
64+
*.log
65+
local_settings.py
66+
db.sqlite3
67+
db.sqlite3-journal
68+
69+
# Flask stuff:
70+
instance/
71+
.webassets-cache
72+
73+
# Scrapy stuff:
74+
.scrapy
75+
76+
# Sphinx documentation
77+
docs/_build/
78+
79+
# PyBuilder
80+
.pybuilder/
81+
target/
82+
83+
# Jupyter Notebook
84+
.ipynb_checkpoints
85+
86+
# IPython
87+
profile_default/
88+
ipython_config.py
89+
90+
# pyenv
91+
# For a library or package, you might want to ignore these files since the code is
92+
# intended to run in multiple environments; otherwise, check them in:
93+
# .python-version
94+
95+
# pipenv
96+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
97+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
98+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
99+
# install all needed dependencies.
100+
#Pipfile.lock
101+
102+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
103+
__pypackages__/
104+
105+
# Celery stuff
106+
celerybeat-schedule
107+
celerybeat.pid
108+
109+
# SageMath parsed files
110+
*.sage.py
111+
112+
# Environments
113+
.env
114+
.venv
115+
env/
116+
venv/
117+
ENV/
118+
env.bak/
119+
venv.bak/
120+
121+
# Spyder project settings
122+
.spyderproject
123+
.spyproject
124+
125+
# Rope project settings
126+
.ropeproject
127+
128+
# mkdocs documentation
129+
/site
130+
131+
# mypy
132+
.mypy_cache/
133+
.dmypy.json
134+
dmypy.json
135+
136+
# Pyre type checker
137+
.pyre/
138+
139+
# pytype static type analyzer
140+
.pytype/
141+
142+
# Cython debug symbols
143+
cython_debug/
144+
145+
# End of https://www.toptal.com/developers/gitignore/api/python

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Python: Current File",
9+
"type": "python",
10+
"request": "launch",
11+
"program": "${file}",
12+
"console": "integratedTerminal"
13+
}
14+
]
15+
}

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"python.defaultInterpreterPath": "./venv/bin/python"
3+
}

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,28 @@
1-
# nested-multi-dimentional-parser
1+
# nested-multi-dimentional-parser
2+
3+
Parser for nested data in multipart form
4+
5+
# Usage
6+
7+
```python
8+
from nested_multipart_parser import parser
9+
...
10+
11+
class YourViewSet(viewsets.ViewSet):
12+
parser_classes = (NestedMultipartParser,)
13+
```
14+
15+
To enable JSON and multipart
16+
17+
```python
18+
from drf_nested_field_multipart import NestedMultipartParser
19+
from rest_framework.parsers import JSONParser
20+
from rest_framework import viewsets
21+
22+
class YourViewSet(viewsets.ViewSet):
23+
parser_classes = (JSONParser, NestedMultipartParser)
24+
```
25+
26+
# Installation
27+
28+
`pip install drf-nested-field-multipart`
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .parser import NestedParser
2+
3+
__all__ = [
4+
'NestedParser'
5+
]

nested_multipart_parser/drf.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from .parser import NestedParser as NestPars
2+
from rest_framework.parsers import MultiPartParser
3+
from django.http.multipartparser import MultiPartParserError
4+
from django.http import QueryDict
5+
6+
7+
class NestedParser(NestPars):
8+
9+
@property
10+
def validate_data(self):
11+
dtc = QueryDict(mutable=True)
12+
dtc.update(super().validate_data)
13+
dtc.mutable = False
14+
return dtc
15+
16+
17+
class DrfNestedParser(MultiPartParser):
18+
19+
def parse(self, stream, media_type=None, parser_context=None):
20+
parsed = super().parse(stream, media_type, parser_context)
21+
22+
copy = parsed.data.copy()
23+
if parsed.files:
24+
copy.update(parsed.files)
25+
parser = NestedParser(copy)
26+
if parser.is_valid():
27+
return parser.validate_data
28+
if parser.errors:
29+
raise MultiPartParserError(parser.errors)
30+
return parsed

nested_multipart_parser/parser.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import re
2+
3+
4+
class NestedParser:
5+
_valid = None
6+
errors = None
7+
_reg = re.compile(r"\[|\]")
8+
9+
def __init__(self, data):
10+
self.data = data
11+
12+
def split_key(self, key):
13+
# remove space
14+
k = key.replace(" ", "")
15+
# remove empty string and count key length for check is a good format
16+
# reduce + filter are a hight cost so do manualy with for loop
17+
results = []
18+
check = -2
19+
20+
for select in self._reg.split(k):
21+
if select:
22+
results.append(select)
23+
check += len(select) + 2
24+
25+
if len(k) != check:
26+
raise Exception(f"invalid format from key {key}")
27+
return results
28+
29+
def set_type(self, dtc, key, value, full_keys):
30+
if key.isdigit():
31+
key = int(key)
32+
if len(dtc) < key:
33+
raise Exception(
34+
f"key \"{full_keys}\" is upper than actual list")
35+
if len(dtc) == key:
36+
dtc.append(value)
37+
return key
38+
elif key not in dtc:
39+
dtc[key] = value
40+
return key
41+
42+
def construct(self, data):
43+
dictionary = {}
44+
45+
for key in data:
46+
keys = self.split_key(key)
47+
tmp = dictionary
48+
49+
# optimize with while loop instend of for in with zip function
50+
i = 0
51+
lenght = len(keys) - 1
52+
while i < lenght:
53+
set_type = [] if keys[i+1].isdigit() else {}
54+
tmp = tmp[self.set_type(tmp, keys[i], set_type, key)]
55+
i += 1
56+
57+
self.set_type(tmp, keys[-1], data[key], key)
58+
return dictionary
59+
60+
def is_valid(self):
61+
self._valid = False
62+
try:
63+
self.__validate_data = self.construct(self.data)
64+
self._valid = True
65+
except Exception as err:
66+
self.errors = err
67+
return self._valid
68+
69+
@property
70+
def validate_data(self):
71+
if self._valid is None:
72+
raise ValueError(
73+
"You need to be call is_valid() before access validate_data")
74+
if self._valid is False:
75+
raise ValueError("You can't get validate data")
76+
return self.__validate_data

requirements/common.txt

Whitespace-only changes.

requirements/dev.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-r common.txt
2+
Django
3+
djangorestframework

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[metadata]
2+
description-file = README.md

0 commit comments

Comments
 (0)