diff --git a/README.md b/README.md index d7f0919..d116d80 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ [![Build Status](https://travis-ci.org/orsinium/djburger.svg?branch=master)](https://travis-ci.org/orsinium/djburger) [![Documentation](https://readthedocs.org/projects/djburger/badge/)](https://djburger.readthedocs.io/en/latest/) [![PyPI version](https://img.shields.io/pypi/v/djburger.svg)](https://pypi.python.org/pypi/djburger) [![Status](https://img.shields.io/pypi/status/djburger.svg)](https://pypi.python.org/pypi/djburger) [![Code size](https://img.shields.io/github/languages/code-size/orsinium/djburger.svg)](https://github.com/orsinium/djburger) [![License](https://img.shields.io/pypi/l/djburger.svg)](LICENSE) -**DjBurger** -- framework for big Django projects. +**DjBurger** -- framework for safe and maintainable web-projects. What DjBurger do? -* Split Django views into steps for secure and clean code. +* Split Django views into [steps](https://djburger.readthedocs.io/en/latest/philosophy.html#dataflow) for secure and clean code. * Provide built-in objects for all steps. -* Integrates this many side libraries like Django REST Framework and Marshmallow. +* Integrates this [many side libraries](https://djburger.readthedocs.io/en/latest/external.html) like Django REST Framework and Marshmallow. DjBurger doesn't depend on Django. You can use it in any projects if you want. @@ -23,17 +23,16 @@ Read more into [documentation](https://djburger.readthedocs.io/en/latest/). 3. Reusable input and output data formats. 4. More clean views. - ## Dataflow -1. **Decorators** (`d`). Feel free to use any side Django decorators like `csrf_exempt`. -2. **Parser** (`p`). Parse request body. -3. **PreValidator** (`prev`). Validate and clear request. -4. **PreRenderer** (`prer`). Render and return PreValidation errors. -5. **Controller** (`c`). Main logic: do some things. -6. **PostValidator** (`postv`). Validate and clear response. -7. **PostRenderer** (`postr`). Render and return PostValidation errors. -8. **Renderer** (`r`). Render successful response. +1. **Decorators**. Feel free to use any side Django decorators like `csrf_exempt`. +2. **Parser**. Parse request body. +3. **PreValidator**. Validate and clear request. +4. **PreRenderer**. Render and return PreValidation errors response. +5. **Controller**. Main logic: do some things. +6. **PostValidator**. Validate and clear response. +7. **PostRenderer**. Render and return PostValidation errors response. +8. **Renderer**. Render successful response. ![Scheme](wiki/source/imgs/scheme.png) @@ -47,3 +46,4 @@ Required only Controller and Renderer. 1. If you have some questions then [view issues](https://github.com/orsinium/djburger/issues) or [create new](https://github.com/orsinium/djburger/issues/new). 1. If you found some mistakes then fix it and [create Pull Request](https://github.com/orsinium/djburger/compare). Contributors are welcome. 1. [Star this project on github](https://github.com/orsinium/djburger) :) + diff --git a/djburger/controllers.py b/djburger/controllers.py index 7f6a4f0..8219594 100644 --- a/djburger/controllers.py +++ b/djburger/controllers.py @@ -207,10 +207,10 @@ def __call__(self, request, data, **kwargs): class pre(object): # noQA """Decorator for input data validation before subcontroller calling - :param djburger.v.b.IValidator validator: validator for pre-validation. + :param djburger.validators.bases.IValidator validator: validator for pre-validation. :param \**kwargs: kwargs for validator. - :raises djburger.e.SubValidationError: if pre-validation not passed + :raises djburger.exceptions.SubValidationError: if pre-validation not passed """ def __init__(self, validator, **kwargs): self.validator = validator @@ -238,10 +238,10 @@ def _wrapper(self, data, request=None, **kwargs): class post(pre): # noQA """Decorator for output data validation before subcontroller calling - :param djburger.v.b.IValidator validator: validator for post-validation. + :param djburger.validators.bases.IValidator validator: validator for post-validation. :param \**kwargs: kwargs for validator. - :raises djburger.e.SubValidationError: if post-validation not passed + :raises djburger.exceptions.SubValidationError: if post-validation not passed """ def _wrapper(self, data, request=None, **kwargs): result = self.controller( @@ -259,18 +259,18 @@ def _wrapper(self, data, request=None, **kwargs): return validator.cleaned_data -def subcontroller(c, prev=None, postv=None): +def subcontroller(controller, prevalidator=None, postvalidator=None): """Constructor for subcontrollers If any validation failed, immediately raise SubValidationError. - :param djburger.v.b.IValidator prev: validator for pre-validation. - :param callable c: controller. - :param djburger.v.b.IValidator postv: validator for post-validation. + :param djburger.validators.bases.IValidator prevalidator: + :param callable controller: + :param djburger.validators.bases.IValidator postvalidator: - :raises djburger.e.SubValidationError: if any validation not passed + :raises djburger.exceptions.SubValidationError: if any validation not passed """ - if prev: - c = pre(prev)(c) - if postv: - c = post(postv)(c) - return c + if prevalidator: + controller = pre(prevalidator)(controller) + if postvalidator: + controller = post(postvalidator)(controller) + return controller diff --git a/djburger/exceptions.py b/djburger/exceptions.py index 8ebe848..2db0612 100644 --- a/djburger/exceptions.py +++ b/djburger/exceptions.py @@ -13,22 +13,23 @@ class StatusCodeError(ValidationError): """Validation error with specified status code Raise it from validator for return validation error with some status code. - View catch this error and call `prer` or `postr` with specified status code. + View catch this error and call `prerenderer` or `postrenderer` + with specified status code. :param int status_code: Model for deleting object. :param \**kwargs: kwargs for ValidationError. """ - def __init__(self, status_code, msg, **kwargs): + def __init__(self, status_code, *args, **kwargs): self.status_code = status_code - self.msg = msg - super(StatusCodeError, self).__init__(msg, **kwargs) + super(StatusCodeError, self).__init__(*args, **kwargs) class SubValidationError(ValidationError): """ValidationError for validators in subcontrollers. If calidation in subcontroller not passed, subcontroller raise this error - for stoping execution and returning `postr` with failed subcontroller's validator. + for stoping execution and returning `postrenderer` with failed + subcontroller's validator. """ pass diff --git a/djburger/mocks.py b/djburger/mocks.py index 90e6542..6718123 100644 --- a/djburger/mocks.py +++ b/djburger/mocks.py @@ -43,5 +43,5 @@ def __call__(self, *args, **kwargs): return self.dispatch(*args, **kwargs) -class QuerySet: +class QuerySet(object): pass diff --git a/djburger/parsers.py b/djburger/parsers.py index 57c8bc7..9c45835 100644 --- a/djburger/parsers.py +++ b/djburger/parsers.py @@ -3,7 +3,7 @@ from functools import partial from json import loads as _json # project -from djburger.utils import is_django_installed +from .utils import is_django_installed # Django diff --git a/djburger/renderers.py b/djburger/renderers.py index e19c3e3..99508d4 100644 --- a/djburger/renderers.py +++ b/djburger/renderers.py @@ -36,11 +36,15 @@ __all__ = [ - 'Base', - 'Template', 'HTTP', - 'JSON', 'YAML', 'Tablib', - 'Redirect', 'Exception', - ] + 'BSON', 'Base', 'BaseWithHTTP', + 'Exception', + 'HTTP', + 'JSON', + 'RESTFramework', + 'Redirect', + 'Tablib', 'Template', + 'YAML', +] class Base(object): @@ -105,7 +109,7 @@ def __call__(self, request=None, data=None, validator=None, status_code=None): class BaseWithHTTP(Base): """Base class wrapped by HttpResponse - :param \**kwargs: all kwargs of djburger.r.Base. + :param \**kwargs: all kwargs of djburger.renderers.Base. """ def set_http_kwargs(self, **kwargs): @@ -199,7 +203,7 @@ def __call__(self, data=None, **kwargs): class Exception(object): # noQA """Raise Exception - I'm recommend use this renderer as `postr`. + We are recommend use this renderer as `postrenderer`. Raised exception can be handled by decorators or loggers. :param exception: exception for raising. diff --git a/djburger/views.py b/djburger/views.py index 9ad6f58..40d8267 100644 --- a/djburger/views.py +++ b/djburger/views.py @@ -20,7 +20,9 @@ __all__ = ['rule', 'ViewBase'] -_fields = ['d', 'p', 'prev', 'c', 'postv', 'prer', 'postr', 'r'] +_fields = ('decorators', 'parser', 'prevalidator', 'prerenderer', 'controller', + 'postvalidator', 'postrenderer', 'renderer') +_aliases = ('d', 'p', 'prev', 'prer', 'c', 'postv', 'postr', 'r') _Rule = namedtuple('Rule', _fields) @@ -39,55 +41,65 @@ def rule(**kwargs): Example:: >>> rule( - ... d=[login_required, csrf_exempt], # decorators - ... prev=SomeDjangoForm, # pre-validator - ... c=some_controller, # controller - ... postv=None, # post-validator + ... decorators=[login_required, csrf_exempt], + ... prevalidator=SomeDjangoForm, + ... prerenderer='postrenderer', + ... # ^ here `prerenderer` point to `postrenderer` + ... controller=some_controller, + ... postvalidator=None, ... # ^ here post-validator is missed - ... prer='postr', # renderer for pre-validator errors - ... # ^ here `prer` point to `postr` - ... postr='r', # renderer for post-validator errors - ... r=djburger.r.JSON(), # renderer + ... postrenderer='renderer', + ... renderer=djburger.renderers.JSON(), ... ) - :param list d: list of decorators. - :param callable p: parser. Parse request body. `djburger.p.Default` by default. - :param djburger.v.b.IValidator prev: Validate and clean user params. - :param callable prer: renderer for pre-validation errors. - :param callable c: controller. - :param djburger.v.b.IValidator postv: post-validator. Validate and clean response. - :param callable postr: renderer for post-validation errors. - :param callable r: renderer for successfull response. + :param list decorators: list of decorators. + :param callable parser: parse request body. `djburger.parsers.Default` by default. + :param djburger.validators.bases.IValidator prevalidator: validate and clean user params. + :param callable prerenderer: renderer for pre-validation errors. + :param callable controller: + :param djburger.validators.bases.IValidator postvalidator: validate and clean response. + :param callable postrenderer: renderer for post-validation errors. + :param callable renderer: renderer for successfull response. :return: rule. :rtype: djburger._Rule :raises TypeError: if missed `c` or `r`. """ + # aliases support + for field, alias in zip(_fields, _aliases): + if alias in kwargs: + kwargs[field] = kwargs[alias] + # check required kwargs - if 'c' not in kwargs: + if 'controller' not in kwargs: TypeError('Controller is required') - if 'r' not in kwargs: + if 'renderer' not in kwargs: TypeError('Renderer is required') # set default parser - if 'p' not in kwargs: - kwargs['p'] = _DefaultParser() + if 'parser' not in kwargs: + kwargs['parser'] = _DefaultParser() # set 'r' as default for error-renderers - for f in ('prer', 'postr'): - if f not in kwargs: - kwargs[f] = 'r' + for field in ('prerenderer', 'postrenderer'): + if field not in kwargs: + kwargs[field] = 'renderer' # set None as default for others - for f in ('d', 'prev', 'postv'): - if f not in kwargs: - kwargs[f] = None + for field in ('decorators', 'prevalidator', 'postvalidator'): + if field not in kwargs: + kwargs[field] = None + # crosslinks support for k, v in kwargs.items(): kwargs[k] = _get_value(v, kwargs) + + # drop aliases and junk + kwargs = {field: kwargs[field] for field in _fields} + # make namedtuple return _Rule(**kwargs) class ViewBase(View): - """Base views for DjBurger usage + """Base views for DjBurger usage. :param django.http.request.HttpRequest request: user request object. :param \**kwargs: kwargs from urls.py. @@ -135,8 +147,8 @@ def dispatch(self, request, **kwargs): # decorators base = self.validate_request - if self.rule.d: - for decorator in self.rule.d: + if self.rule.decorators: + for decorator in self.rule.decorators: base = decorator(base) return base(request, **kwargs) @@ -148,7 +160,7 @@ def get_data(self, request): :return: parsed data. """ - return self.rule.p(request) + return self.rule.parser(request) # pre-validator def validate_request(self, request, **kwargs): @@ -166,11 +178,11 @@ def validate_request(self, request, **kwargs): data = self.get_data(request) # no validator - if not self.rule.prev: + if not self.rule.prevalidator: return self.request_valid(data, **kwargs) # validate - validator = self.rule.prev(**self.get_validator_kwargs(data)) + validator = self.rule.prevalidator(**self.get_validator_kwargs(data)) try: is_valid = validator.is_valid() except StatusCodeError as e: @@ -187,13 +199,13 @@ def validate_request(self, request, **kwargs): def request_invalid(self, validator, status_code): """Return result of prer (renderer for pre-validator errors) - :param djburger.v.b.IValidator validator: validator object with `errors` attr. + :param djburger.validators.bases.IValidator validator: validator object with `errors` attr. :param int status_code: status code for HTTP-response. :return: django response. :rtype: django.http.HttpResponse """ - return self.rule.prer( + return self.rule.prerenderer( request=self.request, validator=validator, status_code=status_code, @@ -214,7 +226,7 @@ def request_valid(self, data, **kwargs): """ # get response from controller try: - response = self.rule.c(self.request, data, **kwargs) + response = self.rule.controller(self.request, data, **kwargs) except SubValidationError as e: validator = e.args[0] return self.subvalidation_invalid(validator) @@ -235,12 +247,12 @@ def validate_response(self, response): :rtype: django.http.HttpResponse """ # no post-validator - if not self.rule.postv: + if not self.rule.postvalidator: return self.make_response(data=response) # post-validation params = self.get_validator_kwargs(response) - validator = self.rule.postv(**params) + validator = self.rule.postvalidator(**params) try: is_valid = validator.is_valid() except StatusCodeError as e: @@ -257,7 +269,7 @@ def validate_response(self, response): def subvalidation_invalid(self, validator, status_code=200): """Return result of postr (renderer for post-validation errors). - :param djburger.v.b.IValidator validator: validator object with `errors` attr. + :param djburger.validators.bases.IValidator validator: validator object with `errors` attr. :param int status_code: status code for HTTP-response. :return: django response. @@ -269,13 +281,13 @@ def subvalidation_invalid(self, validator, status_code=200): def response_invalid(self, validator, status_code): """Return result of postr (renderer for post-validation errors). - :param djburger.v.b.IValidator validator: validator object with `errors` attr. + :param djburger.validators.bases.IValidator validator: validator object with `errors` attr. :param int status_code: status code for HTTP-response. :return: django response. :rtype: django.http.HttpResponse """ - return self.rule.postr( + return self.rule.postrenderer( request=self.request, validator=validator, status_code=status_code, @@ -286,7 +298,7 @@ def response_valid(self, validator): """Return result of make_response. This method calls only if postv is not None. - :param djburger.v.b.IValidator validator: validator object with `cleaned_data` attr. + :param djburger.validators.bases.IValidator validator: validator object with `cleaned_data` attr. :return: django response. :rtype: django.http.HttpResponse @@ -301,7 +313,7 @@ def make_response(self, data): :return: django response. :rtype: django.http.HttpResponse """ - return self.rule.r(request=self.request, data=data) + return self.rule.renderer(request=self.request, data=data) # send request and data into validator def get_validator_kwargs(self, data): diff --git a/example/project/views.py b/example/project/views.py index 7e53659..413110e 100644 --- a/example/project/views.py +++ b/example/project/views.py @@ -4,7 +4,7 @@ class IndexView(djburger.ViewBase): rules = { 'get': djburger.rule( - c=lambda request, data, **kwargs: 'Hello, World!', - r=djburger.r.Template(template_name='index.html'), + controller=lambda request, data, **kwargs: 'Hello, World!', + renderer=djburger.renderers.Template(template_name='index.html'), ), } diff --git a/example/users/controllers.py b/example/users/controllers.py index ebf95c7..47b5a50 100644 --- a/example/users/controllers.py +++ b/example/users/controllers.py @@ -17,8 +17,8 @@ def auth(cls, request, data): class GroupController: - list = staticmethod(djburger.c.List(model=Group)) - info = staticmethod(djburger.c.Info(model=Group)) - add = staticmethod(djburger.c.Add(model=Group)) - edit = staticmethod(djburger.c.Edit(model=Group)) - delete = staticmethod(djburger.c.Delete(model=Group)) + list = staticmethod(djburger.controllers.List(model=Group)) + info = staticmethod(djburger.controllers.Info(model=Group)) + add = staticmethod(djburger.controllers.Add(model=Group)) + edit = staticmethod(djburger.controllers.Edit(model=Group)) + delete = staticmethod(djburger.controllers.Delete(model=Group)) diff --git a/example/users/validators.py b/example/users/validators.py index d2bc948..66ebc1f 100644 --- a/example/users/validators.py +++ b/example/users/validators.py @@ -1,5 +1,5 @@ import djburger -class GroupInputValidator(djburger.v.b.Form): - name = djburger.f.CharField(label='Name', max_length=80) +class GroupInputValidator(djburger.validators.bases.Form): + name = djburger.forms.CharField(label='Name', max_length=80) diff --git a/example/users/views.py b/example/users/views.py index fead683..cc3ca48 100644 --- a/example/users/views.py +++ b/example/users/views.py @@ -12,10 +12,10 @@ class AuthAPIView(djburger.ViewBase): csrf_exempt = True default_rule = djburger.rule( - prev=AuthenticationForm, - c=controllers.UserController.auth, - postv=djburger.v.c.IsBool, - r=djburger.r.JSON(), + prevalidator=AuthenticationForm, + controller=controllers.UserController.auth, + postvalidator=djburger.validators.constructors.IsBool, + renderer=djburger.renderers.JSON(), ) @@ -23,15 +23,15 @@ class GroupCommonAPIView(djburger.ViewBase): csrf_exempt = True rules = { 'get': djburger.rule( - c=controllers.GroupController.list, - postv=djburger.v.c.QuerySet, - r=djburger.r.JSON(), + controller=controllers.GroupController.list, + postvalidator=djburger.validators.constructors.QuerySet, + renderer=djburger.renderers.JSON(), ), 'post': djburger.rule( - prev=validators.GroupInputValidator, - c=controllers.GroupController.add, - postv=djburger.v.c.ModelInstance, - r=djburger.r.JSON(), + prevalidator=validators.GroupInputValidator, + controller=controllers.GroupController.add, + postvalidator=djburger.validators.constructors.ModelInstance, + renderer=djburger.renderers.JSON(), ), 'put': 'post', } @@ -41,20 +41,20 @@ class GroupActionsAPIView(djburger.ViewBase): csrf_exempt = True rules = { 'get': djburger.rule( - c=controllers.GroupController.info, - postv=djburger.v.c.ModelInstance, - r=djburger.r.JSON(), + controller=controllers.GroupController.info, + postvalidator=djburger.validators.constructors.ModelInstance, + renderer=djburger.renderers.JSON(), ), 'patch': djburger.rule( - prev=validators.GroupInputValidator, - c=controllers.GroupController.edit, - postv=djburger.v.c.ModelInstance, - r=djburger.r.JSON(), + prevalidator=validators.GroupInputValidator, + controller=controllers.GroupController.edit, + postvalidator=djburger.validators.constructors.ModelInstance, + renderer=djburger.renderers.JSON(), ), 'post': 'patch', 'delete': djburger.rule( - c=controllers.GroupController.delete, - postv=djburger.v.c.IsInt, - r=djburger.r.JSON(), + controller=controllers.GroupController.delete, + postvalidator=djburger.validators.constructors.IsInt, + renderer=djburger.renderers.JSON(), ), } diff --git a/scheme.dia b/scheme.dia deleted file mode 100644 index efd0bff..0000000 Binary files a/scheme.dia and /dev/null differ diff --git a/tests/django_tests/controllers.py b/tests/django_tests/controllers.py index 4a5d5e8..edc6f76 100644 --- a/tests/django_tests/controllers.py +++ b/tests/django_tests/controllers.py @@ -16,33 +16,33 @@ def test_objects_controllers(self): Group.objects.filter(name=name2).delete() # ADD with self.subTest(src_text='add'): - controller = djburger.c.Add(model=Group) + controller = djburger.controllers.Add(model=Group) response = controller(request=None, data={'name': name}) self.assertEqual(response.name, name) # EDIT with self.subTest(src_text='edit'): - controller = djburger.c.Edit(model=Group) + controller = djburger.controllers.Edit(model=Group) response = controller(request=None, data={'name': name2}, name=name) self.assertEqual(response.name, name2) # LIST with self.subTest(src_text='list'): - controller = djburger.c.List(model=Group) + controller = djburger.controllers.List(model=Group) response = controller(request=None, data={}) names = response.values_list('name', flat=True) self.assertIn(name2, names) with self.subTest(src_text='list filter'): - controller = djburger.c.List(model=Group) + controller = djburger.controllers.List(model=Group) response = controller(request=None, data={'name': name2}) names = response.values_list('name', flat=True) self.assertIn(name2, names) # INFO with self.subTest(src_text='info'): - controller = djburger.c.Info(model=Group) + controller = djburger.controllers.Info(model=Group) response = controller(request=None, data={}, name=name2) self.assertEqual(response.name, name2) # DELETE with self.subTest(src_text='delete'): - controller = djburger.c.Delete(model=Group) + controller = djburger.controllers.Delete(model=Group) response = controller(request=None, data={}, name=name2) self.assertEqual(response, 1) @@ -56,7 +56,7 @@ def base(request, **kwargs): return HttpResponse(data) factory = RequestFactory() - controller = djburger.c.ViewAsController(base) + controller = djburger.controllers.ViewAsController(base) with self.subTest(src_text='get'): request = factory.get('/some/url/', {'test': 'me'}) @@ -73,26 +73,26 @@ def base(request, **kwargs): def test_subcontroller(self): with self.subTest(src_text='pre pass'): - c = djburger.c.subcontroller( - prev=djburger.v.c.IsStr, - c=lambda data, request, **kwargs: data, + subcontroller = djburger.controllers.subcontroller( + prevalidator=djburger.validators.constructors.IsStr, + controller=lambda data, request, **kwargs: data, ) data = 'lol' - result = c(data=data) + result = subcontroller(data=data) self.assertEqual(result, data) with self.subTest(src_text='post pass'): - c = djburger.c.subcontroller( - c=lambda data, request, **kwargs: data, - postv=djburger.v.c.IsStr, + subcontroller = djburger.controllers.subcontroller( + controller=lambda data, request, **kwargs: data, + postvalidator=djburger.validators.constructors.IsStr, ) data = 'lol' - result = c(data=data) + result = subcontroller(data=data) self.assertEqual(result, data) with self.subTest(src_text='pre not pass'): - c = djburger.c.subcontroller( - prev=djburger.v.c.IsStr, - c=lambda data, request, **kwargs: data, + subcontroller = djburger.controllers.subcontroller( + prevalidator=djburger.validators.constructors.IsStr, + controller=lambda data, request, **kwargs: data, ) data = 123 - with self.assertRaises(djburger.e.SubValidationError): - result = c(data=data) + with self.assertRaises(djburger.exceptions.SubValidationError): + result = subcontroller(data=data) diff --git a/tests/django_tests/parsers.py b/tests/django_tests/parsers.py index dbd6ce3..b36f10d 100644 --- a/tests/django_tests/parsers.py +++ b/tests/django_tests/parsers.py @@ -17,7 +17,7 @@ def test_default_parser(self): 'themes': ['1', '2', '4'], } request = factory.get('/some/url/', data) - p = djburger.p.Default() + p = djburger.parsers.Default() parsed_data = p(request) self.assertEqual(parsed_data, data) @@ -34,7 +34,7 @@ def test_json_parser(self): data=json.dumps(data), content_type='application/json', ) - p = djburger.p.JSON() + p = djburger.parsers.JSON() parsed_data = p(request) self.assertEqual(parsed_data, data) @@ -51,6 +51,6 @@ def test_bson_parser(self): data=bson.dumps(data), content_type='application/json', ) - p = djburger.p.BSON() + p = djburger.parsers.BSON() parsed_data = p(request) self.assertEqual(parsed_data, data) diff --git a/tests/django_tests/renderers.py b/tests/django_tests/renderers.py index 0d3a77c..141bf44 100644 --- a/tests/django_tests/renderers.py +++ b/tests/django_tests/renderers.py @@ -16,128 +16,128 @@ class DjangoRenderersTest(unittest.TestCase): def test_json_renderer(self): with self.subTest(src_text='str'): data = 'test' - content = djburger.r.JSON()(data=data).content + content = djburger.renderers.JSON()(data=data).content self.assertEqual(content, b'"test"') with self.subTest(src_text='int'): data = -13 - content = djburger.r.JSON()(data=data).content + content = djburger.renderers.JSON()(data=data).content self.assertEqual(content, b'-13') with self.subTest(src_text='dict'): data = {'data': 1516} - content = djburger.r.JSON()(data=data).content + content = djburger.renderers.JSON()(data=data).content self.assertEqual(content, b'{"data": 1516}') with self.subTest(src_text='list'): data = [1, 2, 3] - content = djburger.r.JSON()(data=data).content + content = djburger.renderers.JSON()(data=data).content self.assertEqual(content, b'[1, 2, 3]') with self.subTest(src_text='mixed'): data = {'data': [1, 2, 3]} - content = djburger.r.JSON()(data=data).content + content = djburger.renderers.JSON()(data=data).content self.assertEqual(content, b'{"data": [1, 2, 3]}') with self.subTest(src_text='non-flat'): data = 1516 - content = djburger.r.JSON(flat=False)(data=data).content + content = djburger.renderers.JSON(flat=False)(data=data).content self.assertEqual(content, b'{"data": 1516}') def test_bson_renderer(self): with self.subTest(src_text='str'): data = 'test' - content = djburger.r.BSON(flat=False)(data=data).content + content = djburger.renderers.BSON(flat=False)(data=data).content self.assertEqual(bson.loads(content), {'data': data}) with self.subTest(src_text='int'): data = -13 - content = djburger.r.BSON(flat=False)(data=data).content + content = djburger.renderers.BSON(flat=False)(data=data).content self.assertEqual(bson.loads(content), {'data': data}) with self.subTest(src_text='dict'): data = {'lol': 1516} - content = djburger.r.BSON(flat=False)(data=data).content + content = djburger.renderers.BSON(flat=False)(data=data).content self.assertEqual(bson.loads(content), {'data': data}) with self.subTest(src_text='list'): data = [1, 2, 3] - content = djburger.r.BSON(flat=False)(data=data).content + content = djburger.renderers.BSON(flat=False)(data=data).content self.assertEqual(bson.loads(content), {'data': data}) with self.subTest(src_text='mixed'): data = {'data': [1, 2, 3]} - content = djburger.r.BSON(flat=False)(data=data).content + content = djburger.renderers.BSON(flat=False)(data=data).content self.assertEqual(bson.loads(content), {'data': data}) with self.subTest(src_text='flat'): data = {'lol': 1516} - content = djburger.r.BSON(flat=True)(data=data).content + content = djburger.renderers.BSON(flat=True)(data=data).content self.assertEqual(bson.loads(content), data) def test_yaml_renderer(self): with self.subTest(src_text='str'): data = 'test' - content = djburger.r.YAML(flat=False)(data=data).content + content = djburger.renderers.YAML(flat=False)(data=data).content self.assertEqual(yaml.load(content), {'data': data}) with self.subTest(src_text='mixed'): data = [1, '2', [3, 4], {5: 6}] - content = djburger.r.YAML(flat=True)(data=data).content + content = djburger.renderers.YAML(flat=True)(data=data).content self.assertEqual(yaml.load(content), data) def test_http_renderer(self): with self.subTest(src_text='str pass'): data = 'test' - content = djburger.r.HTTP()(data=data).content + content = djburger.renderers.HTTP()(data=data).content self.assertEqual(content, b'test') with self.subTest(src_text='bytes pass'): data = b'test' - content = djburger.r.HTTP()(data=data).content + content = djburger.renderers.HTTP()(data=data).content self.assertEqual(content, b'test') with self.subTest(src_text='int pass'): data = 123 - content = djburger.r.HTTP()(data=data).content + content = djburger.renderers.HTTP()(data=data).content self.assertEqual(content, b'123') with self.subTest(src_text='list pass'): data = [1, 2, '3'] - content = djburger.r.HTTP()(data=data).content + content = djburger.renderers.HTTP()(data=data).content self.assertEqual(content, b'123') with self.subTest(src_text='dict not pass'): data = {'test': 'me'} with self.assertRaises(ValueError): - content = djburger.r.HTTP()(data=data) + content = djburger.renderers.HTTP()(data=data) def test_redirect_renderer(self): with self.subTest(src_text='url by init: code'): data = '/login/' - code = djburger.r.Redirect()(data=data).status_code + code = djburger.renderers.Redirect()(data=data).status_code self.assertEqual(code, 302) with self.subTest(src_text='url by init: url'): data = '/login/' - url = djburger.r.Redirect()(data=data).url + url = djburger.renderers.Redirect()(data=data).url self.assertEqual(url, '/login/') def test_exception_renderer(self): with self.subTest(src_text='ValidationError'): with self.assertRaises(ValidationError): - djburger.r.Exception()(data='test') + djburger.renderers.Exception()(data='test') with self.subTest(src_text='AssertionError'): with self.assertRaises(AssertionError): - djburger.r.Exception(AssertionError)(data='test') + djburger.renderers.Exception(AssertionError)(data='test') def test_rest_framework_renderer(self): with self.subTest(src_text='str pass'): - renderer = djburger.r.RESTFramework( + renderer = djburger.renderers.RESTFramework( renderer=rest_framework_renderers.StaticHTMLRenderer() ) data = 'test' content = renderer(data=data).content self.assertEqual(content, b'test') with self.subTest(src_text='bytes pass'): - renderer = djburger.r.RESTFramework( + renderer = djburger.renderers.RESTFramework( renderer=rest_framework_renderers.StaticHTMLRenderer() ) data = b'test' content = renderer(data=data).content self.assertEqual(content, b'test') with self.subTest(src_text='int pass'): - renderer = djburger.r.RESTFramework( + renderer = djburger.renderers.RESTFramework( renderer=rest_framework_renderers.StaticHTMLRenderer() ) data = 123 content = renderer(data=data).content with self.subTest(src_text='list pass'): - renderer = djburger.r.RESTFramework( + renderer = djburger.renderers.RESTFramework( renderer=rest_framework_renderers.StaticHTMLRenderer() ) data = [1, 2, '3'] @@ -147,9 +147,9 @@ def test_rest_framework_renderer(self): def test_tablib_renderer(self): with self.subTest(src_text='table csv'): data = [[1, 2, 3], [4, 5, 6]] - content = djburger.r.Tablib('csv')(data=data).content + content = djburger.renderers.Tablib('csv')(data=data).content self.assertEqual(content.split(), [b'1,2,3', b'4,5,6']) with self.subTest(src_text='table json'): data = [[1, 2, 3], [4, 5, 6]] - content = djburger.r.Tablib('json')(data=data).content + content = djburger.renderers.Tablib('json')(data=data).content self.assertEqual(json.loads(content.decode('utf-8')), data) diff --git a/tests/django_tests/validators.py b/tests/django_tests/validators.py index 9585b32..77586c0 100644 --- a/tests/django_tests/validators.py +++ b/tests/django_tests/validators.py @@ -21,9 +21,9 @@ def tearDownClass(cls): Group.objects.get(name='TEST_IT_2').delete() def test_base_validator(self): - class Base(djburger.v.b.Form): - name = djburger.f.CharField(max_length=20) - mail = djburger.f.EmailField() + class Base(djburger.validators.bases.Form): + name = djburger.forms.CharField(max_length=20) + mail = djburger.forms.EmailField() with self.subTest(src_text='base pass'): data = {'name': 'John Doe', 'mail': 'test@gmail.com'} v = Base(request=None, data=data) @@ -34,10 +34,10 @@ class Base(djburger.v.b.Form): self.assertFalse(v.is_valid()) def test_wrapper_validator(self): - class Base(djburger.f.Form): - name = djburger.f.CharField(max_length=20) - mail = djburger.f.EmailField() - Wrapped = djburger.v.w.Form(Base) # noQA + class Base(djburger.forms.Form): + name = djburger.forms.CharField(max_length=20) + mail = djburger.forms.EmailField() + Wrapped = djburger.validators.wrappers.Form(Base) # noQA with self.subTest(src_text='base pass'): data = {'name': 'John Doe', 'mail': 'test@gmail.com'} v = Wrapped(request=None, data=data) diff --git a/tests/django_tests/views.py b/tests/django_tests/views.py index c3a4f20..d4d6cb9 100644 --- a/tests/django_tests/views.py +++ b/tests/django_tests/views.py @@ -11,8 +11,8 @@ class DjangoViewsTest(unittest.TestCase): def test_controller(self): class Base(djburger.ViewBase): default_rule = djburger.rule( - c=lambda request, data, **kwargs: data, - r=lambda data, **kwargs: data, + controller=lambda request, data, **kwargs: data, + renderer=lambda data, **kwargs: data, ) view = Base.as_view() @@ -23,10 +23,10 @@ class Base(djburger.ViewBase): self.assertEqual(response, data) def test_validator(self): - class Validator(djburger.v.b.Form): - name = djburger.f.CharField(max_length=20) - mail = djburger.f.EmailField() - themes = djburger.f.MultipleChoiceField(choices=( + class Validator(djburger.validators.bases.Form): + name = djburger.forms.CharField(max_length=20) + mail = djburger.forms.EmailField() + themes = djburger.forms.MultipleChoiceField(choices=( (1, 'one'), (2, 'two'), (3, 'three'), @@ -34,9 +34,9 @@ class Validator(djburger.v.b.Form): class Base(djburger.ViewBase): default_rule = djburger.rule( - prev=Validator, - c=lambda request, data, **kwargs: data, - r=lambda **kwargs: kwargs, + prevalidator=Validator, + controller=lambda request, data, **kwargs: data, + renderer=lambda **kwargs: kwargs, ) view = Base.as_view() @@ -62,10 +62,10 @@ class Base(djburger.ViewBase): self.assertEqual(errors, {'themes', 'mail'}) def test_postvalidator(self): - class Validator(djburger.v.b.Form): - name = djburger.f.CharField(max_length=20) - mail = djburger.f.EmailField() - themes = djburger.f.MultipleChoiceField(choices=( + class Validator(djburger.validators.bases.Form): + name = djburger.forms.CharField(max_length=20) + mail = djburger.forms.EmailField() + themes = djburger.forms.MultipleChoiceField(choices=( (1, 'one'), (2, 'two'), (3, 'three'), @@ -73,9 +73,9 @@ class Validator(djburger.v.b.Form): class Base(djburger.ViewBase): default_rule = djburger.rule( - c=lambda request, data, **kwargs: data, - postv=Validator, - r=lambda **kwargs: kwargs, + controller=lambda request, data, **kwargs: data, + postvalidator=Validator, + renderer=lambda **kwargs: kwargs, ) view = Base.as_view() diff --git a/tests/djside_tests.py b/tests/djside_tests.py index b5f40ad..688ee23 100644 --- a/tests/djside_tests.py +++ b/tests/djside_tests.py @@ -25,7 +25,7 @@ def tearDownClass(cls): def test_pyschemes(self): with self.subTest(src_text='pyschemes'): - v = djburger.v.c.PySchemes( + v = djburger.validators.constructors.PySchemes( {'name': str, 'id': int}, policy='drop' ) @@ -34,26 +34,26 @@ def test_pyschemes(self): def test_marshmallow(self): with self.subTest(src_text='marshmallow base'): - class Base(djburger.v.b.Marshmallow): + class Base(djburger.validators.bases.Marshmallow): name = marshmallow.fields.Str() v = Base(request=None, data=self.obj) self.assertTrue(v.is_valid()) with self.subTest(src_text='marshmallow wrapper'): class Base(marshmallow.Schema): name = marshmallow.fields.Str() - Wrapped = djburger.v.w.Marshmallow(Base) # noQA + Wrapped = djburger.validators.wrappers.Marshmallow(Base) # noQA v = Wrapped(request=None, data=self.obj) self.assertTrue(v.is_valid()) def test_rest(self): with self.subTest(src_text='rest framework base'): - class Base(djburger.v.b.RESTFramework): + class Base(djburger.validators.bases.RESTFramework): name = rest_framework.serializers.CharField(max_length=20) v = Base(request=None, data=self.obj) self.assertTrue(v.is_valid()) with self.subTest(src_text='rest framework wrapper'): class Base(rest_framework.serializers.Serializer): name = rest_framework.serializers.CharField(max_length=20) - Wrapped = djburger.v.w.RESTFramework(Base) # noQA + Wrapped = djburger.validators.wrappers.RESTFramework(Base) # noQA v = Wrapped(request=None, data=self.obj) self.assertTrue(v.is_valid()) diff --git a/tests/main_tests.py b/tests/main_tests.py index 33b5fca..6b6acb2 100644 --- a/tests/main_tests.py +++ b/tests/main_tests.py @@ -6,154 +6,154 @@ class MainValidatorsTest(unittest.TestCase): def test_type_validator(self): # BASE with self.subTest(src_text='base pass'): - v = djburger.v.c.Type(int) + v = djburger.validators.constructors.Type(int) v = v(3) self.assertTrue(v.is_valid()) with self.subTest(src_text='base not pass'): - v = djburger.v.c.Type(int) + v = djburger.validators.constructors.Type(int) v = v('3') self.assertFalse(v.is_valid()) # BOOL with self.subTest(src_text='bool pass'): - v = djburger.v.c.IsBool + v = djburger.validators.constructors.IsBool v = v(False) self.assertTrue(v.is_valid()) with self.subTest(src_text='bool int not pass'): - v = djburger.v.c.IsBool + v = djburger.validators.constructors.IsBool v = v(1) self.assertFalse(v.is_valid()) with self.subTest(src_text='bool not pass'): - v = djburger.v.c.IsBool + v = djburger.validators.constructors.IsBool v = v('1') self.assertFalse(v.is_valid()) # INT with self.subTest(src_text='int pass'): - v = djburger.v.c.IsInt + v = djburger.validators.constructors.IsInt v = v(3) self.assertTrue(v.is_valid()) with self.subTest(src_text='int bool not pass'): - v = djburger.v.c.IsInt + v = djburger.validators.constructors.IsInt v = v(True) self.assertFalse(v.is_valid()) with self.subTest(src_text='int not pass'): - v = djburger.v.c.IsInt + v = djburger.validators.constructors.IsInt v = v('4') self.assertFalse(v.is_valid()) # FLOAT with self.subTest(src_text='float pass'): - v = djburger.v.c.IsFloat + v = djburger.validators.constructors.IsFloat v = v(3.2) self.assertTrue(v.is_valid()) with self.subTest(src_text='float bool not pass'): - v = djburger.v.c.IsFloat + v = djburger.validators.constructors.IsFloat v = v(True) self.assertFalse(v.is_valid()) with self.subTest(src_text='float not pass'): - v = djburger.v.c.IsFloat + v = djburger.validators.constructors.IsFloat v = v(4) self.assertFalse(v.is_valid()) # STR with self.subTest(src_text='str pass'): - v = djburger.v.c.IsStr + v = djburger.validators.constructors.IsStr v = v('1') self.assertTrue(v.is_valid()) with self.subTest(src_text='str empty pass'): - v = djburger.v.c.IsStr + v = djburger.validators.constructors.IsStr v = v('') self.assertTrue(v.is_valid()) with self.subTest(src_text='str not pass'): - v = djburger.v.c.IsStr + v = djburger.validators.constructors.IsStr v = v(1) self.assertFalse(v.is_valid()) # DICT with self.subTest(src_text='dict pass'): - v = djburger.v.c.IsDict + v = djburger.validators.constructors.IsDict v = v({1: 2}) self.assertTrue(v.is_valid()) with self.subTest(src_text='dict empty pass'): - v = djburger.v.c.IsDict + v = djburger.validators.constructors.IsDict v = v({}) self.assertTrue(v.is_valid()) with self.subTest(src_text='dict not pass'): - v = djburger.v.c.IsDict + v = djburger.validators.constructors.IsDict v = v([1, 2, 3]) self.assertFalse(v.is_valid()) # LIST with self.subTest(src_text='list pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v([1, 2]) self.assertTrue(v.is_valid()) with self.subTest(src_text='list empty pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v([]) self.assertTrue(v.is_valid()) with self.subTest(src_text='list tuple pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v((1, 2, 3)) self.assertTrue(v.is_valid()) with self.subTest(src_text='list not pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v({}) self.assertFalse(v.is_valid()) # ITER with self.subTest(src_text='iter list pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v([1, 2]) self.assertTrue(v.is_valid()) with self.subTest(src_text='iter tuple pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v((1, 2)) self.assertTrue(v.is_valid()) with self.subTest(src_text='iter empty pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v([]) self.assertTrue(v.is_valid()) with self.subTest(src_text='iter str not pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v('123') self.assertFalse(v.is_valid()) with self.subTest(src_text='iter dict not pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v({1: 2, 3: 4}) self.assertFalse(v.is_valid()) with self.subTest(src_text='iter not pass'): - v = djburger.v.c.IsList + v = djburger.validators.constructors.IsList v = v(4) self.assertFalse(v.is_valid()) def test_list_validator(self): with self.subTest(src_text='list int pass'): - v = djburger.v.c.List(djburger.v.c.IsInt) + v = djburger.validators.constructors.List(djburger.validators.constructors.IsInt) v = v([1, 2, 3]) self.assertTrue(v.is_valid()) with self.subTest(src_text='list mixed not pass'): - v = djburger.v.c.List(djburger.v.c.IsInt) + v = djburger.validators.constructors.List(djburger.validators.constructors.IsInt) v = v([1, '2', 3]) self.assertFalse(v.is_valid()) with self.subTest(src_text='list str pass'): - v = djburger.v.c.List(djburger.v.c.IsStr) + v = djburger.validators.constructors.List(djburger.validators.constructors.IsStr) v = v(['1', '2', '3']) self.assertTrue(v.is_valid()) with self.subTest(src_text='tuple str pass'): - v = djburger.v.c.List(djburger.v.c.IsStr) + v = djburger.validators.constructors.List(djburger.validators.constructors.IsStr) v = v(('1', '2', '3')) self.assertTrue(v.is_valid()) with self.subTest(src_text='str not pass'): - v = djburger.v.c.List(djburger.v.c.IsStr) + v = djburger.validators.constructors.List(djburger.validators.constructors.IsStr) v = v('123') self.assertFalse(v.is_valid()) with self.subTest(src_text='list list str pass'): - v = djburger.v.c.List( - djburger.v.c.List( - djburger.v.c.IsStr + v = djburger.validators.constructors.List( + djburger.validators.constructors.List( + djburger.validators.constructors.IsStr ) ) v = v(data=[('1', '2'), ('3', '4', '5'), ('6', )]) @@ -161,9 +161,9 @@ def test_list_validator(self): def test_dict_mixed_validator(self): with self.subTest(src_text='dict int+str pass'): - v = djburger.v.c.DictMixed(validators={ - 'ping': djburger.v.c.IsInt, - 'pong': djburger.v.c.IsStr, + v = djburger.validators.constructors.DictMixed(validators={ + 'ping': djburger.validators.constructors.IsInt, + 'pong': djburger.validators.constructors.IsStr, }) v = v(data={ 'ping': 3, @@ -173,63 +173,63 @@ def test_dict_mixed_validator(self): def test_lambda_validator(self): with self.subTest(src_text='lambda int pass'): - v = djburger.v.c.Lambda(key=lambda data: data > 0) + v = djburger.validators.constructors.Lambda(key=lambda data: data > 0) v = v(4) self.assertTrue(v.is_valid()) with self.subTest(src_text='lambda int not pass'): - v = djburger.v.c.Lambda(key=lambda data: data > 0) + v = djburger.validators.constructors.Lambda(key=lambda data: data > 0) v = v(-4) self.assertFalse(v.is_valid()) def test_clean_validator(self): with self.subTest(src_text='lambda int pass'): - v = djburger.v.c.Clean(key=lambda data: int(data)) + v = djburger.validators.constructors.Clean(key=lambda data: int(data)) v = v('4') v.is_valid() self.assertEqual(v.cleaned_data, 4) def test_chain_validator(self): with self.subTest(src_text='chain int pass'): - v = djburger.v.c.Chain([ - djburger.v.c.IsInt, - djburger.v.c.Lambda(key=lambda data: data > 0), + v = djburger.validators.constructors.Chain([ + djburger.validators.constructors.IsInt, + djburger.validators.constructors.Lambda(key=lambda data: data > 0), ]) v = v(4) self.assertTrue(v.is_valid()) with self.subTest(src_text='lambda int not pass'): - v = djburger.v.c.Chain([ - djburger.v.c.IsInt, - djburger.v.c.Lambda(key=lambda data: data > 0), + v = djburger.validators.constructors.Chain([ + djburger.validators.constructors.IsInt, + djburger.validators.constructors.Lambda(key=lambda data: data > 0), ]) v = v(-4) self.assertFalse(v.is_valid()) with self.subTest(src_text='lambda str not pass'): - v = djburger.v.c.Chain([ - djburger.v.c.IsInt, - djburger.v.c.Lambda(key=lambda data: data > 0), + v = djburger.validators.constructors.Chain([ + djburger.validators.constructors.IsInt, + djburger.validators.constructors.Lambda(key=lambda data: data > 0), ]) v = v('4') self.assertFalse(v.is_valid()) def test_or_validator(self): with self.subTest(src_text='or int pass'): - v = djburger.v.c.Or([ - djburger.v.c.IsInt, - djburger.v.c.IsStr, + v = djburger.validators.constructors.Or([ + djburger.validators.constructors.IsInt, + djburger.validators.constructors.IsStr, ]) v = v(4) self.assertTrue(v.is_valid()) with self.subTest(src_text='or str pass'): - v = djburger.v.c.Or([ - djburger.v.c.IsInt, - djburger.v.c.IsStr, + v = djburger.validators.constructors.Or([ + djburger.validators.constructors.IsInt, + djburger.validators.constructors.IsStr, ]) v = v('lol') self.assertTrue(v.is_valid()) with self.subTest(src_text='or list not pass'): - v = djburger.v.c.Or([ - djburger.v.c.IsInt, - djburger.v.c.IsStr, + v = djburger.validators.constructors.Or([ + djburger.validators.constructors.IsInt, + djburger.validators.constructors.IsStr, ]) v = v([4, 5]) self.assertFalse(v.is_valid()) diff --git a/tests/rest_tests.py b/tests/rest_tests.py index a91412a..47db832 100644 --- a/tests/rest_tests.py +++ b/tests/rest_tests.py @@ -7,7 +7,7 @@ class RestValidatorsTest(unittest.TestCase): def test_base_validator(self): # BASE - class Base(djburger.v.b.RESTFramework): + class Base(djburger.validators.bases.RESTFramework): name = rest_framework.serializers.CharField(max_length=20) mail = rest_framework.serializers.EmailField() with self.subTest(src_text='base pass'): @@ -23,7 +23,7 @@ def test_wrapper_validator(self): class Base(rest_framework.serializers.Serializer): name = rest_framework.serializers.CharField(max_length=20) mail = rest_framework.serializers.EmailField() - Wrapped = djburger.v.w.RESTFramework(Base) # noQA + Wrapped = djburger.validators.wrappers.RESTFramework(Base) # noQA with self.subTest(src_text='base pass'): data = {'name': 'John Doe', 'mail': 'test@gmail.com'} v = Wrapped(request=None, data=data) diff --git a/tests/side_tests.py b/tests/side_tests.py index eb76d61..0022460 100644 --- a/tests/side_tests.py +++ b/tests/side_tests.py @@ -9,7 +9,7 @@ class MarshmallowValidatorsTest(unittest.TestCase): def test_base_validator(self): # BASE - class Base(djburger.v.b.Marshmallow): + class Base(djburger.validators.bases.Marshmallow): name = marshmallow.fields.Str() mail = marshmallow.fields.Email() with self.subTest(src_text='base pass'): @@ -26,7 +26,7 @@ class Base(marshmallow.Schema): name = marshmallow.fields.Str() mail = marshmallow.fields.Email() - Wrapped = djburger.v.w.Marshmallow(Base) # noQA + Wrapped = djburger.validators.wrappers.Marshmallow(Base) # noQA with self.subTest(src_text='base pass'): data = {'name': 'John Doe', 'mail': 'test@gmail.com'} v = Wrapped(request=None, data=data) @@ -42,31 +42,31 @@ class PySchemesValidatorsTest(unittest.TestCase): def test_base_validator(self): # BASE with self.subTest(src_text='base pass'): - v = djburger.v.c.PySchemes([str, 2, int]) + v = djburger.validators.constructors.PySchemes([str, 2, int]) v = v(request=None, data=['3', 2, 4]) v.is_valid() self.assertTrue(v.is_valid()) with self.subTest(src_text='base not pass'): - v = djburger.v.c.PySchemes([str, 2, int]) + v = djburger.validators.constructors.PySchemes([str, 2, int]) v = v(request=None, data=[1, 2, 4]) self.assertFalse(v.is_valid()) with self.subTest(src_text='base int data'): - v = djburger.v.c.PySchemes(int) + v = djburger.validators.constructors.PySchemes(int) v = v(request=None, data=3) v.is_valid() self.assertEqual(v.cleaned_data, 3) def test_wrapper_validator(self): with self.subTest(src_text='base pass'): - v = djburger.v.w.PySchemes(PySchemes([str, 2, int])) + v = djburger.validators.wrappers.PySchemes(PySchemes([str, 2, int])) v = v(request=None, data=['3', 2, 4]) self.assertTrue(v.is_valid()) with self.subTest(src_text='base not pass'): - v = djburger.v.w.PySchemes(PySchemes([str, 2, int])) + v = djburger.validators.wrappers.PySchemes(PySchemes([str, 2, int])) v = v(request=None, data=[1, 2, 4]) self.assertFalse(v.is_valid()) with self.subTest(src_text='base int data'): - v = djburger.v.w.PySchemes(PySchemes(int)) + v = djburger.validators.wrappers.PySchemes(PySchemes(int)) v = v(request=None, data=3) v.is_valid() self.assertEqual(v.cleaned_data, 3) diff --git a/tox.ini b/tox.ini index aeef647..e203492 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,7 @@ envlist = py{2,3}-{MAIN,SIDE}, py2-django{18,110,111}-{DJSIDE,DJANGO}, py3-django{18,110,111,20}-{DJSIDE,DJANGO}, - flake8, - + flake8, docs, readme, [testenv] commands = @@ -36,7 +35,6 @@ deps = # testing py2: unittest2 - [testenv:flake8] max-line-length=120 skip_install = True @@ -44,3 +42,19 @@ deps = flake8 commands = flake8 {toxinidir}/djburger + +[testenv:docs] +commands = + make html +changedir = {toxinidir}/wiki/ +deps = + sphinx + recommonmark + sphinx_rtd_theme + +[testenv:readme] +commands = + python setup.py check -rs +deps = + docutils + diff --git a/wiki/README.md b/wiki/README.md new file mode 100644 index 0000000..86ea8ad --- /dev/null +++ b/wiki/README.md @@ -0,0 +1,14 @@ +# DjBurger documentation + +## Build + +1. Install dependencies: + ```bash + sudo pip3 install sphinx recommonmark sphinx_rtd_theme + ``` +1. Generate html: + ```bash + make html + ``` +1. Open [generated HTML](build/html/index.html). + diff --git a/wiki/source/aliases.rst b/wiki/source/aliases.rst new file mode 100644 index 0000000..67864d3 --- /dev/null +++ b/wiki/source/aliases.rst @@ -0,0 +1,56 @@ +Short names +=========== + +DjBurger provide some short aliases. Please, do not use it into big and +Open Source packages. Use it only for personal projects. + +Rule +---- + +You can use short kwargs for `rule `__ +function: + ++-----------------+--------------+ +| Full name | Short name | ++=================+==============+ +| decorators | d | ++-----------------+--------------+ +| parser | p | ++-----------------+--------------+ +| prevalidator | prev | ++-----------------+--------------+ +| prerenderer | prer | ++-----------------+--------------+ +| controller | c | ++-----------------+--------------+ +| postvalidator | postv | ++-----------------+--------------+ +| postrenderer | postr | ++-----------------+--------------+ +| renderer | r | ++-----------------+--------------+ + +Modules +------- + +Also DjBurger modules has short names: + ++------------------------------------+----------------+ +| Full name | Short name | ++====================================+================+ +| djburger.controllers | djburger.c | ++------------------------------------+----------------+ +| djburger.exceptions | djburger.e | ++------------------------------------+----------------+ +| djburger.forms | djburger.f | ++------------------------------------+----------------+ +| djburger.parsers | djburger.p | ++------------------------------------+----------------+ +| djburger.renderers | djburger.r | ++------------------------------------+----------------+ +| djburger.validators.bases | djburger.v.b | ++------------------------------------+----------------+ +| djburger.validators.constructors | djburger.v.c | ++------------------------------------+----------------+ +| djburger.validators.wrappers | djburger.v.w | ++------------------------------------+----------------+ diff --git a/wiki/source/changelog.md b/wiki/source/changelog.md new file mode 100644 index 0000000..1f03d32 --- /dev/null +++ b/wiki/source/changelog.md @@ -0,0 +1,24 @@ +# Changelog + +See [projects tab on Github](https://github.com/orsinium/djburger/projects) for roadmap. + +### [v. 0.10.0](https://github.com/orsinium/djburger/releases/tag/0.10.0) + +* Long kwargs for rule function and subcontroller. +* Replaced aliases to long names into documentation, tests and examples. +* Added documentation tests +* Added info about aliases into documentation. + + +### [v. 0.9.0](https://github.com/orsinium/djburger/releases/tag/0.9.0) + +* Good documentation. +* Short readme. +* First anounce on Reddit. + + +### [v. 0.8.0](https://github.com/orsinium/djburger/releases/tag/0.8.0) + +* Tested and improved. +* Many custom renderers. + diff --git a/wiki/source/examples.md b/wiki/source/examples.md index a6cece9..437982e 100644 --- a/wiki/source/examples.md +++ b/wiki/source/examples.md @@ -8,10 +8,10 @@ import djburger class ExampleView(djburger.ViewBase): rules = { 'get': djburger.rule( - c=lambda request, data, **kwargs: 'Hello, World!', # controller - postv=djburger.v.c.IsStr, # post-validator - postr=djburger.r.Exception(), # post-renderer - r=djburger.r.Template(template_name='index.html'), # renderer + controller=lambda request, data, **kwargs: 'Hello, World!', + postvalidator=djburger.validators.constructors.IsStr, + postrenderer=djburger.renderers.Exception(), + renderer=djburger.renderers.Template(template_name='index.html'), ), } ``` @@ -21,12 +21,12 @@ Minimum info: ```python class ExampleView(djburger.ViewBase): default_rule = djburger.rule( - c=lambda request, data, **kwargs: 'Hello, World!', - r=djburger.r.Template(template_name='index.html'), + controller=lambda request, data, **kwargs: 'Hello, World!', + renderer=djburger.renderers.Template(template_name='index.html'), ), ``` -All requests without the method defined in the ``rules`` will use the rule from ``default_rule``. +All requests without the method defined in the `rules` will use the rule from `default_rule`. Example: @@ -34,19 +34,19 @@ Example: class UsersView(djburger.ViewBase): rules = { 'get': djburger.rule( - d=[login_required, csrf_exempt], - prev=SomeValidator, - c=djburger.c.List(model=User), - postv=djburger.v.c.QuerySet, - postr=djburger.r.Exception(), - r=djburger.r.JSON(), + decorators=[login_required, csrf_exempt], + prevalidator=SomeValidator, + controller=djburger.controllers.List(model=User), + postvalidator=djburger.validators.constructors.QuerySet, + postrenderer=djburger.renderers.Exception(), + renderer=djburger.renderers.JSON(), ), 'put': djburger.rule( - d=[csrf_exempt], - p=djburger.p.JSON(), - prev=SomeOtherValidator, - c=djburger.c.Add(model=User), - r=djburger.r.JSON(), + decorators=[csrf_exempt], + parser=djburger.parsers.JSON(), + prevalidator=SomeOtherValidator, + controller=djburger.controllers.Add(model=User), + renderer=djburger.renderers.JSON(), ), } ``` @@ -57,11 +57,11 @@ class UsersView(djburger.ViewBase): Simple base validator: ```python -class GroupInputValidator(djburger.v.b.Form): - name = djburger.f.CharField(label='Name', max_length=80) +class GroupInputValidator(djburger.validators.bases.Form): + name = djburger.forms.CharField(label='Name', max_length=80) ``` -`djburger.f` is just alias for `django.forms`. +`djburger.forms` is useful alias for `django.forms`. Simple wrapper: @@ -72,7 +72,7 @@ from django import forms class GroupInputForm(forms.Form): name = forms.CharField(label='Name', max_length=80) -Validator = djburger.v.w.Form(GroupInputForm) +Validator = djburger.validators.wrappers.Form(GroupInputForm) ``` See [usage](usage.html) for more examples and explore [example project](https://github.com/orsinium/djburger/tree/master/example). diff --git a/wiki/source/external.md b/wiki/source/external.md index 416b955..2994c13 100644 --- a/wiki/source/external.md +++ b/wiki/source/external.md @@ -1,19 +1,19 @@ # External libraries support * [BSON](https://github.com/py-bson/bson) - * `djburger.p.BSON` - * `djburger.r.BSON` + * `djburger.parsers.BSON` + * `djburger.renderers.BSON` * [Django REST Framework](django-rest-framework.org) - * `djburger.v.b.RESTFramework` - * `djburger.v.w.RESTFramework` - * `djburger.r.RESTFramework` + * `djburger.validators.bases.RESTFramework` + * `djburger.validators.wrappers.RESTFramework` + * `djburger.renderers.RESTFramework` * [Marshmallow](https://github.com/marshmallow-code/marshmallow) - * `djburger.v.b.Marshmallow` - * `djburger.v.w.Marshmallow` + * `djburger.validators.bases.Marshmallow` + * `djburger.validators.wrappers.Marshmallow` * [PySchemes](https://github.com/shivylp/pyschemes) - * `djburger.v.c.PySchemes` - * `djburger.v.w.PySchemes` + * `djburger.validators.constructors.PySchemes` + * `djburger.validators.wrappers.PySchemes` * [PyYAML](https://github.com/yaml/pyyaml) - * `djburger.r.YAML` + * `djburger.renderers.YAML` * [Tablib](https://github.com/kennethreitz/tablib) - * `djburger.r.Tablib` + * `djburger.renderers.Tablib` diff --git a/wiki/source/imgs/scheme.dia b/wiki/source/imgs/scheme.dia new file mode 100644 index 0000000..e9550fc Binary files /dev/null and b/wiki/source/imgs/scheme.dia differ diff --git a/wiki/source/imgs/scheme.png b/wiki/source/imgs/scheme.png index a3955b3..c5ad290 100644 Binary files a/wiki/source/imgs/scheme.png and b/wiki/source/imgs/scheme.png differ diff --git a/wiki/source/index.rst b/wiki/source/index.rst index e803848..8054bd0 100644 --- a/wiki/source/index.rst +++ b/wiki/source/index.rst @@ -26,6 +26,7 @@ DjBurger doesn't depend on Django. You can use it in any projects if you want. usage examples interfaces + aliases external .. toctree:: @@ -37,3 +38,4 @@ DjBurger doesn't depend on Django. You can use it in any projects if you want. validators controllers renderers + diff --git a/wiki/source/interfaces.md b/wiki/source/interfaces.md index 3d57bc4..0faa9f6 100644 --- a/wiki/source/interfaces.md +++ b/wiki/source/interfaces.md @@ -1,12 +1,12 @@ -# Interfaces +# Interfaces -1. **Decorator**. Any decorator which can wrap Django view +1. **Decorator**. Any decorator which can wrap Django view. 2. **Parser**. Any callable object which get request object and return parsed data. 3. **Validator**. Have same interfaces as Django Forms, but get `request` by initialization: 1. `.__init__()` - * `request` -- Request object - * `data` -- data from user (prev) or controller (postv) - * `**kwargs` -- any keyword arguments for validator + * `request` -- Request object. + * `data` -- data from user (`prevalidator`) or controller (`postvalidator`). + * `**kwargs` -- any keyword arguments for validator. 2. `.is_valid()` -- return True if data is valid False otherwise. 3. `.errors` -- errors if data is invalid. 4. `.cleaned_data` -- cleaned data if input data is valid. @@ -17,5 +17,5 @@ 5. **Renderer**. Any callable object. Kwargs: 1. `request` -- Request object. 2. `data` -- validated controller data (only for `r`). - 3. `validator` -- validator which not be passed (only for `prer` and `postr`). - 4. `status_code` -- HTTP status code if validator raise `djburger.e.StatusCodeError`. + 3. `validator` -- validator which not be passed (only for `prerenderer` and `postrenderer`). + 4. `status_code` -- HTTP status code if validator raise `djburger.exceptions.StatusCodeError`, None otherwise. diff --git a/wiki/source/philosophy.md b/wiki/source/philosophy.md index c507d78..29f8b74 100644 --- a/wiki/source/philosophy.md +++ b/wiki/source/philosophy.md @@ -11,18 +11,18 @@ ## Dataflow -1. **Decorators** (`d`). Feel free to use any side Django decorators like `csrf_exempt`. -2. **Parser** (`p`). Parse request body. -3. **PreValidator** (`prev`). Validate and clear request. -4. **PreRenderer** (`prer`). Render and return PreValidation errors. -5. **Controller** (`c`). Main logic: do some things. -6. **PostValidator** (`postv`). Validate and clear response. -7. **PostRenderer** (`postr`). Render and return PostValidation errors. -8. **Renderer** (`r`). Render successful response. +1. **Decorators**. Feel free to use any side Django decorators like `csrf_exempt`. +2. **Parser**. Parse request body. +3. **PreValidator**. Validate and clear request. +4. **PreRenderer**. Render and return PreValidation errors response. +5. **Controller**. Main logic: do some things. +6. **PostValidator**. Validate and clear response. +7. **PostRenderer**. Render and return PostValidation errors response. +8. **Renderer**. Render successful response. ![Scheme](imgs/scheme.png) -Required only Controller and Renderer. +Required only controller and renderer. ## Related conceptions @@ -43,8 +43,8 @@ Required only Controller and Renderer. ## Solved problems 1. [Mixins chaos and wrong usage](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html). Mixins is good but dangerous conception. Use it carefully. -1. You can decorate Django view in so many places: urls, view, dispatch method, get/post method, form_valid mrthod etc. DjhBurger have [special place for all decorators](usage.html#decorators) -1. Validation and main logic mixing. In bad code you can validate and apply some data in one loop, and if you found and return validation error operation will be applied partially. Example: +1. You can decorate Django view in so many places: urls, view, dispatch method, get/post method, form_valid method etc. DjBurger has [special place for all decorators](usage.html#decorators). +1. Validation and main logic mixing. In bad code you can validate and apply some data in one loop, and if you found and return validation error then operation will be applied partially. Example: ```python for user in users: if user.is_anonymous(): diff --git a/wiki/source/usage.md b/wiki/source/usage.md index ee31cb5..f60d036 100644 --- a/wiki/source/usage.md +++ b/wiki/source/usage.md @@ -2,29 +2,27 @@ ## Components structure -Use short notation from [dataflow](philosophy.html#dataflow). - DjBurger modules: -+ `djburger.p`. Parsers. Can be used as `p`. -+ `djburger.v`. Validators. Can be used as `prev` and `postv`. - + `djburger.v.b`. Validators that can be used as **base** class for your own validators. - + `djburger.v.c`. Validators that can be used as **constructors** for simple validators. - + `djburger.v.w`. Validators that can be used as **wrappers** for external validators. -+ `djburger.c`. Controllers. Can be used as `c`. -+ `djburger.r`. Renderers. Can be used as `prer`, `postr` and `r`. -+ `djburger.e`. Exceptions. ++ `djburger.parsers`. Parsers. Can be used as `parser`. ++ `djburger.validators`. Can be used as `prevalidator` and `postvalidator`. + + `djburger.validators.bases`. Validators that can be used as base class for your own validators. + + `djburger.validators.constructors`. Validators that can be used as constructors for simple validators. + + `djburger.validators.wrappers`. Validators that can be used as wrappers for external validators. ++ `djburger.controllers`. Can be used as `controller`. ++ `djburger.renderers`. Can be used as `prerenderer`, `postrenderer` and `renderer`. ++ `djburger.exceptions`. Keyword arguments for `djburger.rule`: -1. `d` -- decorators list. Optional. -1. `p` -- parser. `djburger.p.Default` by default. -1. `prev` -- pre-validator. Optional. -1. `prer` -- renderer for pre-validator. If missed then `r` will be used for pre-validation errors rendering. -1. `c` -- controller. Required. -1. `postv` -- post-validator. Optional. -1. `postr` -- renderer for post-validator. If missed then `r` will be used for post-validation errors rendering. -1. `r` -- renderer for success response. Required. +1. `decorators` -- decorators list. Optional. +1. `parser` -- parser. `djburger.parsers.Default` by default. +1. `prevalidator` -- pre-validator. Optional. +1. `prerenderer` -- renderer for pre-validator. If missed then `r` will be used for pre-validation errors rendering. +1. `controller` -- controller. Required. +1. `postvalidator` -- post-validator. Optional. +1. `postrenderer` -- renderer for post-validator. If missed then `r` will be used for post-validation errors rendering. +1. `renderer` -- renderer for success response. Required. ## View @@ -41,10 +39,10 @@ import djburger class ExampleView(djburger.ViewBase): rules = { 'get': djburger.rule( - c=lambda request, data, **kwargs: 'Hello, World!', - postv=djburger.v.c.IsStr, - postr=djburger.r.Exception(), - r=djburger.r.Template(template_name='index.html'), + controller=lambda request, data, **kwargs: 'Hello, World!', + postvalidator=djburger.validators.constructors.IsStr, + postrenderer=djburger.renderers.Exception(), + renderer=djburger.renderers.Template(template_name='index.html'), ), } ``` @@ -62,16 +60,16 @@ More info: You can use any Django decorators like `csrf_exempt`. `djburger.ViewBase` wraps into decorators view's `validate_request` method that get `request` object, `**kwargs` from URL resolver and returns renderer's result (usually Django HttpResponse). ```python -d=[csrf_exempt] +decorators=[csrf_exempt] ``` ## Parsers -Parser get `request` and return `data` which will be passed as is into pre-validator. Usually `data` has `dict` or [QueryDict](https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.QueryDict) interface. DjBurger use `djburger.p.Default` as default parser. See list of built-in parsers into [parsers API](parsers.html). +Parser get `request` and return `data` which will be passed as is into pre-validator. Usually `data` has `dict` or [QueryDict](https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.QueryDict) interface. DjBurger use `djburger.parsers.Default` as default parser. See list of built-in parsers into [parsers API](parsers.html). ```python -p=djburger.p.JSON() +parser=djburger.parsers.JSON() ``` @@ -84,11 +82,11 @@ Validators get data and validate it. They have Django Forms-like interface. See ```python from django import forms -class Validator(djburger.v.b.Form): +class Validator(djburger.validators.bases.Form): name = forms.CharField(max_length=20) ... -prev=Validator +prevalidator=Validator ... ``` @@ -101,7 +99,7 @@ class DjangoValidator(forms.Form): name = forms.CharField(max_length=20) ... -prev=djburger.v.w.Form(DjangoValidator) +prevalidator=djburger.validators.wrappers.Form(DjangoValidator) ... ``` @@ -109,15 +107,15 @@ And [constructors](validators.html#module-djburger.validators.constructors) for ```python -prev=djburger.v.c.IsDict +prevalidator=djburger.validators.constructors.IsDict ``` How to choose validator type: -1. `djburger.v.c` -- for one-line simple validation. -1. `djburger.v.w` -- for using validators which also used into non-DjBurger components. -1. `djburger.v.b` -- for any other cases. +1. `djburger.validators.constructors` -- for one-line simple validation. +1. `djburger.validators.wrappers` -- for using validators which also used into non-DjBurger components. +1. `djburger.validators.bases` -- for any other cases. ## Controllers @@ -129,14 +127,14 @@ def echo_controller(request, data, **kwargs): return data ... -c=echo_controller +controller=echo_controller ... ``` Additionally DjBurger have [built-in controllers](controllers.html) for simple cases. ```python -c=djburger.c.Info(model=User) +controller=djburger.controllers.Info(model=User) ``` @@ -145,52 +143,52 @@ c=djburger.c.Info(model=User) Renderer get errors or cleaned data from validator and return [HttpResponse](https://docs.djangoproject.com/en/2.0/ref/request-response/#httpresponse-objects) or any other view result. ```python -postr=djburger.r.JSON() +postrenderer=djburger.renderers.JSON() ``` ## Exceptions -Raise `djburger.e.StatusCodeError` from validator if you want stop validation and return `errors`. +Raise `djburger.exceptions.StatusCodeError` from validator if you want stop validation and return `errors`. ```python from django import forms -class Validator(djburger.v.b.Form): +class Validator(djburger.validators.bases.Form): name = forms.CharField(max_length=20) def clean_name(self): name = self.cleaned_data['name'] if name == 'admin': self.errors = {'__all__': ['User not found']} - raise djburger.e.StatusCodeError(404, 'User not found') + raise djburger.exceptions.StatusCodeError(404) return name ... -prev=Validator +prevalidator=Validator ... ``` ## SubControllers -If you need to validate data in controller, better use `djburger.c.subcontroller`: +If you need to validate data in controller, better use `djburger.controllers.subcontroller`: ```python def get_name_controller(request, data, **kwargs): return data['name'] def echo_controller(request, data, **kwargs): - subc = djburger.c.subcontroller( - prev=djburger.c.IsDict, - c=get_name_controller, - postv=djburger.c.IsStr, + subc = djburger.controllers.subcontroller( + prevalidator=djburger.controllers.IsDict, + controller=get_name_controller, + postvalidator=djburger.controllers.IsStr, ) return subc(request, data, **kwargs) ... -c=echo_controller +controller=echo_controller ... ``` -If data passed to subcontroller is invalid then `djburger.e.SubValidationError` will be immediately raised. View catch error and pass error to `postr`. +If data passed to subcontroller is invalid then `djburger.exceptions.SubValidationError` will be immediately raised. View catch error and pass error to `postrenderer`.