smartdjango 4.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- smartdjango/__init__.py +29 -0
- smartdjango/analyse.py +132 -0
- smartdjango/choice.py +4 -0
- smartdjango/code.py +63 -0
- smartdjango/error.py +103 -0
- smartdjango/middleware.py +40 -0
- smartdjango/models/__init__.py +20 -0
- smartdjango/models/manager.py +12 -0
- smartdjango/models/queryset.py +6 -0
- smartdjango/paginator.py +69 -0
- smartdjango/utils/__init__.py +0 -0
- smartdjango/utils/inspect.py +8 -0
- smartdjango/utils/io.py +61 -0
- smartdjango/validation/__init__.py +0 -0
- smartdjango/validation/dict_validator.py +61 -0
- smartdjango/validation/key.py +23 -0
- smartdjango/validation/list_validator.py +52 -0
- smartdjango/validation/params.py +60 -0
- smartdjango/validation/validator.py +197 -0
- smartdjango-4.4.0.dist-info/METADATA +26 -0
- smartdjango-4.4.0.dist-info/RECORD +24 -0
- smartdjango-4.4.0.dist-info/WHEEL +5 -0
- smartdjango-4.4.0.dist-info/licenses/LICENSE +21 -0
- smartdjango-4.4.0.dist-info/top_level.txt +1 -0
smartdjango/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from smartdjango.validation.validator import Validator, ValidatorErrors
|
|
2
|
+
from smartdjango.validation.params import Params
|
|
3
|
+
from smartdjango.validation.list_validator import ListValidator
|
|
4
|
+
from smartdjango.validation.dict_validator import DictValidator
|
|
5
|
+
from smartdjango.validation.key import Key
|
|
6
|
+
|
|
7
|
+
from smartdjango.analyse import AnalyseErrors
|
|
8
|
+
from smartdjango import analyse
|
|
9
|
+
from smartdjango.choice import Choice
|
|
10
|
+
from smartdjango.code import Code
|
|
11
|
+
from smartdjango.error import Error, OK
|
|
12
|
+
from smartdjango.middleware import APIPacker
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'Validator',
|
|
17
|
+
'ValidatorErrors',
|
|
18
|
+
'Params',
|
|
19
|
+
'ListValidator',
|
|
20
|
+
'DictValidator',
|
|
21
|
+
'Key',
|
|
22
|
+
'AnalyseErrors',
|
|
23
|
+
'analyse',
|
|
24
|
+
'Choice',
|
|
25
|
+
'Code',
|
|
26
|
+
'Error',
|
|
27
|
+
'APIPacker',
|
|
28
|
+
'OK'
|
|
29
|
+
]
|
smartdjango/analyse.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
|
|
3
|
+
from django.http import HttpRequest
|
|
4
|
+
from django.utils.translation import gettext as _
|
|
5
|
+
from oba import Obj
|
|
6
|
+
|
|
7
|
+
from smartdjango.code import Code
|
|
8
|
+
from smartdjango.error import Error
|
|
9
|
+
from smartdjango.utils import io, inspect
|
|
10
|
+
from smartdjango.validation.dict_validator import DictValidator
|
|
11
|
+
from smartdjango.validation.validator import Validator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Request(HttpRequest):
|
|
15
|
+
json: Obj
|
|
16
|
+
query: Obj
|
|
17
|
+
argument: Obj
|
|
18
|
+
data: Obj
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@Error.register
|
|
22
|
+
class AnalyseErrors:
|
|
23
|
+
REQUEST_NOT_FOUND = Error(_("Cannot find request"), code=Code.InternalServerError)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_request(*args):
|
|
27
|
+
for i, arg in enumerate(args):
|
|
28
|
+
if isinstance(arg, HttpRequest):
|
|
29
|
+
return arg
|
|
30
|
+
raise AnalyseErrors.REQUEST_NOT_FOUND
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def update_to_data(req: Request, target):
|
|
34
|
+
data = getattr(req, 'data', {})
|
|
35
|
+
if isinstance(data, Obj):
|
|
36
|
+
data = data()
|
|
37
|
+
data.update(target())
|
|
38
|
+
req.data = Obj(data)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def analyse(*validators: Validator | str, target_getter, target_setter, restrict_keys):
|
|
42
|
+
def decorator(func):
|
|
43
|
+
@wraps(func)
|
|
44
|
+
def wrapper(*args, **kwargs):
|
|
45
|
+
req = get_request(*args)
|
|
46
|
+
target = target_getter(req, kwargs)
|
|
47
|
+
|
|
48
|
+
validator = DictValidator().fields(*validators)
|
|
49
|
+
if restrict_keys:
|
|
50
|
+
validator.restrict_keys()
|
|
51
|
+
target = validator.clean(target)
|
|
52
|
+
target_setter(req, Obj(target))
|
|
53
|
+
|
|
54
|
+
return func(*args, **kwargs)
|
|
55
|
+
return wrapper
|
|
56
|
+
return decorator
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def json(*validators: Validator | str, restrict_keys=True):
|
|
60
|
+
def getter(req, kwargs):
|
|
61
|
+
return io.json_loads(req.body.decode())
|
|
62
|
+
|
|
63
|
+
def setter(req, target):
|
|
64
|
+
req.json = target
|
|
65
|
+
update_to_data(req, target)
|
|
66
|
+
|
|
67
|
+
return analyse(
|
|
68
|
+
*validators,
|
|
69
|
+
target_getter=getter,
|
|
70
|
+
target_setter=setter,
|
|
71
|
+
restrict_keys=restrict_keys
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def query(*validators: Validator | str, restrict_keys=False):
|
|
76
|
+
def getter(req, kwargs):
|
|
77
|
+
return req.GET.dict()
|
|
78
|
+
|
|
79
|
+
def setter(req, target):
|
|
80
|
+
req.query = target
|
|
81
|
+
update_to_data(req, target)
|
|
82
|
+
|
|
83
|
+
return analyse(
|
|
84
|
+
*validators,
|
|
85
|
+
target_getter=getter,
|
|
86
|
+
target_setter=setter,
|
|
87
|
+
restrict_keys=restrict_keys
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def argument(*validators: Validator | str, restrict_keys=True):
|
|
92
|
+
def getter(req, kwargs):
|
|
93
|
+
return kwargs
|
|
94
|
+
|
|
95
|
+
def setter(req, target):
|
|
96
|
+
req.argument = target
|
|
97
|
+
update_to_data(req, target)
|
|
98
|
+
|
|
99
|
+
return analyse(
|
|
100
|
+
*validators,
|
|
101
|
+
target_getter=getter,
|
|
102
|
+
target_setter=setter,
|
|
103
|
+
restrict_keys=restrict_keys
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def request(bool_func, message=None):
|
|
108
|
+
def decorator(func):
|
|
109
|
+
@wraps(func)
|
|
110
|
+
def wrapper(*args, **kwargs):
|
|
111
|
+
req = get_request(*args)
|
|
112
|
+
Validator().bool(bool_func, message=message).clean(req)
|
|
113
|
+
return func(*args, **kwargs)
|
|
114
|
+
|
|
115
|
+
return wrapper
|
|
116
|
+
return decorator
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def function(*validators: Validator | str, restrict_keys=True):
|
|
120
|
+
validator = DictValidator().fields(*validators)
|
|
121
|
+
if restrict_keys:
|
|
122
|
+
validator.restrict_keys()
|
|
123
|
+
|
|
124
|
+
def decorator(func):
|
|
125
|
+
@wraps(func)
|
|
126
|
+
def wrapper(*args, **kwargs):
|
|
127
|
+
arguments = inspect.get_function_arguments(func, *args, **kwargs)
|
|
128
|
+
arguments = validator.clean(arguments)
|
|
129
|
+
|
|
130
|
+
return func(**arguments)
|
|
131
|
+
return wrapper
|
|
132
|
+
return decorator
|
smartdjango/choice.py
ADDED
smartdjango/code.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
class Code:
|
|
2
|
+
Continue = 100
|
|
3
|
+
SwitchingProtocols = 101
|
|
4
|
+
Processing = 102
|
|
5
|
+
|
|
6
|
+
OK = 200
|
|
7
|
+
Created = 201
|
|
8
|
+
Accepted = 202
|
|
9
|
+
NonAuthoritativeInformation = 203
|
|
10
|
+
NoContent = 204
|
|
11
|
+
ResetContent = 205
|
|
12
|
+
PartialContent = 206
|
|
13
|
+
MultiStatus = 207
|
|
14
|
+
|
|
15
|
+
MultipleChoices = 300
|
|
16
|
+
MovedPermanently = 301
|
|
17
|
+
MoveTemporarily = 302
|
|
18
|
+
SeeOther = 303
|
|
19
|
+
NotModified = 304
|
|
20
|
+
UseProxy = 305
|
|
21
|
+
SwitchProxy = 306 # 不再使用
|
|
22
|
+
TemporaryRedirect = 307
|
|
23
|
+
|
|
24
|
+
BadRequest = 400
|
|
25
|
+
Unauthorized = 401
|
|
26
|
+
PaymentRequired = 402
|
|
27
|
+
Forbidden = 403
|
|
28
|
+
NotFound = 404
|
|
29
|
+
MethodNotAllowed = 405
|
|
30
|
+
NotAcceptable = 406
|
|
31
|
+
ProxyAuthenticationRequired = 407
|
|
32
|
+
RequestTimeout = 408
|
|
33
|
+
Conflict = 409
|
|
34
|
+
Gone = 410
|
|
35
|
+
LengthRequired = 411
|
|
36
|
+
PreconditionFailed = 412
|
|
37
|
+
RequestEntityTooLarge = 413
|
|
38
|
+
RequestURITooLong = 414
|
|
39
|
+
UnsupportedMediaType = 415
|
|
40
|
+
RequestedRangeNotSatisfiable = 416
|
|
41
|
+
ExpectationFailed = 417
|
|
42
|
+
ImATeapot = 418
|
|
43
|
+
TooManyConnections = 421
|
|
44
|
+
UnprocessableEntity = 422
|
|
45
|
+
Locked = 423
|
|
46
|
+
FailedDependency = 424
|
|
47
|
+
TooEarly = 425
|
|
48
|
+
UpgradeRequired = 426
|
|
49
|
+
RetryWith = 449
|
|
50
|
+
UnavailableForLegalReasons = 451
|
|
51
|
+
|
|
52
|
+
InternalServerError = 500
|
|
53
|
+
NotImplemented = 501
|
|
54
|
+
BadGateway = 502
|
|
55
|
+
ServiceUnavailable = 503
|
|
56
|
+
GatewayTimeout = 504
|
|
57
|
+
HTTPVersionNotSupported = 505
|
|
58
|
+
VariantAlsoNegotiates = 506
|
|
59
|
+
InsufficientStorage = 507
|
|
60
|
+
BandwidthLimitExceeded = 509
|
|
61
|
+
NotExtended = 510
|
|
62
|
+
|
|
63
|
+
UnparseableResponseHeaders = 600
|
smartdjango/error.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Dict, Optional, Union
|
|
2
|
+
|
|
3
|
+
from django.utils.encoding import force_str
|
|
4
|
+
from django.utils.functional import Promise
|
|
5
|
+
|
|
6
|
+
from smartdjango.code import Code
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
MessageLike = Union[str, Promise]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Error(Exception):
|
|
13
|
+
__ERRORS: Dict[str, 'Error'] = dict()
|
|
14
|
+
|
|
15
|
+
"""Base class for exceptions in this module."""
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
message: MessageLike,
|
|
19
|
+
code=Code.OK,
|
|
20
|
+
user_message: Optional[MessageLike] = None,
|
|
21
|
+
identifier=None,
|
|
22
|
+
):
|
|
23
|
+
super().__init__(message)
|
|
24
|
+
|
|
25
|
+
self.__identifier = identifier or None
|
|
26
|
+
self.message = message
|
|
27
|
+
self.code = code
|
|
28
|
+
self.details = []
|
|
29
|
+
self.user_message = user_message or message
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def identifier(self) -> str:
|
|
33
|
+
return self.__identifier
|
|
34
|
+
|
|
35
|
+
@identifier.setter
|
|
36
|
+
def identifier(self, value):
|
|
37
|
+
if self.__identifier is not None:
|
|
38
|
+
raise AttributeError('Identifier is already set')
|
|
39
|
+
if value in self.__ERRORS:
|
|
40
|
+
raise AttributeError(f'Conflict error identifier: {value}')
|
|
41
|
+
self.__identifier = value
|
|
42
|
+
self.__ERRORS[value] = self
|
|
43
|
+
|
|
44
|
+
def json(self):
|
|
45
|
+
return {
|
|
46
|
+
'message': force_str(self.message),
|
|
47
|
+
'code': self.code,
|
|
48
|
+
'details': self.details,
|
|
49
|
+
'user_message': force_str(self.user_message),
|
|
50
|
+
'identifier': self.identifier,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def jsonl(self):
|
|
54
|
+
return {
|
|
55
|
+
'message': force_str(self.message),
|
|
56
|
+
'code': self.code,
|
|
57
|
+
'identifier': self.identifier,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def __call__(self, details=None, user_message=None, **kwargs):
|
|
61
|
+
message = force_str(self.message).format(**kwargs)
|
|
62
|
+
if user_message is None:
|
|
63
|
+
user_message = force_str(self.user_message).format(**kwargs)
|
|
64
|
+
|
|
65
|
+
if details is not None and not isinstance(details, str):
|
|
66
|
+
details = force_str(details)
|
|
67
|
+
if user_message is not None and not isinstance(user_message, str):
|
|
68
|
+
user_message = force_str(user_message)
|
|
69
|
+
error = Error(message, code=self.code, user_message=user_message, identifier=self.identifier)
|
|
70
|
+
error.details = self.details.copy()
|
|
71
|
+
if details is not None:
|
|
72
|
+
error.details.append(details)
|
|
73
|
+
return error
|
|
74
|
+
|
|
75
|
+
def __eq__(self, other: 'Error'):
|
|
76
|
+
return self.identifier == other.identifier
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def register(cls, class_):
|
|
80
|
+
class_name = class_.__name__
|
|
81
|
+
if not class_name.upper().endswith('ERRORS'):
|
|
82
|
+
raise AttributeError('Error class name should end with "Errors"')
|
|
83
|
+
class_name = class_name[:-6].upper()
|
|
84
|
+
for name in class_.__dict__: # type: str
|
|
85
|
+
e = getattr(class_, name)
|
|
86
|
+
if not isinstance(e, Error):
|
|
87
|
+
continue
|
|
88
|
+
e.identifier = f'{class_name}@{name}'
|
|
89
|
+
|
|
90
|
+
return class_
|
|
91
|
+
|
|
92
|
+
def ok(self) -> bool:
|
|
93
|
+
return self.identifier == 'OK'
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def all(cls) -> Dict[str, 'Error']:
|
|
97
|
+
return cls.__ERRORS
|
|
98
|
+
|
|
99
|
+
def equals(self, other: 'Error'):
|
|
100
|
+
return self.identifier == other.identifier
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
OK = Error('OK', code=Code.OK, identifier='OK')
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from django.http import HttpResponse
|
|
2
|
+
|
|
3
|
+
from smartdjango.error import Error, OK
|
|
4
|
+
from smartdjango.utils import io
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class APIPacker:
|
|
8
|
+
def __init__(self, get_response):
|
|
9
|
+
self.get_response = get_response
|
|
10
|
+
|
|
11
|
+
def __call__(self, request, *args, **kwargs):
|
|
12
|
+
response = self.get_response(request, *args, **kwargs)
|
|
13
|
+
if isinstance(response, HttpResponse):
|
|
14
|
+
return response
|
|
15
|
+
|
|
16
|
+
return self.pack(response)
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def process_exception(cls, _, error):
|
|
20
|
+
if isinstance(error, Error):
|
|
21
|
+
return cls.pack(error)
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def pack(response):
|
|
26
|
+
if isinstance(response, Error):
|
|
27
|
+
body, error = None, response
|
|
28
|
+
else:
|
|
29
|
+
body, error = response, OK
|
|
30
|
+
|
|
31
|
+
response = error.json()
|
|
32
|
+
response['body'] = body
|
|
33
|
+
|
|
34
|
+
response = io.json_dumps(response, indent=False)
|
|
35
|
+
response = HttpResponse(
|
|
36
|
+
response,
|
|
37
|
+
status=error.code,
|
|
38
|
+
content_type="application/json; encoding=utf-8",
|
|
39
|
+
)
|
|
40
|
+
return response
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from diq import Dictify
|
|
2
|
+
from django.db.models import *
|
|
3
|
+
from django.db.models import Model as BaseModel
|
|
4
|
+
|
|
5
|
+
from smartdjango.models.queryset import QuerySet
|
|
6
|
+
from smartdjango.models.manager import Manager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Model(BaseModel, Dictify):
|
|
10
|
+
objects = Manager()
|
|
11
|
+
normalizers = None
|
|
12
|
+
validators = None
|
|
13
|
+
normalizer = None
|
|
14
|
+
validator = None
|
|
15
|
+
norm = None
|
|
16
|
+
vldt = None
|
|
17
|
+
|
|
18
|
+
class Meta:
|
|
19
|
+
abstract = True
|
|
20
|
+
default_manager_name = 'objects'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 自定义 Manager
|
|
2
|
+
from django.db import models
|
|
3
|
+
|
|
4
|
+
from smartdjango.models.queryset import QuerySet
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Manager(models.Manager):
|
|
8
|
+
def get_queryset(self) -> QuerySet:
|
|
9
|
+
return QuerySet(self.model, using=self._db)
|
|
10
|
+
|
|
11
|
+
def map(self, func, *args, **kwargs):
|
|
12
|
+
return self.get_queryset().map(func, *args, **kwargs)
|
smartdjango/paginator.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from smartdjango.models import QuerySet
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Page:
|
|
5
|
+
OBJECTS = 'objects'
|
|
6
|
+
NEXT = 'next'
|
|
7
|
+
COUNT = 'count'
|
|
8
|
+
|
|
9
|
+
def __init__(self, queryset, count, next_value):
|
|
10
|
+
self.queryset: QuerySet = queryset
|
|
11
|
+
self.count = count
|
|
12
|
+
self.next_value = next_value
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def rename(cls, objects, next_value, count):
|
|
16
|
+
cls.OBJECTS = objects
|
|
17
|
+
cls.NEXT = next_value
|
|
18
|
+
cls.COUNT = count
|
|
19
|
+
|
|
20
|
+
def dict(self, object_map, next_map=None):
|
|
21
|
+
return {
|
|
22
|
+
self.OBJECTS: self.queryset.map(object_map),
|
|
23
|
+
self.NEXT: next_map(self.next_value) if next_map else self.next_value,
|
|
24
|
+
self.COUNT: self.count,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Paginator:
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
queryset: QuerySet,
|
|
32
|
+
page_size: int,
|
|
33
|
+
):
|
|
34
|
+
self.queryset = queryset
|
|
35
|
+
self.page_size = page_size
|
|
36
|
+
self.count = self.queryset.count()
|
|
37
|
+
|
|
38
|
+
self.target_field = None
|
|
39
|
+
|
|
40
|
+
def filter(self, **kwargs):
|
|
41
|
+
assert len(kwargs) == 1, "only one filter is allowed"
|
|
42
|
+
order = list(kwargs.keys())[0]
|
|
43
|
+
assert '__' in order, "attribute must contain '__' to specify the comparison method"
|
|
44
|
+
self.target_field, compare_op = order.split('__', 1)
|
|
45
|
+
assert compare_op in ['lt', 'gt'], "comparison operation must be 'lt' or 'gt'"
|
|
46
|
+
|
|
47
|
+
order = self.target_field
|
|
48
|
+
if compare_op == 'lt':
|
|
49
|
+
order = '-' + order
|
|
50
|
+
|
|
51
|
+
self.queryset = self.queryset.filter(**kwargs).order_by(order)
|
|
52
|
+
self.count = self.queryset.count()
|
|
53
|
+
|
|
54
|
+
def get_page(
|
|
55
|
+
self,
|
|
56
|
+
page: int = None,
|
|
57
|
+
) -> Page:
|
|
58
|
+
if page is None:
|
|
59
|
+
assert self.target_field is not None, "filter should be firstly called when page is None"
|
|
60
|
+
|
|
61
|
+
queryset = self.queryset[:self.page_size]
|
|
62
|
+
next_value = self.queryset.count() > self.page_size and getattr(queryset.last(), self.target_field)
|
|
63
|
+
|
|
64
|
+
return Page(queryset, self.count, next_value)
|
|
65
|
+
|
|
66
|
+
queryset = self.queryset[self.page_size * page:self.page_size * (page + 1)]
|
|
67
|
+
next_value = self.queryset.count() > self.page_size * (page + 1) and page + 1
|
|
68
|
+
|
|
69
|
+
return Page(queryset, self.count, next_value)
|
|
File without changes
|
smartdjango/utils/io.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pickle
|
|
3
|
+
|
|
4
|
+
from django.core.serializers.json import DjangoJSONEncoder
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, cast
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SupportsWriteStr(Protocol):
|
|
10
|
+
def write(self, __s: str) -> object:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SupportsWriteBytes(Protocol):
|
|
15
|
+
def write(self, __s: bytes) -> object:
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def json_load(filepath: str):
|
|
20
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
21
|
+
return json.load(f)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def json_loads(s: str):
|
|
25
|
+
return json.loads(s)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def json_dumps(obj, indent=2) -> str:
|
|
29
|
+
if indent is False:
|
|
30
|
+
indent = None
|
|
31
|
+
return json.dumps(obj, indent=indent, ensure_ascii=False, cls=DjangoJSONEncoder)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def json_save(obj, filepath: str):
|
|
35
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
36
|
+
json.dump(obj, cast(SupportsWriteStr, f), indent=2, ensure_ascii=False, cls=DjangoJSONEncoder)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def jsonl_load(filepath: str):
|
|
40
|
+
lines = file_load(filepath).split('\n') # 读取整个文件并按行分割
|
|
41
|
+
lines = list(filter(lambda line: line.strip(), lines)) # 去掉空行
|
|
42
|
+
lines = list(map(lambda line: json_loads(line), lines)) # 每行解析成 JSON 对象
|
|
43
|
+
return lines
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def file_load(filepath: str) -> str:
|
|
47
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
48
|
+
return f.read()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def file_save(filepath: str, content: str, append=False):
|
|
52
|
+
with open(filepath, 'a+' if append else 'w', encoding='utf-8') as f:
|
|
53
|
+
f.write(content)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def pkl_load(filepath: str):
|
|
57
|
+
return pickle.load(open(filepath, "rb"))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def pkl_save(obj, filepath: str):
|
|
61
|
+
pickle.dump(obj, cast(SupportsWriteBytes, open(filepath, "wb")))
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from smartdjango.error import Error
|
|
2
|
+
from smartdjango.validation.validator import Validator, ValidatorErrors
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DictValidator(Validator):
|
|
6
|
+
def __init__(self, *args, **kwargs):
|
|
7
|
+
super().__init__(*args, **kwargs)
|
|
8
|
+
|
|
9
|
+
self.field_validators = dict()
|
|
10
|
+
|
|
11
|
+
def copy(self):
|
|
12
|
+
new = super().copy()
|
|
13
|
+
new.field_validators = self.field_validators.copy()
|
|
14
|
+
return new
|
|
15
|
+
|
|
16
|
+
def field(self, validator: Validator | str):
|
|
17
|
+
if isinstance(validator, str):
|
|
18
|
+
validator = Validator(validator)
|
|
19
|
+
if validator.key is None:
|
|
20
|
+
raise ValueError('Validator key is required for DictValidator field')
|
|
21
|
+
if validator.key in self.field_validators:
|
|
22
|
+
raise ValidatorErrors.EXIST_PARAM_KEY(key=validator.key)
|
|
23
|
+
self.field_validators[validator.key] = validator
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def fields(self, *validators: Validator | str):
|
|
27
|
+
for validator in validators:
|
|
28
|
+
self.field(validator)
|
|
29
|
+
return self
|
|
30
|
+
|
|
31
|
+
def restrict_keys(self):
|
|
32
|
+
def key_validator(value):
|
|
33
|
+
for key in value:
|
|
34
|
+
if key not in self.field_validators:
|
|
35
|
+
raise ValidatorErrors.INVALID_KEY(key=key)
|
|
36
|
+
self.exception(key_validator)
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def clean(self, value):
|
|
40
|
+
value = super().clean(value)
|
|
41
|
+
if value is None:
|
|
42
|
+
return None
|
|
43
|
+
if not isinstance(value, dict):
|
|
44
|
+
raise ValidatorErrors.NOT_A_DICT
|
|
45
|
+
|
|
46
|
+
new_value = dict()
|
|
47
|
+
for key, validator in self.field_validators.items():
|
|
48
|
+
try:
|
|
49
|
+
final_value = validator.clean(value.get(key.name, Validator.unset()))
|
|
50
|
+
except Error as error:
|
|
51
|
+
raise error
|
|
52
|
+
new_value[key.final_name] = final_value
|
|
53
|
+
return new_value
|
|
54
|
+
|
|
55
|
+
def __str__(self):
|
|
56
|
+
string = super().__str__()
|
|
57
|
+
if self.field_validators:
|
|
58
|
+
string += ' with fields:'
|
|
59
|
+
for key, validator in self.field_validators.items():
|
|
60
|
+
string += self.indent('\n' + str(validator))
|
|
61
|
+
return string
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Key:
|
|
2
|
+
def __init__(self, name, verbose_name=None, final_name=None):
|
|
3
|
+
self.name = name
|
|
4
|
+
self.verbose_name = verbose_name or name
|
|
5
|
+
self.final_name = final_name or name
|
|
6
|
+
|
|
7
|
+
def __hash__(self):
|
|
8
|
+
return hash(self.name)
|
|
9
|
+
|
|
10
|
+
def __eq__(self, other):
|
|
11
|
+
if isinstance(other, Key):
|
|
12
|
+
return self.name == other.name
|
|
13
|
+
if isinstance(other, str):
|
|
14
|
+
return self.name == other
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
def copy(self):
|
|
18
|
+
return Key(self.name, self.verbose_name, self.final_name)
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
if self.verbose_name != self.name:
|
|
22
|
+
return f'Key({self.name}, {self.verbose_name})'
|
|
23
|
+
return f'Key({self.name})'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from smartdjango.validation.validator import Validator, ValidatorErrors
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ListValidator(Validator):
|
|
5
|
+
def __init__(self, *args, **kwargs):
|
|
6
|
+
super().__init__(*args, **kwargs)
|
|
7
|
+
self.element_validator = None
|
|
8
|
+
|
|
9
|
+
def element(self, validator: Validator):
|
|
10
|
+
self.element_validator = validator
|
|
11
|
+
return self
|
|
12
|
+
|
|
13
|
+
def elements(self, *validators: Validator):
|
|
14
|
+
self.element_validator = list(validators)
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def copy(self):
|
|
18
|
+
new = super().copy()
|
|
19
|
+
new.element_validator = self.element_validator
|
|
20
|
+
if isinstance(self.element_validator, (list, tuple)):
|
|
21
|
+
new.element_validator = new.element_validator.copy()
|
|
22
|
+
return new
|
|
23
|
+
|
|
24
|
+
def clean(self, value):
|
|
25
|
+
value = super().clean(value)
|
|
26
|
+
if value is None:
|
|
27
|
+
return None
|
|
28
|
+
if not isinstance(value, list):
|
|
29
|
+
raise ValidatorErrors.NOT_A_LIST
|
|
30
|
+
if self.element_validator is None:
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
if isinstance(self.element_validator, (list, tuple)):
|
|
34
|
+
if len(self.element_validator) != len(value):
|
|
35
|
+
raise ValidatorErrors.LIST_LENGTH_MISMATCH
|
|
36
|
+
values = [validator.clean(v) for validator, v in zip(self.element_validator, value)]
|
|
37
|
+
else:
|
|
38
|
+
values = [self.element_validator.clean(v) for v in value]
|
|
39
|
+
|
|
40
|
+
return values
|
|
41
|
+
|
|
42
|
+
def __str__(self):
|
|
43
|
+
string = super().__str__()
|
|
44
|
+
if self.element_validator is not None:
|
|
45
|
+
string += ' with element validator:'
|
|
46
|
+
|
|
47
|
+
if isinstance(self.element_validator, (list, tuple)):
|
|
48
|
+
for validator in self.element_validator:
|
|
49
|
+
string += self.indent('\n' + str(validator))
|
|
50
|
+
else:
|
|
51
|
+
string += self.indent('\n' + str(self.element_validator))
|
|
52
|
+
return string
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
from smartdjango.validation.validator import Validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Params(type):
|
|
9
|
+
model_class: Type[models.Model]
|
|
10
|
+
__params = None
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def _handler_source(model_class, *attr_names):
|
|
14
|
+
sources = []
|
|
15
|
+
for attr_name in attr_names:
|
|
16
|
+
source = getattr(model_class, attr_name, None)
|
|
17
|
+
if source is not None:
|
|
18
|
+
sources.append((attr_name, source))
|
|
19
|
+
|
|
20
|
+
unique_sources = []
|
|
21
|
+
for attr_name, source in sources:
|
|
22
|
+
if all(existing is not source for _, existing in unique_sources):
|
|
23
|
+
unique_sources.append((attr_name, source))
|
|
24
|
+
|
|
25
|
+
if len(unique_sources) > 1:
|
|
26
|
+
attr_names_str = ', '.join(name for name, _ in unique_sources)
|
|
27
|
+
raise AttributeError(f'Conflicting params hook sources: {attr_names_str}')
|
|
28
|
+
|
|
29
|
+
if not unique_sources:
|
|
30
|
+
return None
|
|
31
|
+
return unique_sources[0][1]
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def _field_handler(cls, model_class, field_name, *attr_names):
|
|
35
|
+
source = cls._handler_source(model_class, *attr_names)
|
|
36
|
+
if source is None:
|
|
37
|
+
return None
|
|
38
|
+
handler = getattr(source, field_name, None)
|
|
39
|
+
if callable(handler):
|
|
40
|
+
return handler
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def __getattr__(cls, field_name):
|
|
44
|
+
if cls.__params is None:
|
|
45
|
+
cls.__params = {}
|
|
46
|
+
|
|
47
|
+
if field_name not in cls.__params:
|
|
48
|
+
field = cls.model_class._meta.get_field(field_name)
|
|
49
|
+
validator = Validator.from_field(field, name=field.name, verbose_name=field.verbose_name)
|
|
50
|
+
|
|
51
|
+
norm = cls._field_handler(cls.model_class, field_name, 'normalizers', 'normalizer', 'norm')
|
|
52
|
+
if norm is not None:
|
|
53
|
+
validator.to(norm)
|
|
54
|
+
|
|
55
|
+
vldt = cls._field_handler(cls.model_class, field_name, 'validators', 'validator', 'vldt')
|
|
56
|
+
if vldt is not None:
|
|
57
|
+
validator.exception(vldt)
|
|
58
|
+
|
|
59
|
+
cls.__params[field_name] = validator
|
|
60
|
+
return cls.__params[field_name]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import Callable, Union
|
|
3
|
+
|
|
4
|
+
from django.core.validators import BaseValidator
|
|
5
|
+
from django.db import models
|
|
6
|
+
from django.utils.translation import gettext as _
|
|
7
|
+
|
|
8
|
+
from smartdjango.code import Code
|
|
9
|
+
from smartdjango.error import Error
|
|
10
|
+
from smartdjango.validation.key import Key
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@Error.register
|
|
14
|
+
class ValidatorErrors:
|
|
15
|
+
NO_DEFAULT = Error(_('No default value'), code=Code.BadRequest)
|
|
16
|
+
NULL_NOT_ALLOW = Error(_('Null is not allowed'), code=Code.BadRequest)
|
|
17
|
+
NOT_VALID = Error(_('Not valid: {message}'), code=Code.BadRequest)
|
|
18
|
+
VALIDATOR_CRUSHED = Error(_('Validator crushed'), code=Code.InternalServerError)
|
|
19
|
+
PROCESSOR_CRUSHED = Error(_('Processor crushed'), code=Code.InternalServerError)
|
|
20
|
+
NOT_A_LIST = Error(_('Not a list'), code=Code.BadRequest)
|
|
21
|
+
LIST_LENGTH_MISMATCH = Error(_('List length mismatch'), code=Code.BadRequest)
|
|
22
|
+
NOT_A_DICT = Error(_('Not a dict'), code=Code.BadRequest)
|
|
23
|
+
INVALID_KEY = Error(_('{key} is an invalid key'), code=Code.BadRequest)
|
|
24
|
+
EXIST_PARAM_KEY = Error(_('Param key {key} already exists'), code=Code.InternalServerError)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Validator:
|
|
28
|
+
class __NoDefaultValue:
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
class __UnSetValue:
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def unset(cls):
|
|
36
|
+
return cls.__UnSetValue
|
|
37
|
+
|
|
38
|
+
def __init__(self, name=None, verbose_name=None, final_name=None, **kwargs):
|
|
39
|
+
self.allow_null = False
|
|
40
|
+
self.default_value = self.__NoDefaultValue
|
|
41
|
+
self.to_python = []
|
|
42
|
+
self.validators = []
|
|
43
|
+
|
|
44
|
+
self._default_as_final = False
|
|
45
|
+
|
|
46
|
+
if isinstance(name, str):
|
|
47
|
+
name = Key(name, verbose_name, final_name)
|
|
48
|
+
if name and not isinstance(name, Key):
|
|
49
|
+
raise TypeError('name must be a string or Key instance')
|
|
50
|
+
self.key = name
|
|
51
|
+
|
|
52
|
+
def _carry_key_info(self, error: Error):
|
|
53
|
+
if self.key is None:
|
|
54
|
+
return error
|
|
55
|
+
|
|
56
|
+
err = error()
|
|
57
|
+
err.details.append(_('Target key: {key}').format(key=str(self.key)))
|
|
58
|
+
return err
|
|
59
|
+
|
|
60
|
+
def rename(self, name: Union[str, Key], verbose_name=__UnSetValue, final_name=__UnSetValue):
|
|
61
|
+
if isinstance(name, Key):
|
|
62
|
+
self.key = name
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
self.key = self.key.copy() if self.key else Key(name)
|
|
66
|
+
|
|
67
|
+
self.key.name = name
|
|
68
|
+
if verbose_name is not self.unset():
|
|
69
|
+
self.key.verbose_name = verbose_name
|
|
70
|
+
if final_name is not self.unset():
|
|
71
|
+
self.key.final_name = final_name
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def copy(self):
|
|
75
|
+
new = Validator()
|
|
76
|
+
new.allow_null = self.allow_null
|
|
77
|
+
new.default_value = self.default_value
|
|
78
|
+
new.to_python = self.to_python.copy()
|
|
79
|
+
new.validators = self.validators.copy()
|
|
80
|
+
new._default_as_final = self._default_as_final
|
|
81
|
+
new.key = self.key.copy() if self.key else None
|
|
82
|
+
return new
|
|
83
|
+
|
|
84
|
+
def null(self, allow_null=True):
|
|
85
|
+
self.allow_null = allow_null
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def default(self, value, as_final=False):
|
|
89
|
+
self.default_value = value
|
|
90
|
+
self._default_as_final = as_final
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def to(self, to_python: Callable):
|
|
94
|
+
self.to_python.append(to_python)
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def exception(self, validator: Callable, message: str = None):
|
|
98
|
+
def wrap(value):
|
|
99
|
+
try:
|
|
100
|
+
validator(value)
|
|
101
|
+
except Error as e:
|
|
102
|
+
raise e
|
|
103
|
+
except Exception as err:
|
|
104
|
+
raise ValidatorErrors.NOT_VALID(message=message, details=err)
|
|
105
|
+
|
|
106
|
+
self.validators.append(wrap)
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
def bool(self, validator: Callable, message: str = None):
|
|
110
|
+
def wrap(value):
|
|
111
|
+
if not validator(value):
|
|
112
|
+
raise ValidatorErrors.NOT_VALID(message=message or '')
|
|
113
|
+
|
|
114
|
+
return self.exception(wrap)
|
|
115
|
+
|
|
116
|
+
def clean(self, value):
|
|
117
|
+
if value is self.__UnSetValue:
|
|
118
|
+
if self.default_value is self.__NoDefaultValue:
|
|
119
|
+
raise self._carry_key_info(ValidatorErrors.NO_DEFAULT)
|
|
120
|
+
|
|
121
|
+
value = self.default_value
|
|
122
|
+
if self._default_as_final:
|
|
123
|
+
return value
|
|
124
|
+
|
|
125
|
+
if value is None:
|
|
126
|
+
if not self.allow_null:
|
|
127
|
+
raise self._carry_key_info(ValidatorErrors.NULL_NOT_ALLOW)
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
for to_python in self.to_python:
|
|
131
|
+
try:
|
|
132
|
+
value = to_python(value)
|
|
133
|
+
except Error as e:
|
|
134
|
+
raise self._carry_key_info(e)
|
|
135
|
+
except Exception as err:
|
|
136
|
+
raise self._carry_key_info(ValidatorErrors.PROCESSOR_CRUSHED(details=err))
|
|
137
|
+
for validator in self.validators:
|
|
138
|
+
try:
|
|
139
|
+
validator(value)
|
|
140
|
+
except Error as e:
|
|
141
|
+
raise self._carry_key_info(e)
|
|
142
|
+
except Exception as err:
|
|
143
|
+
raise self._carry_key_info(ValidatorErrors.VALIDATOR_CRUSHED(details=err))
|
|
144
|
+
return value
|
|
145
|
+
|
|
146
|
+
def __call__(self, value):
|
|
147
|
+
return self.clean(value)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_field(cls, field: models.Field, *args, **kwargs):
|
|
151
|
+
validator = cls(*args, **kwargs)
|
|
152
|
+
validator.null(field.null)
|
|
153
|
+
if field.default != models.fields.NOT_PROVIDED:
|
|
154
|
+
validator.default(field.default)
|
|
155
|
+
if field.choices:
|
|
156
|
+
validator.bool(lambda x: x in dict(field.choices), message=_('Invalid choice'))
|
|
157
|
+
if field.validators:
|
|
158
|
+
for field_validator in field.validators:
|
|
159
|
+
if not isinstance(field_validator, BaseValidator):
|
|
160
|
+
validator.exception(field_validator)
|
|
161
|
+
if isinstance(field, models.CharField):
|
|
162
|
+
validator.bool(lambda x: isinstance(x, str), message=_('Not a string'))
|
|
163
|
+
validator.bool(lambda x: len(x) <= field.max_length, message=_('Too long'))
|
|
164
|
+
if isinstance(field, models.IntegerField):
|
|
165
|
+
validator.bool(lambda x: isinstance(x, int), message=_('Not an integer'))
|
|
166
|
+
if isinstance(field, models.FloatField):
|
|
167
|
+
validator.bool(lambda x: isinstance(x, float), message=_('Not a float'))
|
|
168
|
+
if isinstance(field, models.BooleanField):
|
|
169
|
+
validator.bool(lambda x: isinstance(x, bool), message=_('Not a boolean'))
|
|
170
|
+
if isinstance(field, models.DateField):
|
|
171
|
+
validator.bool(lambda x: isinstance(x, datetime.date), message=_('Not a date'))
|
|
172
|
+
if isinstance(field, models.DateTimeField):
|
|
173
|
+
validator.bool(lambda x: isinstance(x, datetime.datetime), message=_('Not a datetime'))
|
|
174
|
+
return validator
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def classname(self):
|
|
178
|
+
return self.__class__.__name__
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def indent(string, indent='\t'):
|
|
182
|
+
lines = string.split('\n')
|
|
183
|
+
return '\n'.join([indent + line for line in lines])
|
|
184
|
+
|
|
185
|
+
def __str__(self):
|
|
186
|
+
num_validators = len(self.validators)
|
|
187
|
+
name_str = f'{self.key}' if self.key else ''
|
|
188
|
+
if num_validators == 0:
|
|
189
|
+
validator_str = ''
|
|
190
|
+
elif num_validators == 1:
|
|
191
|
+
validator_str = ' 1 validator'
|
|
192
|
+
else:
|
|
193
|
+
validator_str = f'{num_validators} validators'
|
|
194
|
+
if name_str and validator_str:
|
|
195
|
+
name_str += ', '
|
|
196
|
+
|
|
197
|
+
return f'{self.classname}({name_str}{validator_str})'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smartdjango
|
|
3
|
+
Version: 4.4.0
|
|
4
|
+
Summary: fast Django app development
|
|
5
|
+
Home-page: https://github.com/Jyonn/smartdjango
|
|
6
|
+
Author: Jyonn Liu
|
|
7
|
+
Author-email: i@6-79.cn
|
|
8
|
+
License: MIT Licence
|
|
9
|
+
Keywords: django
|
|
10
|
+
Platform: any
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: django
|
|
13
|
+
Requires-Dist: oba
|
|
14
|
+
Requires-Dist: diq
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: keywords
|
|
20
|
+
Dynamic: license
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
Dynamic: platform
|
|
23
|
+
Dynamic: requires-dist
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
field validation detector, model advanced search, unified error class
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
smartdjango/__init__.py,sha256=CEly2u6Zt8wnZ9JutDklexuCcOFwbMjisEL_ej1-n3A,752
|
|
2
|
+
smartdjango/analyse.py,sha256=f8sKVoiPC5qJY_hS5w_gGsbCIVnOciA-ok2sBOOvpSE,3378
|
|
3
|
+
smartdjango/choice.py,sha256=EYuJu4NU0lmGeVNuhi1WUYgsE2FexJeM_YXV7aPy9s0,138
|
|
4
|
+
smartdjango/code.py,sha256=u7mFnpPJUASqIfNHevsgks3M-BOSWVdo4dYLgeHkmvI,1481
|
|
5
|
+
smartdjango/error.py,sha256=xvDJkq9bJFX__Daj5ACsPzm6LcUdgIu_OQnKmY-kjfU,3140
|
|
6
|
+
smartdjango/middleware.py,sha256=q0eirW_f-dBZ9atTF_NCG1tCKyGA5IxuYxkFZn4ISqY,1067
|
|
7
|
+
smartdjango/paginator.py,sha256=BhN6lwcvgduYvO7CV0zoQ7OhmjiR25bfJvzAjTKWYa4,2189
|
|
8
|
+
smartdjango/models/__init__.py,sha256=xKJfWojCOCw3CpbCc0EQkJ9O2gRiJtHGqML32Y0posQ,461
|
|
9
|
+
smartdjango/models/manager.py,sha256=CS-7nLRElxX6RVT4S4a8gv59Xd4-198cLMVQq1ANAAY,329
|
|
10
|
+
smartdjango/models/queryset.py,sha256=iHP67SY4C7luc-EBa3b2Dm6xSBFXhNzC3WbTmaP3dJI,166
|
|
11
|
+
smartdjango/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
smartdjango/utils/inspect.py,sha256=-6gTEfEWVS1TL545G6tPUTu_RD157bqTLr9PKQC3VA0,220
|
|
13
|
+
smartdjango/utils/io.py,sha256=bECY69QVi-9lxOl__OaFIYVCw9CYt1CxglRO1Rnx6ks,1583
|
|
14
|
+
smartdjango/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
smartdjango/validation/dict_validator.py,sha256=4ICxEuO9UwkQTQvNseW5FOb0ILIVNaikEE6gqaDOE3o,2065
|
|
16
|
+
smartdjango/validation/key.py,sha256=Q6bKmHgmwC7Rw9zRuv6dEp2F0GgIR1ZYRd8YfU41fMM,703
|
|
17
|
+
smartdjango/validation/list_validator.py,sha256=RLFiGxkNR9RK27CZlNB_YYXzy9T5QCD9Zchu7JhwcSE,1817
|
|
18
|
+
smartdjango/validation/params.py,sha256=m7U_YjGXyP1tubcyhT6qEkixy4FORSLMFpbctv60fRY,2042
|
|
19
|
+
smartdjango/validation/validator.py,sha256=ImT_Awk4PWD6FNBhbTfUnjTXvtKn01ykXwWW-rtCeQw,7137
|
|
20
|
+
smartdjango-4.4.0.dist-info/licenses/LICENSE,sha256=MNmQxQoNjYQdJiUYOg0kOT_lCLbdR8p53uuWSFjNNss,1065
|
|
21
|
+
smartdjango-4.4.0.dist-info/METADATA,sha256=ijPO4CNaDcXmxXtxumGnJh3fr0zUiQvD3gmw3LcII94,580
|
|
22
|
+
smartdjango-4.4.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
23
|
+
smartdjango-4.4.0.dist-info/top_level.txt,sha256=U7Ho0CB4EW-R-rDFMAC7MU9kOsoR4Babc2O434oW_UM,12
|
|
24
|
+
smartdjango-4.4.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 JyonnLIU
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
smartdjango
|