diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a834033 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,76 @@ +repos: + - repo: https://github.com/commitizen-tools/commitizen + rev: 3.2.1 + hooks: + - id: commitizen + stages: + - commit-msg + + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + args: + - --profile=black + - --line-length=100 + + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + args: # arguments to configure black + - --line-length=100 + + # these folders won't be formatted by black + - --exclude="""\.git | + \.__pycache__| + \.hg| + \.mypy_cache| + \.tox| + \.venv| + _build| + buck-out| + build| + dist""" + + # flake8 + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + args: # arguments to configure flake8 + # making isort line length compatible with black + - "--max-line-length=100" + - "--max-complexity=18" + - "--select=B,C,E,F,W,T4,B9" + - "--exclude=.git,__pycache__,.mypy_cache,.tox,.venv,_build,buck-out,build,dist" + + # these are errors that will be ignored by flake8 + # check out their meaning here + # https://flake8.pycqa.org/en/latest/user/error-codes.html + - "--ignore=E203,E266,E501,W503,F405,F403,F401,E402,W605" + + # Others pre-commit hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/PyCQA/autoflake + rev: v2.1.1 + hooks: + - id: autoflake + args: + - --remove-unused-variables + - --remove-all-unused-imports + + - repo: https://github.com/python-poetry/poetry + rev: 1.4.0 + hooks: + - id: poetry-check + - id: poetry-lock + language_version: python3.10 + args: + - --check diff --git a/LICENSE b/LICENSE index d92dba7..1d7fc50 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in index e79e4f6..5e07ee5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include README.md include README.rst -include LICENSE \ No newline at end of file +include LICENSE diff --git a/README.md b/README.md index 9198e6b..91ca1ef 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ - ---- - # Graphene-Django-Extras + ![Travis (.org) branch](https://img.shields.io/travis/eamigo86/graphene-django-extras/master) ![Codecov](https://img.shields.io/codecov/c/github/eamigo86/graphene-django-extras) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/graphene-django-extras) @@ -11,7 +9,8 @@ ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg) This package adds some extra functionalities to graphene-django to facilitate the graphql use without Relay: - 1. Allow pagination and filtering on Queries. + + 1. Allow pagination and filtering on Queries 2. Allow defining DjangoRestFramework serializers based on Mutations. 3. Allow using Directives on Queries and Fragments. @@ -21,33 +20,37 @@ This package adds some extra functionalities to graphene-django to facilitate th For installing graphene-django-extras, just run this command in your shell: -``` +```bash pip install graphene-django-extras ``` -## Documentation: +## Documentation + +### Extra functionalities -### Extra functionalities: **Fields:** - 1. DjangoObjectField - 2. DjangoFilterListField - 3. DjangoFilterPaginateListField - 4. DjangoListObjectField (*Recommended for Queries definition*) + + 1. DjangoObjectField + 2. DjangoFilterListField + 3. DjangoFilterPaginateListField + 4. DjangoListObjectField (*Recommended for Queries definition*) **Mutations:** - 1. DjangoSerializerMutation (*Recommended for Mutations definition*) + + 1. DjangoSerializerMutation (*Recommended for Mutations definition*) **Types:** - 1. DjangoListObjectType (*Recommended for Types definition*) - 2. DjangoInputObjectType - 3. DjangoSerializerType (*Recommended for quick queries and mutations definitions*) + + 1. DjangoListObjectType (*Recommended for Types definition*) + 2. DjangoInputObjectType + 3. DjangoSerializerType (*Recommended for quick queries and mutations definitions*) **Paginations:** - 1. LimitOffsetGraphqlPagination - 2. PageGraphqlPagination + 1. LimitOffsetGraphqlPagination + 2. PageGraphqlPagination -### Queries and Mutations examples: +### Queries and Mutations examples This is a basic example of graphene-django-extras package use. You can configure global params for DjangoListObjectType classes pagination definitions on settings.py like this: @@ -62,7 +65,7 @@ for DjangoListObjectType classes pagination definitions on settings.py like this } ``` -#### 1- Types Definition: +#### 1- Types Definition ```python from django.contrib.auth.models import User @@ -110,7 +113,7 @@ class UserModelType(DjangoSerializerType): } ``` -#### 2- You can to define InputTypes for use on mutations: +#### 2- You can to define InputTypes for use on mutations ```python from graphene_django_extras import DjangoInputObjectType @@ -122,7 +125,7 @@ class UserInput(DjangoInputObjectType): model = User ``` -#### 3- You can define traditional mutations that use InputTypes or Mutations based on DRF serializers: +#### 3- You can define traditional mutations that use InputTypes or Mutations based on DRF serializers ```python import graphene @@ -161,7 +164,7 @@ class UserMutation(graphene.Mutation): ... ``` -#### 4- Defining the Schema file: +#### 4- Defining the Schema file ```python import graphene @@ -217,8 +220,10 @@ class Mutations(graphene.ObjectType): traditional_user_mutation = UserMutation.Field() ``` -#### 5- Directives settings: +#### 5- Directives settings + For use Directives you must follow two simple steps: + 1. You must add **'graphene_django_extras.ExtraGraphQLDirectiveMiddleware'** to your GRAPHENE dict config on your settings.py: @@ -234,8 +239,8 @@ GRAPHENE = { ``` 2. You must add the *directives* param with your custom directives to your schema definition. This module comes with -some common directives for you, these directives allow to you format strings, numbers, lists, and dates (optional), and -you can load like this: + some common directives for you, these directives allow to you format strings, numbers, lists, and dates (optional), + and you can load like this: ```python # schema.py @@ -247,35 +252,43 @@ schema = graphene.Schema( directives=all_directives ) ``` + **NOTE**: Date directive depends on *dateutil* module, so if you do not have installed it, this directive will not be available. You can install *dateutil* module manually: -``` + +```bash pip install python-dateutil ``` + or like this: -``` + +```bash pip install graphene-django-extras[date] ``` + That's all !!! +#### 6- Complete Directive list -#### 6- Complete Directive list: +##### FOR NUMBERS -##### FOR NUMBERS: 1. **FloorGraphQLDirective**: Floors value. Supports both String and Float fields. 2. **CeilGraphQLDirective**: Ceils value. Supports both String and Float fields. -##### FOR LIST: +##### FOR LIST + 1. **ShuffleGraphQLDirective**: Shuffle the list in place. 2. **SampleGraphQLDirective**: Take a 'k' int argument and return a k length list of unique elements chosen from the taken list -##### FOR DATE: +##### FOR DATE + 1. **DateGraphQLDirective**: Take a optional 'format' string argument and format the date from resolving the field by dateutil module with the 'format' format. Default format is: 'DD MMM YYYY HH:mm:SS' equivalent to '%d %b %Y %H:%M:%S' python format. -##### FOR STRING: +##### FOR STRING + 1. **DefaultGraphQLDirective**: Take a 'to' string argument. Default to given value if None or "" 2. **Base64GraphQLDirective**: Take a optional ("encode" or "decode") 'op' string argument(default='encode'). Encode or decode the string taken. @@ -304,8 +317,8 @@ length. Return the taken string with all occurrences of substring 'old' argument replaced by 'new' argument value. If the optional argument 'count' is given, only the first 'count' occurrences are replaced. +#### 7- Queries's examples -#### 7- Queries's examples: ```js { allUsers(username_Icontains:"john"){ @@ -346,7 +359,7 @@ If the optional argument 'count' is given, only the first 'count' occurrences ar } ``` -#### 8- Mutations's examples: +#### 8- Mutations's examples ```js mutation{ @@ -386,8 +399,10 @@ mutation{ } ``` -#### 9- Directives's examples: +#### 9- Directives's examples + Let's suppose that we have this query: + ```js query{ allUsers{ @@ -401,7 +416,9 @@ query{ } } ``` + And return this data: + ```js { "data": { @@ -433,8 +450,10 @@ And return this data: } } ``` + As we see, some data it's missing or just not have the format that we like it, so let's go to format the output data -that we desired: + that we desired: + ```js query{ allUsers{ @@ -448,7 +467,9 @@ query{ } } ``` + And we get this output data: + ```js { "data": { @@ -480,6 +501,7 @@ And we get this output data: } } ``` + As we see, the directives are an easy way to format output data on queries, and it's can be put together like a chain. **List of possible date's tokens**: @@ -491,199 +513,241 @@ You can use this shortcuts too: 2. "iso": "YYYY-MMM-DDTHH:mm:ss" 3. "js" or "javascript": "ddd MMM DD YYYY HH:mm:ss" +## Change Log + +### v1.0.0 + +1. Added support to Django 4.x +2. Removed support for Django versions < 3.2 + +### v0.5.1 + +1. Update dependencies + +### v0.5.0 + +1. Upgrade to graphene v3 + +### v0.4.8 + +1. Upgrade graphene-django dependency to version == 2.6.0. + +### v0.4.6 + +1. Upgrade graphql-core dependency to version >= 2.2.1. +2. Upgrade graphene dependency to version >= 2.1.8. +3. Upgrade graphene-django dependency to version >= 2.5.0. +4. Upgrade django-filter dependency to version >= 2.2.0. +5. Fixed bug 'DjangoSerializerOptions' object has no attribute 'interfaces' after update to graphene==2.1.8. +6. The tests were refactored and added some extra tests for DjangoSerializerType. + +### v0.4.5 + +1. Fixed compatibilities issues to use graphene-django>=2.3.2. +2. Improved code quality and use Black code format. +3. Fixed minor bug with "time ago" date directive. + +### v0.3.7 + +1. Improved DjangoListType and DjangoObjectType to share the filterset_class between the two class. + +### v0.3.6 + +1. Improve DjangoSerializerMutation resolvers. + +### v0.3.5 + +1. Fixed minor bug on ExtraGraphQLDirectiveMiddleware. +2. Fixed error with DRF 3.8 Compatibility. +3. Updated List's Fields to pass info.context to filterset as request, this allow filtering by request data. +4. Added new feature to ordering paginated queries. + +### v0.3.4-alpha2 + +1. Fixed minor bug on DjangoListObjectType. + +### v0.3.4-alpha1 + +1. Added filterset_class to the listing types as default filter. +2. Changed getOne by RetrieveField on DjangoListObjectType. + +### v0.3.3 + +1. Added filterset_class to DjangoObjectType. +2. Fixed minor bug on factory_types function. + +### v0.3.3-alpha1 -## Change Log: +1. Fixed minor bug on *queryset_factory* function. -#### v1.0.0: - 1. Added support to Django 4.x - 2. Removed support for Django versions < 3.2 +### v0.3.2 -#### v0.5.1: - 1. Update dependencies +1. Updated Date directive format function for better string format combinations. +2. Updated custom Time, Date and DateTime base types to be used with Date directive. +3. Fixed bug with caching Introspection queries on ExtraGraphQLView. -#### v0.5.0: - 1. Upgrade to graphene v3 +### v0.3.1 -#### v0.4.8: - 1. Upgrade graphene-django dependency to version == 2.6.0. +1. Fixed bug with default Date directive format. -#### v0.4.6: - 1. Upgrade graphql-core dependency to version >= 2.2.1. - 2. Upgrade graphene dependency to version >= 2.1.8. - 3. Upgrade graphene-django dependency to version >= 2.5.0. - 4. Upgrade django-filter dependency to version >= 2.2.0. - 5. Fixed bug 'DjangoSerializerOptions' object has no attribute 'interfaces' after update to graphene==2.1.8. - 6. The tests were refactored and added some extra tests for DjangoSerializerType. +### v0.3.0 -#### v0.4.5: - 1. Fixed compatibilities issues to use graphene-django>=2.3.2. - 2. Improved code quality and use Black code format. - 3. Fixed minor bug with "time ago" date directive. +1. Added Binary graphql type. A BinaryArray is used to convert a Django BinaryField to the string form. +2. Added 'CACHE_ACTIVE' and 'CACHE_TIMEOUT' config options to GRAPHENE_DJANGO_EXTRAS settings for activate cache and + define a expire time. Default values are: CACHE_ACTIVE=False, CACHE_TIMEOUT=300 (seconds). Only available for Queries. +3. Updated Date directive for use with Django TimeField, DateField, and DateTimeField. +4. Updated ExtraGraphQLView and AuthenticatedGraphQLView to allow use subscription requests on graphene-django >=2.0 +5. Updated setup dependence to graphene-django>=2.0. -#### v0.3.7: - 1. Improved DjangoListType and DjangoObjectType to share the filterset_class between the two class. +### v0.2.2 -#### v0.3.6: - 1. Improve DjangoSerializerMutation resolvers. - -#### v0.3.5: - 1. Fixed minor bug on ExtraGraphQLDirectiveMiddleware. - 2. Fixed error with DRF 3.8 Compatibility. - 3. Updated List's Fields to pass info.context to filterset as request, this allow filtering by request data. - 4. Added new feature to ordering paginated queries. - -#### v0.3.4-alpha2: - 1. Fixed minor bug on DjangoListObjectType. - -#### v0.3.4-alpha1: - 1. Added filterset_class to the listing types as default filter. - 2. Changed getOne by RetrieveField on DjangoListObjectType. - -#### v0.3.3: - 1. Added filterset_class to DjangoObjectType. - 2. Fixed minor bug on factory_types function. - -#### v0.3.3-alpha1: - 1. Fixed minor bug on *queryset_factory* function. - -#### v0.3.2: - 1. Updated Date directive format function for better string format combinations. - 2. Updated custom Time, Date and DateTime base types to be used with Date directive. - 3. Fixed bug with caching Introspection queries on ExtraGraphQLView. - -#### v0.3.1: - 1. Fixed bug with default Date directive format. - -#### v0.3.0: - 1. Added Binary graphql type. A BinaryArray is used to convert a Django BinaryField to the string form. - 2. Added 'CACHE_ACTIVE' and 'CACHE_TIMEOUT' config options to GRAPHENE_DJANGO_EXTRAS settings for activate cache and - define a expire time. Default values are: CACHE_ACTIVE=False, CACHE_TIMEOUT=300 (seconds). Only available for - Queries. - 3. Updated Date directive for use with Django TimeField, DateField, and DateTimeField. - 4. Updated ExtraGraphQLView and AuthenticatedGraphQLView to allow use subscription requests on graphene-django >=2.0 - 5. Updated setup dependence to graphene-django>=2.0. - -#### v0.2.2: - 1. Fixed performance bug on some queries when request nested ManyToMany fields. - -#### v0.2.1: - 1. Fixed bug with default PaginationClass and DjangoFilterPaginateListField. - -#### v0.2.0: - 1. Added some useful directives to use on queries and fragments. - 2. Fixed error on DjangoFilterPaginateListField resolve function. - -#### v0.1.6: - 1. Fixed bug on create and update function on serializer mutation. - -#### v0.1.3: - 1. Fixed minors bugs. - -#### v0.1.2: - 1. Added ok field and errors field to DjangoSerializerType like on DjangoSerializerMutation. - 2. Added possibility of filtering in those queries fields that return a list of objects. - 3. Updated DRF compatibility. - 4. Fixed bug with filters when use global DEFAULT_PAGINATION_CLASS. - -#### v0.1.1: - 1. Fixed error with JSONField reference on Django==1.8.x installations. - -#### v0.1.0: - 1. Added DjangoSerializerType for quick Django's models types definition (See documentation). - 2. Moved support for Subscriptions to graphene-django-subscriptions packages for - incompatibility with graphene-django>=2.0. - 3. Fixed bug on DjangoFilterPaginateListField's pagination. - -#### v0.1.0-alpha12: - 1. Added new settings param: MAX_PAGE_SIZE, to use on GRAPHENE_DJANGO_EXTRAS - configuration dict for better customize DjangoListObjectType's pagination. - 2. Added support to Django's field: GenericRel. - 3. Improve model's fields calculation for to add all possible related and reverse fields. - 4. Improved documentation translation. - -#### v0.1.0-alpha11: - 1. Improved ordering for showed fields on graphqli's IDE. - 2. Added better descriptions for auto generated fields. - -#### v0.1.0-alpha10: - 1. Improve converter.py file to avoid create field for auto generate OneToOneField - product of an inheritance. - 2. Fixed bug in Emun generation for fields with choices of model inheritance child. - -#### v0.1.0-alpha9: - 1. Fixed bug on GenericType and GenericInputType generations for - Queries list Type and Mutations. - -#### v0.1.0-alpha6: - 1. Fixed with exclude fields and converter function. - -#### v0.1.0-alpha5: - 1. Updated dependencies to graphene-django>=2.0. - 2. Fixed minor bugs on queryset_builder performance. - -#### v0.1.0-alpha4: - 1. Add queryset options to DjangoListObjectType Meta class for specify wanted model queryset. - 2. Add AuthenticatedGraphQLView on graphene_django_extras.views for use - 'permission', 'authorization' and 'throttle' classes based on the DRF settings. Special thanks to - [@jacobh](https://github.com/jacobh) for this - [comment](https://github.com/graphql-python/graphene/issues/249#issuecomment-300068390) - -#### v0.1.0-alpha3: - 1. Fixed bug on subscriptions when not specified any field in "data" parameter to bean return on notification +1. Fixed performance bug on some queries when request nested ManyToMany fields. + +### v0.2.1 + +1. Fixed bug with default PaginationClass and DjangoFilterPaginateListField. + +### v0.2.0 + +1. Added some useful directives to use on queries and fragments. +2. Fixed error on DjangoFilterPaginateListField resolve function. + +### v0.1.6 + +1. Fixed bug on create and update function on serializer mutation. + +### v0.1.3 + +1. Fixed minors bugs. + +### v0.1.2 + +1. Added ok field and errors field to DjangoSerializerType like on DjangoSerializerMutation. +2. Added possibility of filtering in those queries fields that return a list of objects. +3. Updated DRF compatibility. +4. Fixed bug with filters when use global DEFAULT_PAGINATION_CLASS. + +### v0.1.1 + +1. Fixed error with JSONField reference on Django==1.8.x installations. + +### v0.1.0 + +1. Added DjangoSerializerType for quick Django's models types definition (See documentation). +2. Moved support for Subscriptions to graphene-django-subscriptions packages for + incompatibility with graphene-django>=2.0. +3. Fixed bug on DjangoFilterPaginateListField's pagination. + +### v0.1.0-alpha12 + +1. Added new settings param: MAX_PAGE_SIZE, to use on GRAPHENE_DJANGO_EXTRAS + configuration dict for better customize DjangoListObjectType's pagination. +2. Added support to Django's field: GenericRel. +3. Improve model's fields calculation for to add all possible related and reverse fields. +4. Improved documentation translation. + +### v0.1.0-alpha11 + +1. Improved ordering for showed fields on graphqli's IDE. +2. Added better descriptions for auto generated fields. + +### v0.1.0-alpha10 + +1. Improve converter.py file to avoid create field for auto generate OneToOneField + product of an inheritance. +2. Fixed bug in Emun generation for fields with choices of model inheritance child. + +### v0.1.0-alpha9 + +1. Fixed bug on GenericType and GenericInputType generations for Queries list Type and Mutations. + +### v0.1.0-alpha6 + +1. Fixed with exclude fields and converter function. + +### v0.1.0-alpha5 + +1. Updated dependencies to graphene-django>=2.0. +2. Fixed minor bugs on queryset_builder performance. + +### v0.1.0-alpha4 + +1. Add queryset options to DjangoListObjectType Meta class for specify wanted model queryset. +2. Add AuthenticatedGraphQLView on graphene_django_extras.views for use + 'permission', 'authorization' and 'throttle' classes based on the DRF settings. Special thanks to +[@jacobh](https://github.com/jacobh) for this +[comment](https://github.com/graphql-python/graphene/issues/249#issuecomment-300068390) + +### v0.1.0-alpha3 + +1. Fixed bug on subscriptions when not specified any field in "data" parameter to bean return on notification message. -#### v0.1.0-alpha2: - 1. Fixed bug when subscribing to a given action (create, update or delete). - 2. Added intuitive and simple web tool to test notifications of graphene-django-extras subscription. - -#### v0.1.0-alpha1: - 1. Added support to multiselect choices values for models.CharField with choices attribute, - on queries and mutations. Example: Integration with django-multiselectfield package. - 2. Added support to GenericForeignKey and GenericRelation fields, on queries and mutations. - 3. Added first approach to support Subscriptions with Channels, with subscribe and unsubscribe operations. - Using channels-api package. - 4. Fixed minors bugs. - -#### v0.0.4: - 1. Fix error on DateType encode. - -#### v0.0.3: - 1. Implement custom implementation of DateType for use converter and avoid error on Serializer Mutation. - -#### v0.0.2: - 1. Changed dependency of DRF to 3.6.4 on setup.py file, to avoid an import error produced by some changes in - new version of DRF=3.7.0 and because DRF 3.7.0 dropped support to Django versions < 1.10. - -#### v0.0.1: - 1. Fixed bug on DjangoInputObjectType class that refer to unused interface attribute. - 2. Added support to create nested objects like in - [DRF](http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations), - it's valid to SerializerMutation and DjangoInputObjectType, only is necessary to specify nested_fields=True - on its Meta class definition. - 3. Added support to show, only in mutations types to create objects and with debug=True on settings, - inputs autocomplete ordered by required fields first. - 4. Fixed others minors bugs. - -#### v0.0.1-rc.2: - 1. Make queries pagination configuration is more friendly. - -#### v0.0.1-rc.1: - 1. Fixed a bug with input fields in the converter function. - -#### v0.0.1-beta.10: - 1. Fixed bug in the queryset_factory function because it did not always return a queryset. - -#### v0.0.1-beta.9: - 1. Remove hard dependence with psycopg2 module. - 2. Fixed bug that prevented use queries with fragments. - 3. Fixed bug relating to custom django_filters module and ordering fields. - -#### v0.0.1-beta.6: - 1. Optimizing imports, fix some minors bugs and working on performance. - -#### v0.0.1-beta.5: - 1. Repair conflict on converter.py, by the use of get_related_model function with: OneToOneRel, - ManyToManyRel and ManyToOneRel. - -#### v0.0.1-beta.4: - 1. First commit. +### v0.1.0-alpha2 + +1. Fixed bug when subscribing to a given action (create, update or delete). +2. Added intuitive and simple web tool to test notifications of graphene-django-extras subscription. + +#### v0.1.0-alpha1 + +1. Added support to multiselect choices values for models.CharField with choices attribute, + on queries and mutations. Example: Integration with django-multiselectfield package. +2. Added support to GenericForeignKey and GenericRelation fields, on queries and mutations. +3. Added first approach to support Subscriptions with Channels, with subscribe and unsubscribe + operations using channels-api package. +4. Fixed minors bugs. + +#### v0.0.4 + +1. Fix error on DateType encode. + +### v0.0.3 + +1. Implement custom implementation of DateType for use converter and avoid error on Serializer Mutation. + +### v0.0.2 + +1. Changed dependency of DRF to 3.6.4 on setup.py file, to avoid an import error produced by some changes in + new version of DRF=3.7.0 and because DRF 3.7.0 dropped support to Django versions < 1.10. + +### v0.0.1 + +1. Fixed bug on DjangoInputObjectType class that refer to unused interface attribute. +2. Added support to create nested objects like in + [DRF](http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations), + it's valid to SerializerMutation and DjangoInputObjectType, only is necessary to specify nested_fields=True + on its Meta class definition. +3. Added support to show, only in mutations types to create objects and with debug=True on settings, + inputs autocomplete ordered by required fields first. +4. Fixed others minors bugs. + +### v0.0.1-rc.2 + +1. Make queries pagination configuration is more friendly. + +### v0.0.1-rc.1 + +1. Fixed a bug with input fields in the converter function. + +### v0.0.1-beta.10 + +1. Fixed bug in the queryset_factory function because it did not always return a queryset. + +### v0.0.1-beta.9 + +1. Remove hard dependence with psycopg2 module. +2. Fixed bug that prevented use queries with fragments. +3. Fixed bug relating to custom django_filters module and ordering fields. + +### v0.0.1-beta.6 + +1. Optimizing imports, fix some minors bugs and working on performance. + +### v0.0.1-beta.5 + +1. Repair conflict on converter.py, by the use of get_related_model function with: OneToOneRel, + ManyToManyRel and ManyToOneRel. + +### v0.0.1-beta.4 + +1. First commit. diff --git a/README.rst b/README.rst index 76905b7..8a8c56f 100644 --- a/README.rst +++ b/README.rst @@ -804,4 +804,4 @@ v0.0.1-beta.4: :alt: Black .. |pypi-downloads| image:: https://img.shields.io/pypi/dm/graphene-django-extras?style=flat :target: https://pypi.python.org/pypi/graphene-django-extras - :alt: PyPI Downloads \ No newline at end of file + :alt: PyPI Downloads diff --git a/graphene_django_extras/__init__.py b/graphene_django_extras/__init__.py index 9ec708a..89e76ed 100644 --- a/graphene_django_extras/__init__.py +++ b/graphene_django_extras/__init__.py @@ -3,18 +3,18 @@ from .directives import all_directives from .fields import ( - DjangoObjectField, DjangoFilterListField, DjangoFilterPaginateListField, DjangoListObjectField, + DjangoObjectField, ) from .middleware import ExtraGraphQLDirectiveMiddleware from .mutation import DjangoSerializerMutation from .paginations import LimitOffsetGraphqlPagination, PageGraphqlPagination from .types import ( - DjangoObjectType, DjangoInputObjectType, DjangoListObjectType, + DjangoObjectType, DjangoSerializerType, ) diff --git a/graphene_django_extras/base_types.py b/graphene_django_extras/base_types.py index e78c47d..411360a 100644 --- a/graphene_django_extras/base_types.py +++ b/graphene_django_extras/base_types.py @@ -5,7 +5,7 @@ import datetime import graphene -from graphene.types.datetime import Date, Time, DateTime +from graphene.types.datetime import Date, DateTime, Time from graphene.utils.str_converters import to_camel_case from graphql.language import ast @@ -154,9 +154,9 @@ def serialize(time): if isinstance(time, datetime.datetime): time = time.time() - assert isinstance( - time, datetime.time - ), 'Received not compatible time "{}"'.format(repr(time)) + assert isinstance(time, datetime.time), 'Received not compatible time "{}"'.format( + repr(time) + ) return time.isoformat() @@ -168,9 +168,9 @@ def serialize(date): if isinstance(date, datetime.datetime): date = date.date() - assert isinstance( - date, datetime.date - ), 'Received not compatible date "{}"'.format(repr(date)) + assert isinstance(date, datetime.date), 'Received not compatible date "{}"'.format( + repr(date) + ) return date.isoformat() diff --git a/graphene_django_extras/converter.py b/graphene_django_extras/converter.py index 9e6c9cd..86cc519 100644 --- a/graphene_django_extras/converter.py +++ b/graphene_django_extras/converter.py @@ -1,44 +1,28 @@ # -*- coding: utf-8 -*- import re from collections import OrderedDict +from functools import singledispatch from django.conf import settings -from django.contrib.contenttypes.fields import ( - GenericForeignKey, - GenericRelation, - GenericRel, -) +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel, GenericRelation from django.db import models from django.utils.encoding import force_str -from graphene import ( - Field, - ID, - Boolean, - Dynamic, - Enum, - Float, - Int, - List, - NonNull, - String, - UUID, -) +from graphene import ID, UUID, Boolean, Dynamic, Enum, Field, Float, Int, List, NonNull, String from graphene.types.json import JSONString from graphene.utils.str_converters import to_camel_case -from graphene_django.compat import ArrayField, HStoreField, RangeField, JSONField -from functools import singledispatch +from graphene_django.compat import ArrayField, HStoreField, JSONField, RangeField from graphene_django.utils.str_converters import to_const from .base_types import ( - GenericForeignKeyType, - GenericForeignKeyInputType, + Binary, + CustomDate, CustomDateTime, CustomTime, - CustomDate, - Binary, + GenericForeignKeyInputType, + GenericForeignKeyType, ) from .fields import DjangoFilterListField, DjangoListField -from .utils import is_required, get_model_fields, get_related_model +from .utils import get_model_fields, get_related_model, is_required NAME_PATTERN = r"^[_a-zA-Z][_a-zA-Z0-9]*$" COMPILED_NAME_PATTERN = re.compile(NAME_PATTERN) @@ -46,9 +30,9 @@ def assert_valid_name(name): """Helper to assert that provided names are valid.""" - assert COMPILED_NAME_PATTERN.match( - name - ), 'Names must match /{}/ but "{}" does not.'.format(NAME_PATTERN, name) + assert COMPILED_NAME_PATTERN.match(name), 'Names must match /{}/ but "{}" does not.'.format( + NAME_PATTERN, name + ) def convert_choice_name(name): @@ -75,9 +59,7 @@ def get_choices(choices): yield name, value, description -def convert_django_field_with_choices( - field, registry=None, input_flag=None, nested_field=False -): +def convert_django_field_with_choices(field, registry=None, input_flag=None, nested_field=False): choices = getattr(field, "choices", None) if choices: meta = field.model._meta @@ -127,18 +109,14 @@ def construct_fields( if settings.DEBUG: if input_flag == "create": - _model_fields = sorted( - _model_fields, key=lambda f: (not is_required(f[1]), f[0]) - ) + _model_fields = sorted(_model_fields, key=lambda f: (not is_required(f[1]), f[0])) elif not input_flag: _model_fields = sorted(_model_fields, key=lambda f: f[0]) fields = OrderedDict() if input_flag == "delete": - converted = convert_django_field_with_choices( - dict(_model_fields)["id"], registry - ) + converted = convert_django_field_with_choices(dict(_model_fields)["id"], registry) fields["id"] = converted else: for name, field in _model_fields: @@ -148,9 +126,7 @@ def construct_fields( nested_field = name in nested_fields is_not_in_only = only_fields and name not in only_fields # is_already_created = name in options.fields - is_excluded = ( - exclude_fields and name in exclude_fields - ) # or is_already_created + is_excluded = exclude_fields and name in exclude_fields # or is_already_created # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name is_no_backref = str(name).endswith("+") # if is_not_in_only or is_excluded or is_no_backref: @@ -168,9 +144,7 @@ def construct_fields( ): continue - converted = convert_django_field_with_choices( - field, registry, input_flag, nested_field - ) + converted = convert_django_field_with_choices(field, registry, input_flag, nested_field) fields[name] = converted return fields @@ -178,9 +152,7 @@ def construct_fields( @singledispatch def convert_django_field(field, registry=None, input_flag=None, nested_field=False): raise Exception( - "Don't know how to convert the Django field {} ({})".format( - field, field.__class__ - ) + "Don't know how to convert the Django field {} ({})".format(field, field.__class__) ) @@ -240,9 +212,7 @@ def convert_field_to_boolean(field, registry=None, input_flag=None, nested_field @convert_django_field.register(models.NullBooleanField) -def convert_field_to_nullboolean( - field, registry=None, input_flag=None, nested_field=False -): +def convert_field_to_nullboolean(field, registry=None, input_flag=None, nested_field=False): return Boolean( description=field.help_text or field.verbose_name, required=is_required(field) and input_flag == "create", @@ -276,9 +246,7 @@ def convert_date_to_string(field, registry=None, input_flag=None, nested_field=F @convert_django_field.register(models.DateTimeField) -def convert_datetime_to_string( - field, registry=None, input_flag=None, nested_field=False -): +def convert_datetime_to_string(field, registry=None, input_flag=None, nested_field=False): return CustomDateTime( description=field.help_text or field.verbose_name, required=is_required(field) and input_flag == "create", @@ -311,9 +279,7 @@ def dynamic_type(): @convert_django_field.register(models.ManyToManyField) -def convert_field_to_list_or_connection( - field, registry=None, input_flag=None, nested_field=False -): +def convert_field_to_list_or_connection(field, registry=None, input_flag=None, nested_field=False): model = get_related_model(field) def dynamic_type(): @@ -349,9 +315,7 @@ def dynamic_type(): @convert_django_field.register(GenericRel) @convert_django_field.register(models.ManyToManyRel) @convert_django_field.register(models.ManyToOneRel) -def convert_many_rel_to_djangomodel( - field, registry=None, input_flag=None, nested_field=False -): +def convert_many_rel_to_djangomodel(field, registry=None, input_flag=None, nested_field=False): model = field.related_model def dynamic_type(): @@ -379,16 +343,12 @@ def dynamic_type(): @convert_django_field.register(models.OneToOneField) @convert_django_field.register(models.ForeignKey) -def convert_field_to_djangomodel( - field, registry=None, input_flag=None, nested_field=False -): +def convert_field_to_djangomodel(field, registry=None, input_flag=None, nested_field=False): model = get_related_model(field) def dynamic_type(): # Avoid create field for auto generate OneToOneField product of an inheritance - if isinstance(field, models.OneToOneField) and issubclass( - field.model, field.related_model - ): + if isinstance(field, models.OneToOneField) and issubclass(field.model, field.related_model): return if input_flag and not nested_field: return ID( @@ -473,9 +433,7 @@ def dynamic_type(): @convert_django_field.register(ArrayField) -def convert_postgres_array_to_list( - field, registry=None, input_flag=None, nested_field=False -): +def convert_postgres_array_to_list(field, registry=None, input_flag=None, nested_field=False): base_type = convert_django_field(field.base_field) if not isinstance(base_type, (List, NonNull)): base_type = type(base_type) @@ -488,9 +446,7 @@ def convert_postgres_array_to_list( @convert_django_field.register(HStoreField) @convert_django_field.register(JSONField) -def convert_postgres_field_to_string( - field, registry=None, input_flag=None, nested_field=False -): +def convert_postgres_field_to_string(field, registry=None, input_flag=None, nested_field=False): return JSONString( description=field.help_text or field.verbose_name, required=is_required(field) and input_flag == "create", @@ -498,9 +454,7 @@ def convert_postgres_field_to_string( @convert_django_field.register(RangeField) -def convert_postgres_range_to_string( - field, registry=None, input_flag=None, nested_field=False -): +def convert_postgres_range_to_string(field, registry=None, input_flag=None, nested_field=False): inner_type = convert_django_field(field.base_field) if not isinstance(inner_type, (List, NonNull)): inner_type = type(inner_type) diff --git a/graphene_django_extras/directives/__init__.py b/graphene_django_extras/directives/__init__.py index 7c38c01..ef760b3 100644 --- a/graphene_django_extras/directives/__init__.py +++ b/graphene_django_extras/directives/__init__.py @@ -2,24 +2,24 @@ from graphql.type.directives import specified_directives as default_directives from .date import DateGraphQLDirective -from .list import ShuffleGraphQLDirective, SampleGraphQLDirective -from .numbers import FloorGraphQLDirective, CeilGraphQLDirective +from .list import SampleGraphQLDirective, ShuffleGraphQLDirective +from .numbers import CeilGraphQLDirective, FloorGraphQLDirective from .string import ( - DefaultGraphQLDirective, Base64GraphQLDirective, - NumberGraphQLDirective, + CamelCaseGraphQLDirective, + CapitalizeGraphQLDirective, + CenterGraphQLDirective, CurrencyGraphQLDirective, + DefaultGraphQLDirective, + KebabCaseGraphQLDirective, LowercaseGraphQLDirective, - UppercaseGraphQLDirective, - CapitalizeGraphQLDirective, - CamelCaseGraphQLDirective, + NumberGraphQLDirective, + ReplaceGraphQLDirective, SnakeCaseGraphQLDirective, - KebabCaseGraphQLDirective, - SwapCaseGraphQLDirective, StripGraphQLDirective, + SwapCaseGraphQLDirective, TitleCaseGraphQLDirective, - CenterGraphQLDirective, - ReplaceGraphQLDirective, + UppercaseGraphQLDirective, ) all_directives = ( diff --git a/graphene_django_extras/directives/date.py b/graphene_django_extras/directives/date.py index 8eeacba..963e077 100644 --- a/graphene_django_extras/directives/date.py +++ b/graphene_django_extras/directives/date.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- import time as t -from datetime import date, datetime, timedelta, time +from datetime import date, datetime, time, timedelta import six from dateutil import parser, relativedelta from django.utils import timezone from graphql import GraphQLArgument, GraphQLString -from .base import BaseExtraGraphQLDirective from ..base_types import CustomDateFormat +from .base import BaseExtraGraphQLDirective __all__ = ("DateGraphQLDirective",) @@ -138,12 +138,9 @@ def _format_relativedelta(rdelta, full=False, two_days=False, original_dt=None): def _format_time_ago(dt, now=None, full=False, ago_in=False, two_days=False): - if not isinstance(dt, timedelta): if now is None: - now = timezone.localtime( - timezone=timezone.get_fixed_timezone(-int(t.timezone / 60)) - ) + now = timezone.localtime(timezone=timezone.get_fixed_timezone(-int(t.timezone / 60))) original_dt = dt dt = _parse(dt) @@ -204,9 +201,7 @@ def _format_dt(dt, format="default"): else: if temp_format != "": if temp_format in FORMATS_MAP: - translate_format_list.append( - FORMATS_MAP.get(temp_format, "") - ) + translate_format_list.append(FORMATS_MAP.get(temp_format, "")) else: return None if str_in_dict_keys(char, FORMATS_MAP): @@ -241,9 +236,7 @@ def get_args(): @staticmethod def resolve(value, directive, root, info, **kwargs): - format_argument = [ - arg for arg in directive.arguments if arg.name.value == "format" - ] + format_argument = [arg for arg in directive.arguments if arg.name.value == "format"] format_argument = format_argument[0] if len(format_argument) > 0 else None custom_format = format_argument.value.value if format_argument else "default" diff --git a/graphene_django_extras/directives/list.py b/graphene_django_extras/directives/list.py index 3d06a1f..7b995b6 100644 --- a/graphene_django_extras/directives/list.py +++ b/graphene_django_extras/directives/list.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import random -from graphql import GraphQLArgument, GraphQLNonNull, GraphQLInt +from graphql import GraphQLArgument, GraphQLInt, GraphQLNonNull from .base import BaseExtraGraphQLDirective @@ -24,11 +24,7 @@ def resolve(value, directive, root, info, **kwargs): class SampleGraphQLDirective(BaseExtraGraphQLDirective): @staticmethod def get_args(): - return { - "k": GraphQLArgument( - GraphQLNonNull(GraphQLInt), description="Value to default to" - ) - } + return {"k": GraphQLArgument(GraphQLNonNull(GraphQLInt), description="Value to default to")} @staticmethod def resolve(value, directive, root, info, **kwargs): diff --git a/graphene_django_extras/directives/string.py b/graphene_django_extras/directives/string.py index 1ba13e8..e78a124 100644 --- a/graphene_django_extras/directives/string.py +++ b/graphene_django_extras/directives/string.py @@ -2,11 +2,11 @@ import base64 import six -from graphene.utils.str_converters import to_snake_case, to_camel_case -from graphql import GraphQLArgument, GraphQLNonNull, GraphQLString, GraphQLInt +from graphene.utils.str_converters import to_camel_case, to_snake_case +from graphql import GraphQLArgument, GraphQLInt, GraphQLNonNull, GraphQLString -from .base import BaseExtraGraphQLDirective from ..utils import to_kebab_case +from .base import BaseExtraGraphQLDirective __all__ = ( "DefaultGraphQLDirective", @@ -35,17 +35,13 @@ class DefaultGraphQLDirective(BaseExtraGraphQLDirective): @staticmethod def get_args(): return { - "to": GraphQLArgument( - GraphQLNonNull(GraphQLString), description="Value to default to" - ) + "to": GraphQLArgument(GraphQLNonNull(GraphQLString), description="Value to default to") } @staticmethod def resolve(value, directive, root, info, **kwargs): if not value: - to_argument = [ - arg for arg in directive.arguments if arg.name.value == "to" - ][0] + to_argument = [arg for arg in directive.arguments if arg.name.value == "to"][0] return to_argument.value.value return value @@ -86,9 +82,7 @@ class NumberGraphQLDirective(BaseExtraGraphQLDirective): @staticmethod def get_args(): return { - "as": GraphQLArgument( - GraphQLNonNull(GraphQLString), description="Value to default to" - ) + "as": GraphQLArgument(GraphQLNonNull(GraphQLString), description="Value to default to") } @staticmethod @@ -101,9 +95,7 @@ class CurrencyGraphQLDirective(BaseExtraGraphQLDirective): @staticmethod def get_args(): return { - "symbol": GraphQLArgument( - GraphQLString, description="Currency symbol (default: $)" - ) + "symbol": GraphQLArgument(GraphQLString, description="Currency symbol (default: $)") } @staticmethod @@ -212,12 +204,8 @@ def get_args(): @staticmethod def resolve(value, directive, root, info, **kwargs): - chars_argument = [ - arg for arg in directive.arguments if arg.name.value == "chars" - ] - chars_argument = ( - chars_argument[0].value.value if len(chars_argument) > 0 else " " - ) + chars_argument = [arg for arg in directive.arguments if arg.name.value == "chars"] + chars_argument = chars_argument[0].value.value if len(chars_argument) > 0 else " " value = value if isinstance(value, six.string_types) else str(value) return value.strip(chars_argument) @@ -254,19 +242,11 @@ def get_args(): @staticmethod def resolve(value, directive, root, info, **kwargs): - width_argument = [ - arg for arg in directive.arguments if arg.name.value == "width" - ] - width_argument = ( - width_argument[0].value.value if len(width_argument) > 0 else len(value) - ) + width_argument = [arg for arg in directive.arguments if arg.name.value == "width"] + width_argument = width_argument[0].value.value if len(width_argument) > 0 else len(value) - fillchar_argument = [ - arg for arg in directive.arguments if arg.name.value == "fillchar" - ] - fillchar_argument = ( - fillchar_argument[0].value.value if len(fillchar_argument) > 0 else " " - ) + fillchar_argument = [arg for arg in directive.arguments if arg.name.value == "fillchar"] + fillchar_argument = fillchar_argument[0].value.value if len(fillchar_argument) > 0 else " " value = value if isinstance(value, six.string_types) else str(value) return value.center(int(width_argument), fillchar_argument) @@ -289,9 +269,7 @@ def get_args(): GraphQLNonNull(GraphQLString), description="Value of new character to replace", ), - "count": GraphQLArgument( - GraphQLInt, description="Value to returned str lenght" - ), + "count": GraphQLArgument(GraphQLInt, description="Value to returned str lenght"), } @staticmethod @@ -302,12 +280,8 @@ def resolve(value, directive, root, info, **kwargs): new_argument = [arg for arg in directive.arguments if arg.name.value == "new"] new_argument = new_argument[0].value.value if len(new_argument) > 0 else None - count_argument = [ - arg for arg in directive.arguments if arg.name.value == "count" - ] - count_argument = ( - count_argument[0].value.value if len(count_argument) > 0 else -1 - ) + count_argument = [arg for arg in directive.arguments if arg.name.value == "count"] + count_argument = count_argument[0].value.value if len(count_argument) > 0 else -1 value = value if isinstance(value, six.string_types) else str(value) return value.replace(old_argument, new_argument, int(count_argument)) diff --git a/graphene_django_extras/fields.py b/graphene_django_extras/fields.py index 3e3ce30..f5eed8f 100644 --- a/graphene_django_extras/fields.py +++ b/graphene_django_extras/fields.py @@ -2,21 +2,18 @@ import operator from functools import partial -from graphene import Field, List, ID, Argument -from graphene.types.structures import Structure, NonNull +from graphene import ID, Argument, Field, List +from graphene.types.structures import NonNull, Structure from graphene_django.fields import DjangoListField as DLF from graphene_django.filter.utils import get_filtering_args_from_filterset -from graphene_django.utils import ( - maybe_queryset, - is_valid_django_model, - DJANGO_FILTER_INSTALLED, -) +from graphene_django.utils import DJANGO_FILTER_INSTALLED, is_valid_django_model, maybe_queryset from graphene_django_extras.filters.filter import get_filterset_class from graphene_django_extras.settings import graphql_api_settings + from .base_types import DjangoListObjectBase from .paginations.pagination import BaseDjangoGraphqlPagination -from .utils import get_extra_filters, queryset_factory, get_related_fields, find_field +from .utils import find_field, get_extra_filters, get_related_fields, queryset_factory # *********************************************** # @@ -24,9 +21,7 @@ # *********************************************** # class DjangoObjectField(Field): def __init__(self, _type, *args, **kwargs): - kwargs["id"] = ID( - required=True, description="Django object unique identification field" - ) + kwargs["id"] = ID(required=True, description="Django object unique identification field") super(DjangoObjectField, self).__init__(_type, *args, **kwargs) @@ -68,7 +63,6 @@ def __init__( *args, **kwargs, ): - if DJANGO_FILTER_INSTALLED: _fields = _type._meta.filter_fields _model = _type._meta.model @@ -79,26 +73,16 @@ def __init__( meta.update(extra_filter_meta) filterset_class = filterset_class or _type._meta.filterset_class self.filterset_class = get_filterset_class(filterset_class, **meta) - self.filtering_args = get_filtering_args_from_filterset( - self.filterset_class, _type - ) + self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, _type) kwargs.setdefault("args", {}) kwargs["args"].update(self.filtering_args) if "id" not in kwargs["args"].keys(): self.filtering_args.update( - { - "id": Argument( - ID, description="Django object unique identification field" - ) - } + {"id": Argument(ID, description="Django object unique identification field")} ) kwargs["args"].update( - { - "id": Argument( - ID, description="Django object unique identification field" - ) - } + {"id": Argument(ID, description="Django object unique identification field")} ) if not kwargs.get("description", None): @@ -124,24 +108,18 @@ def list_resolver(manager, filterset_class, filtering_args, root, info, **kwargs try: if filter_kwargs: qs = operator.attrgetter( - "{}.filter".format( - getattr(field, "related_name", None) or field.name - ) + "{}.filter".format(getattr(field, "related_name", None) or field.name) )(root)(**filter_kwargs) else: qs = operator.attrgetter( - "{}.all".format( - getattr(field, "related_name", None) or field.name - ) + "{}.all".format(getattr(field, "related_name", None) or field.name) )(root)() except AttributeError: qs = None if qs is None: qs = queryset_factory(manager, root, info, **kwargs) - qs = filterset_class( - data=filter_kwargs, queryset=qs, request=info.context - ).qs + qs = filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs if root and is_valid_django_model(root._meta.model): extra_filters = get_extra_filters(root, manager.model) @@ -172,7 +150,6 @@ def __init__( *args, **kwargs, ): - _fields = _type._meta.filter_fields _model = _type._meta.model @@ -183,26 +160,16 @@ def __init__( filterset_class = filterset_class or _type._meta.filterset_class self.filterset_class = get_filterset_class(filterset_class, **meta) - self.filtering_args = get_filtering_args_from_filterset( - self.filterset_class, _type - ) + self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, _type) kwargs.setdefault("args", {}) kwargs["args"].update(self.filtering_args) if "id" not in kwargs["args"].keys(): self.filtering_args.update( - { - "id": Argument( - ID, description="Django object unique identification field" - ) - } + {"id": Argument(ID, description="Django object unique identification field")} ) kwargs["args"].update( - { - "id": Argument( - ID, description="Django object unique identification field" - ) - } + {"id": Argument(ID, description="Django object unique identification field")} ) pagination = pagination or graphql_api_settings.DEFAULT_PAGINATION_CLASS() @@ -220,9 +187,7 @@ def __init__( if not kwargs.get("description", None): kwargs["description"] = "{} list".format(_type._meta.model.__name__) - super(DjangoFilterPaginateListField, self).__init__( - List(NonNull(_type)), *args, **kwargs - ) + super(DjangoFilterPaginateListField, self).__init__(List(NonNull(_type)), *args, **kwargs) @property def model(self): @@ -231,9 +196,7 @@ def model(self): def get_queryset(self, manager, root, info, **kwargs): return queryset_factory(manager, root, info, **kwargs) - def list_resolver( - self, manager, filterset_class, filtering_args, root, info, **kwargs - ): + def list_resolver(self, manager, filterset_class, filtering_args, root, info, **kwargs): filter_kwargs = {k: v for k, v in kwargs.items() if k in filtering_args} qs = self.get_queryset(manager, root, info, **kwargs) qs = filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs @@ -269,7 +232,6 @@ def __init__( *args, **kwargs, ): - if DJANGO_FILTER_INSTALLED: _fields = _type._meta.filter_fields _model = _type._meta.model @@ -282,17 +244,13 @@ def __init__( filterset_class = filterset_class or _type._meta.filterset_class self.filterset_class = get_filterset_class(filterset_class, **meta) - self.filtering_args = get_filtering_args_from_filterset( - self.filterset_class, _type - ) + self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, _type) kwargs.setdefault("args", {}) kwargs["args"].update(self.filtering_args) if "id" not in kwargs["args"].keys(): id_description = "Django object unique identification field" - self.filtering_args.update( - {"id": Argument(ID, description=id_description)} - ) + self.filtering_args.update({"id": Argument(ID, description=id_description)}) kwargs["args"].update({"id": Argument(ID, description=id_description)}) if not kwargs.get("description", None): @@ -304,10 +262,7 @@ def __init__( def model(self): return self.type._meta.model - def list_resolver( - self, manager, filterset_class, filtering_args, root, info, **kwargs - ): - + def list_resolver(self, manager, filterset_class, filtering_args, root, info, **kwargs): qs = queryset_factory(manager, root, info, **kwargs) filter_kwargs = {k: v for k, v in kwargs.items() if k in filtering_args} diff --git a/graphene_django_extras/filters/__init__.py b/graphene_django_extras/filters/__init__.py index 0bd8259..175cac7 100644 --- a/graphene_django_extras/filters/__init__.py +++ b/graphene_django_extras/filters/__init__.py @@ -3,9 +3,9 @@ ALL_LOOKUPS, BASIC_LOOKUPS, COMMON_LOOKUPS, - NUMBER_LOOKUPS, - DATETIME_LOOKUPS, DATE_LOOKUPS, + DATETIME_LOOKUPS, + NUMBER_LOOKUPS, TIME_LOOKUPS, ) diff --git a/graphene_django_extras/filters/filter.py b/graphene_django_extras/filters/filter.py index b2d4d3d..07155c1 100644 --- a/graphene_django_extras/filters/filter.py +++ b/graphene_django_extras/filters/filter.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from django_filters.filterset import BaseFilterSet, FilterSet -from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS +from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS, BaseFilterSet, FilterSet from graphene_django.filter.utils import replace_csv_filters diff --git a/graphene_django_extras/middleware.py b/graphene_django_extras/middleware.py index ef13c21..24cf619 100644 --- a/graphene_django_extras/middleware.py +++ b/graphene_django_extras/middleware.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from graphql.type.directives import GraphQLIncludeDirective -from graphql.type.directives import GraphQLSkipDirective +from graphql.type.directives import GraphQLIncludeDirective, GraphQLSkipDirective from .registry import get_global_registry @@ -23,8 +22,6 @@ def __process_value(self, value, root, info, **kwargs): GraphQLSkipDirective.name, ): directive_class = registry.get_directive(directive.name.value) - new_value = directive_class.resolve( - new_value, directive, root, info, **kwargs - ) + new_value = directive_class.resolve(new_value, directive, root, info, **kwargs) return new_value diff --git a/graphene_django_extras/mutation.py b/graphene_django_extras/mutation.py index efd60eb..f83cac9 100644 --- a/graphene_django_extras/mutation.py +++ b/graphene_django_extras/mutation.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from collections import OrderedDict -from graphene import Boolean, List, Field, ID, Argument, ObjectType +from graphene import ID, Argument, Boolean, Field, List, ObjectType from graphene.types.base import BaseOptions from graphene.utils.deprecated import warn_deprecation from graphene.utils.props import props @@ -9,7 +9,7 @@ from .base_types import factory_type from .registry import get_global_registry -from .types import DjangoObjectType, DjangoInputObjectType +from .types import DjangoInputObjectType, DjangoObjectType from .utils import get_Object_or_None @@ -49,17 +49,12 @@ def __init_subclass_with_meta__( nested_fields=(), **options, ): - if not serializer_class: - raise Exception( - "serializer_class is required on all DjangoSerializerMutation" - ) + raise Exception("serializer_class is required on all DjangoSerializerMutation") model = serializer_class.Meta.model - description = description or "SerializerMutation for {} model".format( - model.__name__ - ) + description = description or "SerializerMutation for {} model".format(model.__name__) input_field_name = input_field_name or "new_{}".format(model._meta.model_name) output_field_name = output_field_name or model._meta.model_name @@ -211,9 +206,7 @@ def delete(cls, root, info, **kwargs): ErrorType( field="id", messages=[ - "A {} obj with id {} do not exist".format( - cls._meta.model.__name__, pk - ) + "A {} obj with id {} do not exist".format(cls._meta.model.__name__, pk) ], ) ] @@ -249,9 +242,7 @@ def update(cls, root, info, **kwargs): ErrorType( field="id", messages=[ - "A {} obj with id: {} do not exist".format( - cls._meta.model.__name__, pk - ) + "A {} obj with id: {} do not exist".format(cls._meta.model.__name__, pk) ], ) ] @@ -265,17 +256,14 @@ def save(cls, serialized_obj, root, info, **kwargs): """ for key in serialized_obj.initial_data: if "Enum" in type(serialized_obj.initial_data[key]).__name__: - serialized_obj.initial_data[key] = serialized_obj.initial_data[ - key - ].value + serialized_obj.initial_data[key] = serialized_obj.initial_data[key].value if serialized_obj.is_valid(): obj = serialized_obj.save() return True, obj else: errors = [ - ErrorType(field=key, messages=value) - for key, value in serialized_obj.errors.items() + ErrorType(field=key, messages=value) for key, value in serialized_obj.errors.items() ] return False, errors diff --git a/graphene_django_extras/paginations/fields.py b/graphene_django_extras/paginations/fields.py index 5fac1f5..7b9efa7 100644 --- a/graphene_django_extras/paginations/fields.py +++ b/graphene_django_extras/paginations/fields.py @@ -4,8 +4,8 @@ from graphene import Field, Int, List, NonNull, String -from .utils import _nonzero_int, _get_count from ..settings import graphql_api_settings +from .utils import _get_count, _nonzero_int __all__ = ("LimitOffsetPaginationField", "PagePaginationField", "CursorPaginationField") @@ -16,9 +16,7 @@ def model(self): return self.type.of_type._meta.node._meta.model def wrap_resolve(self, parent_resolver): - return partial( - self.list_resolver, self.type.of_type._meta.model._default_manager - ) + return partial(self.list_resolver, self.type.of_type._meta.model._default_manager) # *********************************************** # @@ -37,7 +35,6 @@ def __init__( *args, **kwargs, ): - kwargs.setdefault("args", {}) self.limit_query_param = limit_query_param @@ -50,9 +47,7 @@ def __init__( kwargs[limit_query_param] = Int( default_value=self.default_limit, description="Number of results to return per page. Actual " - "'default_limit': {}, and 'max_limit': {}".format( - self.default_limit, self.max_limit - ), + "'default_limit': {}, and 'max_limit': {}".format(self.default_limit, self.max_limit), ) kwargs[offset_query_param] = Int( @@ -104,7 +99,6 @@ def __init__( *args, **kwargs, ): - kwargs.setdefault("args", {}) # Client can control the page using this query parameter. @@ -126,9 +120,7 @@ def __init__( self.ordering_param = ordering_param self.page_size_query_description = ( - "Number of results to return per page. Actual 'page_size': {}".format( - self.page_size - ) + "Number of results to return per page. Actual 'page_size': {}".format(self.page_size) ) kwargs[self.page_query_param] = Int( @@ -179,9 +171,7 @@ def list_resolver(self, manager, root, info, **kwargs): "declaration to specify a custom page size value through a query parameters" ) - offset = ( - int(count - fabs(page_size * page)) if page < 0 else page_size * (page - 1) - ) + offset = int(count - fabs(page_size * page)) if page < 0 else page_size * (page - 1) order = kwargs.pop(self.ordering_param, None) or self.ordering if order: @@ -196,9 +186,7 @@ def list_resolver(self, manager, root, info, **kwargs): class CursorPaginationField(AbstractPaginationField): - def __init__( - self, _type, ordering="-created", cursor_query_param="cursor", *args, **kwargs - ): + def __init__(self, _type, ordering="-created", cursor_query_param="cursor", *args, **kwargs): kwargs.setdefault("args", {}) self.page_size = graphql_api_settings.DEFAULT_PAGE_SIZE @@ -208,9 +196,7 @@ def __init__( self.cursor_query_description = "The pagination cursor value." self.page_size_query_description = "Number of results to return per page." - kwargs[self.cursor_query_param] = NonNull( - String, description=self.cursor_query_description - ) + kwargs[self.cursor_query_param] = NonNull(String, description=self.cursor_query_description) if self.page_size_query_param: if not self.page_size: @@ -226,7 +212,5 @@ def __init__( def list_resolver(self, manager, root, info, **kwargs): raise NotImplementedError( - "{} list_resolver() are not implemented yet.".format( - self.__class__.__name__ - ) + "{} list_resolver() are not implemented yet.".format(self.__class__.__name__) ) diff --git a/graphene_django_extras/paginations/pagination.py b/graphene_django_extras/paginations/pagination.py index b5e3740..6811147 100644 --- a/graphene_django_extras/paginations/pagination.py +++ b/graphene_django_extras/paginations/pagination.py @@ -4,8 +4,8 @@ from graphene import Int, NonNull, String from graphene_django_extras.paginations.utils import ( - _get_count, GenericPaginationField, + _get_count, _nonzero_int, ) from graphene_django_extras.settings import graphql_api_settings @@ -32,9 +32,7 @@ def to_graphql_fields(self): ) def to_dict(self): - raise NotImplementedError( - "to_dict() function must be implemented into child classes." - ) + raise NotImplementedError("to_dict() function must be implemented into child classes.") def paginate_queryset(self, qs, **kwargs): raise NotImplementedError( @@ -54,7 +52,6 @@ def __init__( offset_query_param="offset", ordering_param="ordering", ): - # A numeric value indicating the limit to use if one is not provided by the client in a query parameter. self.default_limit = default_limit @@ -137,7 +134,6 @@ def __init__( ordering="", ordering_param="ordering", ): - # Client can control the page using this query parameter. self.page_query_param = "page" @@ -160,9 +156,7 @@ def __init__( self.ordering_param = ordering_param self.page_size_query_description = ( - "Number of results to return per page. Default 'page_size': {}".format( - self.page_size - ) + "Number of results to return per page. Default 'page_size': {}".format(self.page_size) ) def to_dict(self): @@ -189,11 +183,7 @@ def to_graphql_fields(self): if self.page_size_query_param: paginator_dict.update( - { - self.page_size_query_param: Int( - description=self.page_size_query_description - ) - } + {self.page_size_query_param: Int(description=self.page_size_query_description)} ) return paginator_dict @@ -222,11 +212,7 @@ def paginate_queryset(self, qs, **kwargs): """ return None - offset = ( - max(0, int(count + page_size * page)) - if page < 0 - else page_size * (page - 1) - ) + offset = max(0, int(count + page_size * page)) if page < 0 else page_size * (page - 1) order = kwargs.pop(self.ordering_param, None) or self.ordering if order: @@ -246,7 +232,6 @@ class CursorGraphqlPagination(BaseDjangoGraphqlPagination): page_size = graphql_api_settings.DEFAULT_PAGE_SIZE def __init__(self, ordering="-created", cursor_query_param="cursor"): - self.page_size_query_param = "page_size" if not self.page_size else None self.cursor_query_param = cursor_query_param self.ordering = ordering @@ -260,11 +245,7 @@ def to_dict(self): } def to_graphql_fields(self): - return { - self.cursor_query_param: NonNull( - String, description=self.cursor_query_description - ) - } + return {self.cursor_query_param: NonNull(String, description=self.cursor_query_description)} def paginate_queryset(self, qs, **kwargs): raise NotImplementedError( diff --git a/graphene_django_extras/paginations/utils.py b/graphene_django_extras/paginations/utils.py index 01b2be3..b35d97b 100644 --- a/graphene_django_extras/paginations/utils.py +++ b/graphene_django_extras/paginations/utils.py @@ -26,9 +26,7 @@ def __init__(self, _type, paginator_instance, *args, **kwargs): } ) - super(GenericPaginationField, self).__init__( - graphene.List(_type), *args, **kwargs - ) + super(GenericPaginationField, self).__init__(graphene.List(_type), *args, **kwargs) @property def model(self): @@ -40,9 +38,7 @@ def list_resolver(self, manager, root, info, **kwargs): return None def wrap_resolve(self, parent_resolver): - return partial( - self.list_resolver, self.type.of_type._meta.model._default_manager - ) + return partial(self.list_resolver, self.type.of_type._meta.model._default_manager) def _positive_int(integer_string, strict=False, cutoff=None): diff --git a/graphene_django_extras/settings.py b/graphene_django_extras/settings.py index 44a39a6..552b419 100644 --- a/graphene_django_extras/settings.py +++ b/graphene_django_extras/settings.py @@ -3,7 +3,6 @@ from django.test.signals import setting_changed from rest_framework.settings import APISettings - DEFAULTS = { # Pagination "DEFAULT_PAGINATION_CLASS": None, # 'graphene_django_extras.paginations.LimitOffsetGraphqlPagination' diff --git a/graphene_django_extras/types.py b/graphene_django_extras/types.py index ba57b98..c0504a0 100644 --- a/graphene_django_extras/types.py +++ b/graphene_django_extras/types.py @@ -3,24 +3,20 @@ from django.db.models import QuerySet from django.utils.functional import SimpleLazyObject -from graphene import Field, InputField, ObjectType, Int, Argument, ID, Boolean, List +from graphene import ID, Argument, Boolean, Field, InputField, Int, List, ObjectType from graphene.types.base import BaseOptions from graphene.types.inputobjecttype import InputObjectType, InputObjectTypeContainer from graphene.types.utils import yank_fields_from_attrs from graphene.utils.deprecated import warn_deprecation from graphene.utils.props import props from graphene_django.types import ErrorType -from graphene_django.utils import ( - is_valid_django_model, - DJANGO_FILTER_INSTALLED, - maybe_queryset, -) +from graphene_django.utils import DJANGO_FILTER_INSTALLED, is_valid_django_model, maybe_queryset from .base_types import DjangoListObjectBase, factory_type from .converter import construct_fields -from .fields import DjangoObjectField, DjangoListObjectField, DjangoListField +from .fields import DjangoListField, DjangoListObjectField, DjangoObjectField from .paginations.pagination import BaseDjangoGraphqlPagination -from .registry import get_global_registry, Registry +from .registry import Registry, get_global_registry from .settings import graphql_api_settings from .utils import get_Object_or_None, queryset_factory @@ -88,8 +84,7 @@ def __init_subclass_with_meta__( registry = get_global_registry() assert isinstance(registry, Registry), ( - "The attribute registry in {} needs to be an instance of " - 'Registry, received "{}".' + "The attribute registry in {} needs to be an instance of " 'Registry, received "{}".' ).format(cls.__name__, registry) if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class): @@ -98,9 +93,7 @@ def __init_subclass_with_meta__( ) django_fields = yank_fields_from_attrs( - construct_fields( - model, registry, only_fields, include_fields, exclude_fields - ), + construct_fields(model, registry, only_fields, include_fields, exclude_fields), _as=Field, ) @@ -169,8 +162,7 @@ def __init_subclass_with_meta__( registry = get_global_registry() assert isinstance(registry, Registry), ( - "The attribute registry in {} needs to be an instance of " - 'Registry, received "{}".' + "The attribute registry in {} needs to be an instance of " 'Registry, received "{}".' ).format(cls.__name__, registry) assert input_for.lower not in ("create", "delete", "update"), ( @@ -197,9 +189,7 @@ def __init_subclass_with_meta__( ) for base in reversed(cls.__mro__): - django_input_fields.update( - yank_fields_from_attrs(base.__dict__, _as=InputField) - ) + django_input_fields.update(yank_fields_from_attrs(base.__dict__, _as=InputField)) if container is None: container = type(cls.__name__, (InputObjectTypeContainer, cls), {}) @@ -251,7 +241,6 @@ def __init_subclass_with_meta__( filterset_class=None, **options, ): - assert is_valid_django_model(model), ( 'You need to pass a valid Django Model in {}.Meta, received "{}".' ).format(cls.__name__, model) @@ -325,9 +314,7 @@ def __init_subclass_with_meta__( ] ) - super(DjangoListObjectType, cls).__init_subclass_with_meta__( - _meta=_meta, **options - ) + super(DjangoListObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options) @classmethod def RetrieveField(cls, *args, **kwargs): @@ -367,15 +354,12 @@ def __init_subclass_with_meta__( filterset_class=None, **options, ): - if not serializer_class: raise Exception("serializer_class is required on all ModelSerializerType") model = serializer_class.Meta.model - description = description or "ModelSerializerType for {} model".format( - model.__name__ - ) + description = description or "ModelSerializerType for {} model".format(model.__name__) input_field_name = input_field_name or "new_{}".format(model._meta.model_name) output_field_name = output_field_name or model._meta.model_name @@ -545,9 +529,7 @@ def delete(cls, root, info, **kwargs): ErrorType( field="id", messages=[ - "A {} obj with id {} do not exist".format( - cls._meta.model.__name__, pk - ) + "A {} obj with id {} do not exist".format(cls._meta.model.__name__, pk) ], ) ] @@ -583,9 +565,7 @@ def update(cls, root, info, **kwargs): ErrorType( field="id", messages=[ - "A {} obj with id: {} do not exist".format( - cls._meta.model.__name__, pk - ) + "A {} obj with id: {} do not exist".format(cls._meta.model.__name__, pk) ], ) ] @@ -599,8 +579,7 @@ def save(cls, serialized_obj, root, info, **kwargs): else: errors = [ - ErrorType(field=key, messages=value) - for key, value in serialized_obj.errors.items() + ErrorType(field=key, messages=value) for key, value in serialized_obj.errors.items() ] return False, errors @@ -615,7 +594,6 @@ def retrieve(cls, manager, root, info, **kwargs): @classmethod def list(cls, manager, filterset_class, filtering_args, root, info, **kwargs): - qs = queryset_factory(cls._meta.queryset or manager, root, info, **kwargs) filter_kwargs = {k: v for k, v in kwargs.items() if k in filtering_args} @@ -635,9 +613,7 @@ def RetrieveField(cls, *args, **kwargs): @classmethod def ListField(cls, *args, **kwargs): - return DjangoListObjectField( - cls._meta.output_list_type, resolver=cls.list, **kwargs - ) + return DjangoListObjectField(cls._meta.output_list_type, resolver=cls.list, **kwargs) @classmethod def CreateField(cls, *args, **kwargs): diff --git a/graphene_django_extras/utils.py b/graphene_django_extras/utils.py index 4b2df79..05931f9 100644 --- a/graphene_django_extras/utils.py +++ b/graphene_django_extras/utils.py @@ -7,15 +7,8 @@ from django import VERSION as DJANGO_VERSION from django.apps import apps from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel -from django.core.exceptions import ValidationError, ImproperlyConfigured -from django.db.models import ( - NOT_PROVIDED, - QuerySet, - Manager, - Model, - ManyToOneRel, - ManyToManyRel, -) +from django.core.exceptions import ImproperlyConfigured, ValidationError +from django.db.models import NOT_PROVIDED, Manager, ManyToManyRel, ManyToOneRel, Model, QuerySet from django.db.models.base import ModelBase from graphene.utils.str_converters import to_snake_case from graphene_django.utils import is_valid_django_model @@ -92,9 +85,7 @@ def get_model_fields(model): reverse_fields = list(get_reverse_fields(model)) exclude_fields = [field[1] for field in reverse_fields] - local_fields = [ - (field.name, field) for field in all_fields_list if field not in exclude_fields - ] + local_fields = [(field.name, field) for field in all_fields_list if field not in exclude_fields] all_fields = local_fields + reverse_fields @@ -174,9 +165,7 @@ def clean_dict(d): return d if isinstance(d, list): return [v for v in (clean_dict(v) for v in d) if v] - return OrderedDict( - [(k, v) for k, v in ((k, clean_dict(v)) for k, v in list(d.items())) if v] - ) + return OrderedDict([(k, v) for k, v in ((k, clean_dict(v)) for k, v in list(d.items())) if v]) def get_type(_type): @@ -300,9 +289,7 @@ def get_related_fields(model): def find_field(field, fields_dict): - temp = fields_dict.get( - field.name.value, fields_dict.get(to_snake_case(field.name.value), None) - ) + temp = fields_dict.get(field.name.value, fields_dict.get(to_snake_case(field.name.value), None)) return temp @@ -310,9 +297,7 @@ def find_field(field, fields_dict): def recursive_params( selection_set, fragments, available_related_fields, select_related, prefetch_related ): - for field in selection_set.selections: - if isinstance(field, FragmentSpreadNode) and fragments: a, b = recursive_params( fragments[field.name.value].selection_set, @@ -362,7 +347,6 @@ def recursive_params( def queryset_factory(manager, root, info, **kwargs): - select_related = set() prefetch_related = set() available_related_fields = get_related_fields(manager.model) @@ -404,7 +388,6 @@ def queryset_factory(manager, root, info, **kwargs): def parse_validation_exc(validation_exc): - errors_list = [] for key, value in validation_exc.error_dict.items(): for exc in value: diff --git a/graphene_django_extras/views.py b/graphene_django_extras/views.py index f1eec68..442ced1 100644 --- a/graphene_django_extras/views.py +++ b/graphene_django_extras/views.py @@ -4,13 +4,13 @@ from django.core.cache import caches from django.views.decorators.csrf import csrf_exempt from graphene_django.views import GraphQLView -from graphql import Source, parse, execute +from graphql import Source, execute, parse from graphql.execution.executor import subscribe from graphql.utils.get_operation_ast import get_operation_ast from rest_framework.decorators import ( + api_view, authentication_classes, permission_classes, - api_view, throttle_classes, ) from rest_framework.permissions import IsAuthenticated @@ -105,9 +105,7 @@ def get_response(self, request, data, show_graphiql=False): response = {} if execution_result.errors: - response["errors"] = [ - self.format_error(e) for e in execution_result.errors - ] + response["errors"] = [self.format_error(e) for e in execution_result.errors] if execution_result.invalid: status_code = 400 diff --git a/scripts/bump.py b/scripts/bump.py index f7b5cee..14c3423 100755 --- a/scripts/bump.py +++ b/scripts/bump.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -from pathlib import Path import argparse import re +from pathlib import Path + import toml @@ -27,9 +28,7 @@ def get_args(): nargs="*", help="Patch version bump. Empty flag will auto bump current version.", ) - parser.add_argument( - "--dry-run", type=str, default="yes", choices=["yes", "no"], help="Dry run" - ) + parser.add_argument("--dry-run", type=str, default="yes", choices=["yes", "no"], help="Dry run") return parser.parse_args() @@ -130,6 +129,4 @@ def bump_version(major, minor, patch): NV = f'version = "{NEW_VERSION}"' content = content.replace(CV, NV) file.write(content) - print( - f"Successfully updated package version from {CURRENT_VERSION} to {NEW_VERSION}" - ) + print(f"Successfully updated package version from {CURRENT_VERSION} to {NEW_VERSION}") diff --git a/scripts/notification.sh b/scripts/notification.sh index 9c8ed35..47012b6 100755 --- a/scripts/notification.sh +++ b/scripts/notification.sh @@ -30,4 +30,4 @@ ${GITHUB_COMMIT_MESSAGE} [See complete job log here](${GITHUB_RUN_URL}) ----------------------------------------------------- -" \ No newline at end of file +" diff --git a/tests/conftest.py b/tests/conftest.py index 5a95c13..f6f6daa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,9 +29,7 @@ def pytest_configure(config): settings.configure( ALLOWED_HOSTS=["*"], DEBUG_PROPAGATE_EXCEPTIONS=True, - DATABASES={ - "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"} - }, + DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}, SITE_ID=1, SECRET_KEY="not very secret in tests", USE_I18N=True, diff --git a/tests/factories.py b/tests/factories.py index 7d02d88..1953809 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -1,5 +1,4 @@ import factory - from django.contrib.auth.models import User diff --git a/tests/schema.py b/tests/schema.py index 8846946..cc1de71 100644 --- a/tests/schema.py +++ b/tests/schema.py @@ -5,19 +5,20 @@ from django.utils.translation import gettext_lazy as _ from graphene_django_extras import all_directives -from graphene_django_extras.base_types import CustomDateTime, CustomDate, CustomTime +from graphene_django_extras.base_types import CustomDate, CustomDateTime, CustomTime from graphene_django_extras.fields import ( - DjangoObjectField, - DjangoListObjectField, - DjangoFilterPaginateListField, DjangoFilterListField, + DjangoFilterPaginateListField, + DjangoListObjectField, + DjangoObjectField, ) from graphene_django_extras.paginations import LimitOffsetGraphqlPagination from graphene_django_extras.types import ( DjangoListObjectType, - DjangoSerializerType, DjangoObjectType, + DjangoSerializerType, ) + from .filtersets import UserFilterSet from .serializers import UserSerializer @@ -39,18 +40,14 @@ class User1ListType(DjangoListObjectType): class Meta: description = " Type definition for user list " model = User - pagination = LimitOffsetGraphqlPagination( - default_limit=25, ordering="-username" - ) + pagination = LimitOffsetGraphqlPagination(default_limit=25, ordering="-username") class UserModelType(DjangoSerializerType): class Meta: description = " Serializer Type definition for user " serializer_class = UserSerializer - pagination = LimitOffsetGraphqlPagination( - default_limit=25, ordering="-username" - ) + pagination = LimitOffsetGraphqlPagination(default_limit=25, ordering="-username") filterset_class = UserFilterSet @@ -74,9 +71,7 @@ class Meta: class Query(graphene.ObjectType): # Possible User list queries definitions all_users = DjangoListObjectField(User1ListType, description=_("All Users query")) - all_users1 = DjangoFilterPaginateListField( - UserType, pagination=LimitOffsetGraphqlPagination() - ) + all_users1 = DjangoFilterPaginateListField(UserType, pagination=LimitOffsetGraphqlPagination()) all_users2 = DjangoFilterListField(UserType) all_users3 = DjangoListObjectField( User1ListType, filterset_class=UserFilterSet, description=_("All Users query") @@ -89,9 +84,7 @@ class Query(graphene.ObjectType): user = DjangoObjectField(UserType, description=_("Single User query")) # Another way to define a query to single user - user1 = User1ListType.RetrieveField( - description=_("User List with pagination and filtering") - ) + user1 = User1ListType.RetrieveField(description=_("User List with pagination and filtering")) # Exist two ways to define single or list user queries with DjangoSerializerType user2, users = UserModelType.QueryFields() diff --git a/tests/test_fields.py b/tests/test_fields.py index fc17beb..ca090c1 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,8 +1,8 @@ import uuid from django.test import TestCase -from tests import factories -from tests import queries + +from tests import factories, queries from tests.client import Client @@ -24,9 +24,7 @@ def test_should_return_expected_status_code(self): self.assertEqual(self.response.status_code, self.expected_status_code) def test_should_return_expected_payload(self): - self.assertEqual( - self.response.json(), self.expected_return_payload, self.response.content - ) + self.assertEqual(self.response.json(), self.expected_return_payload, self.response.content) class DjangoListObjectFieldTest(ParentTest, TestCase): @@ -34,9 +32,7 @@ class DjangoListObjectFieldTest(ParentTest, TestCase): expected_return_payload = {"data": {"allUsers": {"results": [{"id": "1"}]}}} def test_field(self): - self.assertEqual( - self.data["data"]["allUsers"]["results"][0]["id"], str(self.user.id) - ) + self.assertEqual(self.data["data"]["allUsers"]["results"][0]["id"], str(self.user.id)) class DjangoFilterPaginateListFieldTest(ParentTest, TestCase): @@ -62,9 +58,7 @@ class DjangoFilterListFieldTest(ParentTest, TestCase): class DjangoListObjectFieldWithFilterSetTest(ParentTest, TestCase): - expected_return_payload = { - "data": {"allUsers3": {"results": [{"username": "graphql"}]}} - } + expected_return_payload = {"data": {"allUsers3": {"results": [{"username": "graphql"}]}}} @property def query(self): @@ -84,9 +78,7 @@ def test_filter_charfield_icontains(self): self.assertIn("allUsers3", data["data"]) self.assertIn("results", data["data"]["allUsers3"]) self.assertTrue(data["data"]["allUsers3"]["results"]) - self.assertEqual( - data["data"]["allUsers3"]["results"][0]["username"], self.user.username - ) + self.assertEqual(data["data"]["allUsers3"]["results"][0]["username"], self.user.username) def test_filter_charfield_iexact(self): query = queries.ALL_USERS3_WITH_FILTER % { @@ -99,18 +91,14 @@ def test_filter_charfield_iexact(self): self.assertIn("allUsers3", data["data"]) self.assertIn("results", data["data"]["allUsers3"]) self.assertTrue(data["data"]["allUsers3"]["results"]) - self.assertEqual( - data["data"]["allUsers3"]["results"][0]["username"], self.user.username - ) + self.assertEqual(data["data"]["allUsers3"]["results"][0]["username"], self.user.username) class DjangoSerializerTypeTest(ParentTest, TestCase): expected_return_payload = { "data": { "users": { - "results": [ - {"id": "1", "username": "graphql", "email": "eamigop86@gmail.com"} - ], + "results": [{"id": "1", "username": "graphql", "email": "eamigop86@gmail.com"}], "totalCount": 1, } } @@ -141,9 +129,7 @@ class DjangoCustomResolverTest(ParentTest, TestCase): query = queries.ALL_USERS4 def setUp(self): - self.staff_user = factories.UserFactory( - username=uuid.uuid4().hex, is_staff=True - ) + self.staff_user = factories.UserFactory(username=uuid.uuid4().hex, is_staff=True) super().setUp() @property diff --git a/tests/urls.py b/tests/urls.py index 0f66a07..000e0d5 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -8,9 +8,7 @@ urlpatterns = [ path("admin/", admin.site.urls), - path( - "graphql", csrf_exempt(GraphQLView.as_view(graphiql=True)), name="graphql" - ), + path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True)), name="graphql"), ] else: from django.conf.urls import url