From 968e5e5b97cc60a9d42b5a23e29128ffdf3cb293 Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Thu, 26 May 2022 15:04:50 +0200 Subject: [PATCH 01/10] Added Python 3.10; set 3.10 as default version & updated pre-commit hooks --- .github/workflows/deploy.yml | 4 ++-- .github/workflows/lint.yml | 4 ++-- .github/workflows/tests.yml | 6 +++--- .pre-commit-config.yaml | 17 +++++++---------- setup.py | 2 ++ tox.ini | 3 ++- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1ae7b4b6..e4e4f559 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,10 +11,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Build wheel and source tarball run: | pip install wheel diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 559326c4..235fae26 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,10 +8,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a9a3bd5d..5d0d86f9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 10 matrix: sql-alchemy: ["1.2", "1.3", "1.4"] - python-version: ["3.6", "3.7", "3.8", "3.9"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 @@ -27,12 +27,12 @@ jobs: SQLALCHEMY: ${{ matrix.sql-alchemy }} TOXENV: ${{ matrix.toxenv }} - name: Upload coverage.xml - if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.9' }} + if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.10' }} uses: actions/upload-artifact@v2 with: name: graphene-sqlalchemy-coverage path: coverage.xml if-no-files-found: error - name: Upload coverage.xml to codecov - if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.9' }} + if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.10' }} uses: codecov/codecov-action@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c67ab03..58143000 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,8 @@ default_language_version: - python: python3.7 + python: python3.10 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: c8bad492e1b1d65d9126dba3fe3bd49a5a52b9d6 # v2.1.0 + rev: v4.2.0 hooks: - id: check-merge-conflict - id: check-yaml @@ -12,14 +12,11 @@ repos: - id: trailing-whitespace exclude: README.md - repo: https://github.com/PyCQA/flake8 - rev: 88caf5ac484f5c09aedc02167c59c66ff0af0068 # 3.7.7 + rev: v4.0.0 hooks: - id: flake8 -- repo: https://github.com/asottile/seed-isort-config - rev: v1.7.0 + - repo: https://github.com/pycqa/isort + rev: 5.10.1 hooks: - - id: seed-isort-config -- repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.4 - hooks: - - id: isort + - id: isort + name: isort (python) diff --git a/setup.py b/setup.py index da49f1d4..6f368c18 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,8 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: PyPy", ], keywords="api graphql protocol rest relay graphene", diff --git a/tox.ini b/tox.ini index b8ce0618..82356725 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ python = 3.7: py37 3.8: py38 3.9: py39 + 3.10: py310 [gh-actions:env] SQLALCHEMY = @@ -27,7 +28,7 @@ commands = pytest graphene_sqlalchemy --cov=graphene_sqlalchemy --cov-report=term --cov-report=xml {posargs} [testenv:pre-commit] -basepython=python3.9 +basepython=python3.10 deps = .[dev] commands = From 3e192bb5f760e01bf8ae0ced69db6f2a93464bfc Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Thu, 26 May 2022 15:41:51 +0200 Subject: [PATCH 02/10] Fixes to Pre-Commit, GH Actions --- .github/workflows/deploy.yml | 6 +++--- .github/workflows/lint.yml | 6 +++--- .github/workflows/tests.yml | 8 ++++---- .pre-commit-config.yaml | 20 ++++++++++---------- graphene_sqlalchemy/__init__.py | 2 +- graphene_sqlalchemy/converter.py | 3 ++- graphene_sqlalchemy/utils.py | 1 + setup.py | 5 ++--- tox.ini | 3 +-- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e4e4f559..9cc136a1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -10,11 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: - python-version: 3.10 + python-version: '3.10' - name: Build wheel and source tarball run: | pip install wheel diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 235fae26..9352dbe5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,11 +7,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: - python-version: 3.10 + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d0d86f9..1c79baf9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,12 +9,12 @@ jobs: max-parallel: 10 matrix: sql-alchemy: ["1.2", "1.3", "1.4"] - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -28,7 +28,7 @@ jobs: TOXENV: ${{ matrix.toxenv }} - name: Upload coverage.xml if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.10' }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: graphene-sqlalchemy-coverage path: coverage.xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58143000..d76c4a0a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,20 @@ default_language_version: - python: python3.10 + python: python3.10 repos: -- repo: https://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.2.0 hooks: - - id: check-merge-conflict - - id: check-yaml - - id: debug-statements - - id: end-of-file-fixer + - id: check-merge-conflict + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer exclude: ^docs/.*$ - - id: trailing-whitespace + - id: trailing-whitespace exclude: README.md -- repo: https://github.com/PyCQA/flake8 - rev: v4.0.0 + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.0 hooks: - - id: flake8 + - id: flake8 - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: diff --git a/graphene_sqlalchemy/__init__.py b/graphene_sqlalchemy/__init__.py index 060bd13b..18d34f1d 100644 --- a/graphene_sqlalchemy/__init__.py +++ b/graphene_sqlalchemy/__init__.py @@ -1,5 +1,5 @@ -from .types import SQLAlchemyObjectType from .fields import SQLAlchemyConnectionField +from .types import SQLAlchemyObjectType from .utils import get_query, get_session __version__ = "3.0.0b1" diff --git a/graphene_sqlalchemy/converter.py b/graphene_sqlalchemy/converter.py index 5d75984b..60e14ddd 100644 --- a/graphene_sqlalchemy/converter.py +++ b/graphene_sqlalchemy/converter.py @@ -29,7 +29,8 @@ from typing import _ForwardRef as ForwardRef try: - from sqlalchemy_utils import ChoiceType, JSONType, ScalarListType, TSVectorType + from sqlalchemy_utils import (ChoiceType, JSONType, ScalarListType, + TSVectorType) except ImportError: ChoiceType = JSONType = ScalarListType = TSVectorType = object diff --git a/graphene_sqlalchemy/utils.py b/graphene_sqlalchemy/utils.py index 301e782c..084f9b86 100644 --- a/graphene_sqlalchemy/utils.py +++ b/graphene_sqlalchemy/utils.py @@ -140,6 +140,7 @@ def sort_argument_for_model(cls, has_default=True): ) from graphene import Argument, List + from .enums import sort_enum_for_object_type enum = sort_enum_for_object_type( diff --git a/setup.py b/setup.py index 6f368c18..ac9ad7e6 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,6 @@ "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -54,8 +53,8 @@ extras_require={ "dev": [ "tox==3.7.0", # Should be kept in sync with tox.ini - "pre-commit==1.14.4", - "flake8==3.7.9", + "pre-commit==2.19", + "flake8==4.0.0", ], "test": tests_require, }, diff --git a/tox.ini b/tox.ini index 82356725..2b926fa0 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ minversion = 3.7.0 [gh-actions] python = - 3.6: py36 3.7: py37 3.8: py38 3.9: py39 @@ -35,7 +34,7 @@ commands = pre-commit {posargs:run --all-files} [testenv:flake8] -basepython = python3.9 +basepython = python3.10 deps = -e.[dev] commands = flake8 --exclude setup.py,docs,examples,tests,.tox --max-line-length 120 From 576dc4a06a6df9fa061c3afda2849a8c3330cc87 Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Thu, 26 May 2022 18:06:58 +0200 Subject: [PATCH 03/10] Fix tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2b926fa0..2802dee0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = pre-commit,py{36,37,38,39}-sql{12,13,14} +envlist = pre-commit,py{37,38,39,310}-sql{12,13,14} skipsdist = true minversion = 3.7.0 From 3b3314097fa69fad863ae78d6cabb4eebed7ed6e Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Tue, 31 May 2022 11:04:13 +0200 Subject: [PATCH 04/10] Update deprecated codecov v3 action --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1c79baf9..de78190d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,4 +35,4 @@ jobs: if-no-files-found: error - name: Upload coverage.xml to codecov if: ${{ matrix.sql-alchemy == '1.4' && matrix.python-version == '3.10' }} - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 From ec7daecc285b53b92cc5459f94e875bb533ca5d0 Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Tue, 31 May 2022 11:39:41 +0200 Subject: [PATCH 05/10] Add Black, pyupgrade to pre-commit & apply to all files --- .pre-commit-config.yaml | 15 +- docs/conf.py | 87 ++-- examples/flask_sqlalchemy/database.py | 23 +- examples/flask_sqlalchemy/models.py | 22 +- examples/flask_sqlalchemy/schema.py | 9 +- examples/nameko_sqlalchemy/app.py | 76 ++-- examples/nameko_sqlalchemy/database.py | 23 +- examples/nameko_sqlalchemy/models.py | 22 +- examples/nameko_sqlalchemy/service.py | 4 +- graphene_sqlalchemy/batching.py | 12 +- graphene_sqlalchemy/converter.py | 142 ++++--- graphene_sqlalchemy/enums.py | 16 +- graphene_sqlalchemy/fields.py | 22 +- graphene_sqlalchemy/resolvers.py | 2 +- graphene_sqlalchemy/tests/conftest.py | 2 +- graphene_sqlalchemy/tests/models.py | 43 +- graphene_sqlalchemy/tests/test_batching.py | 392 ++++++++++-------- graphene_sqlalchemy/tests/test_benchmark.py | 84 ++-- graphene_sqlalchemy/tests/test_converter.py | 164 +++++--- graphene_sqlalchemy/tests/test_enums.py | 29 +- graphene_sqlalchemy/tests/test_fields.py | 5 +- graphene_sqlalchemy/tests/test_query.py | 22 +- graphene_sqlalchemy/tests/test_query_enums.py | 47 +-- graphene_sqlalchemy/tests/test_reflected.py | 1 - graphene_sqlalchemy/tests/test_sort_enums.py | 2 +- graphene_sqlalchemy/tests/test_types.py | 309 ++++++++------ graphene_sqlalchemy/tests/test_utils.py | 16 +- graphene_sqlalchemy/types.py | 109 +++-- graphene_sqlalchemy/utils.py | 15 +- 29 files changed, 1005 insertions(+), 710 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d76c4a0a..00082f7b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,15 @@ repos: exclude: ^docs/.*$ - id: trailing-whitespace exclude: README.md + - repo: https://github.com/asottile/pyupgrade + rev: v2.32.1 + hooks: + - id: pyupgrade + - repo: https://github.com/ambv/black + rev: 22.3.0 + hooks: + - id: black - repo: https://github.com/PyCQA/flake8 - rev: 4.0.0 + rev: 4.0.1 hooks: - id: flake8 - - repo: https://github.com/pycqa/isort - rev: 5.10.1 - hooks: - - id: isort - name: isort (python) diff --git a/docs/conf.py b/docs/conf.py index 3fa6391d..9c9fc1d7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ import os -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" # -*- coding: utf-8 -*- # @@ -34,46 +34,46 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", ] if not on_rtd: extensions += [ - 'sphinx.ext.githubpages', + "sphinx.ext.githubpages", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Graphene Django' -copyright = u'Graphene 2016' -author = u'Syrus Akbary' +project = "Graphene Django" +copyright = "Graphene 2016" +author = "Syrus Akbary" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'1.0' +version = "1.0" # The full version, including alpha/beta/rc tags. -release = u'1.0.dev' +release = "1.0.dev" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -94,7 +94,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -116,7 +116,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -175,7 +175,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -255,34 +255,30 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'Graphenedoc' +htmlhelp_basename = "Graphenedoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Graphene.tex', u'Graphene Documentation', - u'Syrus Akbary', 'manual'), + (master_doc, "Graphene.tex", "Graphene Documentation", "Syrus Akbary", "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -323,8 +319,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'graphene_django', u'Graphene Django Documentation', - [author], 1) + (master_doc, "graphene_django", "Graphene Django Documentation", [author], 1) ] # If true, show URL addresses after external links. @@ -338,9 +333,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Graphene-Django', u'Graphene Django Documentation', - author, 'Graphene Django', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "Graphene-Django", + "Graphene Django Documentation", + author, + "Graphene Django", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. @@ -414,7 +415,7 @@ # epub_post_files = [] # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # The depth of the table of contents in toc.ncx. # @@ -446,4 +447,4 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {"https://docs.python.org/": None} diff --git a/examples/flask_sqlalchemy/database.py b/examples/flask_sqlalchemy/database.py index ca4d4122..74ec7ca9 100644 --- a/examples/flask_sqlalchemy/database.py +++ b/examples/flask_sqlalchemy/database.py @@ -2,10 +2,10 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker -engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True) -db_session = scoped_session(sessionmaker(autocommit=False, - autoflush=False, - bind=engine)) +engine = create_engine("sqlite:///database.sqlite3", convert_unicode=True) +db_session = scoped_session( + sessionmaker(autocommit=False, autoflush=False, bind=engine) +) Base = declarative_base() Base.query = db_session.query_property() @@ -15,24 +15,25 @@ def init_db(): # they will be registered properly on the metadata. Otherwise # you will have to import them first before calling init_db() from models import Department, Employee, Role + Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) # Create the fixtures - engineering = Department(name='Engineering') + engineering = Department(name="Engineering") db_session.add(engineering) - hr = Department(name='Human Resources') + hr = Department(name="Human Resources") db_session.add(hr) - manager = Role(name='manager') + manager = Role(name="manager") db_session.add(manager) - engineer = Role(name='engineer') + engineer = Role(name="engineer") db_session.add(engineer) - peter = Employee(name='Peter', department=engineering, role=engineer) + peter = Employee(name="Peter", department=engineering, role=engineer) db_session.add(peter) - roy = Employee(name='Roy', department=engineering, role=engineer) + roy = Employee(name="Roy", department=engineering, role=engineer) db_session.add(roy) - tracy = Employee(name='Tracy', department=hr, role=manager) + tracy = Employee(name="Tracy", department=hr, role=manager) db_session.add(tracy) db_session.commit() diff --git a/examples/flask_sqlalchemy/models.py b/examples/flask_sqlalchemy/models.py index efbbe690..38f0fd0a 100644 --- a/examples/flask_sqlalchemy/models.py +++ b/examples/flask_sqlalchemy/models.py @@ -4,35 +4,31 @@ class Department(Base): - __tablename__ = 'department' + __tablename__ = "department" id = Column(Integer, primary_key=True) name = Column(String) class Role(Base): - __tablename__ = 'roles' + __tablename__ = "roles" role_id = Column(Integer, primary_key=True) name = Column(String) class Employee(Base): - __tablename__ = 'employee' + __tablename__ = "employee" id = Column(Integer, primary_key=True) name = Column(String) # Use default=func.now() to set the default hiring time # of an Employee to be the current time when an # Employee record was created hired_on = Column(DateTime, default=func.now()) - department_id = Column(Integer, ForeignKey('department.id')) - role_id = Column(Integer, ForeignKey('roles.role_id')) + department_id = Column(Integer, ForeignKey("department.id")) + role_id = Column(Integer, ForeignKey("roles.role_id")) # Use cascade='delete,all' to propagate the deletion of a Department onto its Employees department = relationship( - Department, - backref=backref('employees', - uselist=True, - cascade='delete,all')) + Department, backref=backref("employees", uselist=True, cascade="delete,all") + ) role = relationship( - Role, - backref=backref('roles', - uselist=True, - cascade='delete,all')) + Role, backref=backref("roles", uselist=True, cascade="delete,all") + ) diff --git a/examples/flask_sqlalchemy/schema.py b/examples/flask_sqlalchemy/schema.py index ea525e3b..c4a91e63 100644 --- a/examples/flask_sqlalchemy/schema.py +++ b/examples/flask_sqlalchemy/schema.py @@ -10,26 +10,27 @@ class Department(SQLAlchemyObjectType): class Meta: model = DepartmentModel - interfaces = (relay.Node, ) + interfaces = (relay.Node,) class Employee(SQLAlchemyObjectType): class Meta: model = EmployeeModel - interfaces = (relay.Node, ) + interfaces = (relay.Node,) class Role(SQLAlchemyObjectType): class Meta: model = RoleModel - interfaces = (relay.Node, ) + interfaces = (relay.Node,) class Query(graphene.ObjectType): node = relay.Node.Field() # Allow only single column sorting all_employees = SQLAlchemyConnectionField( - Employee.connection, sort=Employee.sort_argument()) + Employee.connection, sort=Employee.sort_argument() + ) # Allows sorting over multiple columns, by default over the primary key all_roles = SQLAlchemyConnectionField(Role.connection) # Disable sorting over this field diff --git a/examples/nameko_sqlalchemy/app.py b/examples/nameko_sqlalchemy/app.py index 05352529..64d305ea 100755 --- a/examples/nameko_sqlalchemy/app.py +++ b/examples/nameko_sqlalchemy/app.py @@ -1,37 +1,45 @@ from database import db_session, init_db from schema import schema -from graphql_server import (HttpQueryError, default_format_error, - encode_execution_results, json_encode, - load_json_body, run_http_query) - - -class App(): - def __init__(self): - init_db() - - def query(self, request): - data = self.parse_body(request) - execution_results, params = run_http_query( - schema, - 'post', - data) - result, status_code = encode_execution_results( - execution_results, - format_error=default_format_error,is_batch=False, encode=json_encode) - return result - - def parse_body(self,request): - # We use mimetype here since we don't need the other - # information provided by content_type - content_type = request.mimetype - if content_type == 'application/graphql': - return {'query': request.data.decode('utf8')} - - elif content_type == 'application/json': - return load_json_body(request.data.decode('utf8')) - - elif content_type in ('application/x-www-form-urlencoded', 'multipart/form-data'): - return request.form - - return {} +from graphql_server import ( + HttpQueryError, + default_format_error, + encode_execution_results, + json_encode, + load_json_body, + run_http_query, +) + + +class App: + def __init__(self): + init_db() + + def query(self, request): + data = self.parse_body(request) + execution_results, params = run_http_query(schema, "post", data) + result, status_code = encode_execution_results( + execution_results, + format_error=default_format_error, + is_batch=False, + encode=json_encode, + ) + return result + + def parse_body(self, request): + # We use mimetype here since we don't need the other + # information provided by content_type + content_type = request.mimetype + if content_type == "application/graphql": + return {"query": request.data.decode("utf8")} + + elif content_type == "application/json": + return load_json_body(request.data.decode("utf8")) + + elif content_type in ( + "application/x-www-form-urlencoded", + "multipart/form-data", + ): + return request.form + + return {} diff --git a/examples/nameko_sqlalchemy/database.py b/examples/nameko_sqlalchemy/database.py index ca4d4122..74ec7ca9 100644 --- a/examples/nameko_sqlalchemy/database.py +++ b/examples/nameko_sqlalchemy/database.py @@ -2,10 +2,10 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker -engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True) -db_session = scoped_session(sessionmaker(autocommit=False, - autoflush=False, - bind=engine)) +engine = create_engine("sqlite:///database.sqlite3", convert_unicode=True) +db_session = scoped_session( + sessionmaker(autocommit=False, autoflush=False, bind=engine) +) Base = declarative_base() Base.query = db_session.query_property() @@ -15,24 +15,25 @@ def init_db(): # they will be registered properly on the metadata. Otherwise # you will have to import them first before calling init_db() from models import Department, Employee, Role + Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) # Create the fixtures - engineering = Department(name='Engineering') + engineering = Department(name="Engineering") db_session.add(engineering) - hr = Department(name='Human Resources') + hr = Department(name="Human Resources") db_session.add(hr) - manager = Role(name='manager') + manager = Role(name="manager") db_session.add(manager) - engineer = Role(name='engineer') + engineer = Role(name="engineer") db_session.add(engineer) - peter = Employee(name='Peter', department=engineering, role=engineer) + peter = Employee(name="Peter", department=engineering, role=engineer) db_session.add(peter) - roy = Employee(name='Roy', department=engineering, role=engineer) + roy = Employee(name="Roy", department=engineering, role=engineer) db_session.add(roy) - tracy = Employee(name='Tracy', department=hr, role=manager) + tracy = Employee(name="Tracy", department=hr, role=manager) db_session.add(tracy) db_session.commit() diff --git a/examples/nameko_sqlalchemy/models.py b/examples/nameko_sqlalchemy/models.py index efbbe690..38f0fd0a 100644 --- a/examples/nameko_sqlalchemy/models.py +++ b/examples/nameko_sqlalchemy/models.py @@ -4,35 +4,31 @@ class Department(Base): - __tablename__ = 'department' + __tablename__ = "department" id = Column(Integer, primary_key=True) name = Column(String) class Role(Base): - __tablename__ = 'roles' + __tablename__ = "roles" role_id = Column(Integer, primary_key=True) name = Column(String) class Employee(Base): - __tablename__ = 'employee' + __tablename__ = "employee" id = Column(Integer, primary_key=True) name = Column(String) # Use default=func.now() to set the default hiring time # of an Employee to be the current time when an # Employee record was created hired_on = Column(DateTime, default=func.now()) - department_id = Column(Integer, ForeignKey('department.id')) - role_id = Column(Integer, ForeignKey('roles.role_id')) + department_id = Column(Integer, ForeignKey("department.id")) + role_id = Column(Integer, ForeignKey("roles.role_id")) # Use cascade='delete,all' to propagate the deletion of a Department onto its Employees department = relationship( - Department, - backref=backref('employees', - uselist=True, - cascade='delete,all')) + Department, backref=backref("employees", uselist=True, cascade="delete,all") + ) role = relationship( - Role, - backref=backref('roles', - uselist=True, - cascade='delete,all')) + Role, backref=backref("roles", uselist=True, cascade="delete,all") + ) diff --git a/examples/nameko_sqlalchemy/service.py b/examples/nameko_sqlalchemy/service.py index d9c519c9..7f4c5078 100644 --- a/examples/nameko_sqlalchemy/service.py +++ b/examples/nameko_sqlalchemy/service.py @@ -4,8 +4,8 @@ class DepartmentService: - name = 'department' + name = "department" - @http('POST', '/graphql') + @http("POST", "/graphql") def query(self, request): return App().query(request) diff --git a/graphene_sqlalchemy/batching.py b/graphene_sqlalchemy/batching.py index 85cc8855..f4a80e20 100644 --- a/graphene_sqlalchemy/batching.py +++ b/graphene_sqlalchemy/batching.py @@ -10,7 +10,9 @@ def get_batch_resolver(relationship_prop): # Cache this across `batch_load_fn` calls # This is so SQL string generation is cached under-the-hood via `bakery` - selectin_loader = strategies.SelectInLoader(relationship_prop, (('lazy', 'selectin'),)) + selectin_loader = strategies.SelectInLoader( + relationship_prop, (("lazy", "selectin"),) + ) class RelationshipLoader(aiodataloader.DataLoader): cache = False @@ -55,19 +57,19 @@ async def batch_load_fn(self, parents): # For our purposes, the query_context will only used to get the session query_context = None - if is_sqlalchemy_version_less_than('1.4'): + if is_sqlalchemy_version_less_than("1.4"): query_context = QueryContext(session.query(parent_mapper.entity)) else: parent_mapper_query = session.query(parent_mapper.entity) query_context = parent_mapper_query._compile_context() - if is_sqlalchemy_version_less_than('1.4'): + if is_sqlalchemy_version_less_than("1.4"): selectin_loader._load_for_path( query_context, parent_mapper._path_registry, states, None, - child_mapper + child_mapper, ) else: selectin_loader._load_for_path( @@ -76,7 +78,7 @@ async def batch_load_fn(self, parents): states, None, child_mapper, - None + None, ) return [getattr(parent, relationship_prop.key) for parent in parents] diff --git a/graphene_sqlalchemy/converter.py b/graphene_sqlalchemy/converter.py index 60e14ddd..26fb0d58 100644 --- a/graphene_sqlalchemy/converter.py +++ b/graphene_sqlalchemy/converter.py @@ -9,18 +9,33 @@ from sqlalchemy.dialects import postgresql from sqlalchemy.orm import interfaces, strategies -from graphene import (ID, Boolean, Date, DateTime, Dynamic, Enum, Field, Float, - Int, List, String, Time) +from graphene import ( + ID, + Boolean, + Date, + DateTime, + Dynamic, + Enum, + Field, + Float, + Int, + List, + String, + Time, +) from graphene.types.json import JSONString from .batching import get_batch_resolver from .enums import enum_for_sa_enum -from .fields import (BatchSQLAlchemyConnectionField, - default_connection_field_factory) +from .fields import BatchSQLAlchemyConnectionField, default_connection_field_factory from .registry import get_global_registry from .resolvers import get_attr_resolver, get_custom_resolver -from .utils import (registry_sqlalchemy_model_from_str, safe_isinstance, - singledispatchbymatchfunction, value_equals) +from .utils import ( + registry_sqlalchemy_model_from_str, + safe_isinstance, + singledispatchbymatchfunction, + value_equals, +) try: from typing import ForwardRef @@ -29,8 +44,7 @@ from typing import _ForwardRef as ForwardRef try: - from sqlalchemy_utils import (ChoiceType, JSONType, ScalarListType, - TSVectorType) + from sqlalchemy_utils import ChoiceType, JSONType, ScalarListType, TSVectorType except ImportError: ChoiceType = JSONType = ScalarListType = TSVectorType = object @@ -39,7 +53,7 @@ except ImportError: EnumTypeImpl = object -is_selectin_available = getattr(strategies, 'SelectInLoader', None) +is_selectin_available = getattr(strategies, "SelectInLoader", None) def get_column_doc(column): @@ -50,8 +64,14 @@ def is_column_nullable(column): return bool(getattr(column, "nullable", True)) -def convert_sqlalchemy_relationship(relationship_prop, obj_type, connection_field_factory, batching, - orm_field_name, **field_kwargs): +def convert_sqlalchemy_relationship( + relationship_prop, + obj_type, + connection_field_factory, + batching, + orm_field_name, + **field_kwargs, +): """ :param sqlalchemy.RelationshipProperty relationship_prop: :param SQLAlchemyObjectType obj_type: @@ -65,24 +85,34 @@ def convert_sqlalchemy_relationship(relationship_prop, obj_type, connection_fiel def dynamic_type(): """:rtype: Field|None""" direction = relationship_prop.direction - child_type = obj_type._meta.registry.get_type_for_model(relationship_prop.mapper.entity) + child_type = obj_type._meta.registry.get_type_for_model( + relationship_prop.mapper.entity + ) batching_ = batching if is_selectin_available else False if not child_type: return None if direction == interfaces.MANYTOONE or not relationship_prop.uselist: - return _convert_o2o_or_m2o_relationship(relationship_prop, obj_type, batching_, orm_field_name, - **field_kwargs) + return _convert_o2o_or_m2o_relationship( + relationship_prop, obj_type, batching_, orm_field_name, **field_kwargs + ) if direction in (interfaces.ONETOMANY, interfaces.MANYTOMANY): - return _convert_o2m_or_m2m_relationship(relationship_prop, obj_type, batching_, - connection_field_factory, **field_kwargs) + return _convert_o2m_or_m2m_relationship( + relationship_prop, + obj_type, + batching_, + connection_field_factory, + **field_kwargs, + ) return Dynamic(dynamic_type) -def _convert_o2o_or_m2o_relationship(relationship_prop, obj_type, batching, orm_field_name, **field_kwargs): +def _convert_o2o_or_m2o_relationship( + relationship_prop, obj_type, batching, orm_field_name, **field_kwargs +): """ Convert one-to-one or many-to-one relationshsip. Return an object field. @@ -93,17 +123,24 @@ def _convert_o2o_or_m2o_relationship(relationship_prop, obj_type, batching, orm_ :param dict field_kwargs: :rtype: Field """ - child_type = obj_type._meta.registry.get_type_for_model(relationship_prop.mapper.entity) + child_type = obj_type._meta.registry.get_type_for_model( + relationship_prop.mapper.entity + ) resolver = get_custom_resolver(obj_type, orm_field_name) if resolver is None: - resolver = get_batch_resolver(relationship_prop) if batching else \ - get_attr_resolver(obj_type, relationship_prop.key) + resolver = ( + get_batch_resolver(relationship_prop) + if batching + else get_attr_resolver(obj_type, relationship_prop.key) + ) return Field(child_type, resolver=resolver, **field_kwargs) -def _convert_o2m_or_m2m_relationship(relationship_prop, obj_type, batching, connection_field_factory, **field_kwargs): +def _convert_o2m_or_m2m_relationship( + relationship_prop, obj_type, batching, connection_field_factory, **field_kwargs +): """ Convert one-to-many or many-to-many relationshsip. Return a list field or a connection field. @@ -114,30 +151,34 @@ def _convert_o2m_or_m2m_relationship(relationship_prop, obj_type, batching, conn :param dict field_kwargs: :rtype: Field """ - child_type = obj_type._meta.registry.get_type_for_model(relationship_prop.mapper.entity) + child_type = obj_type._meta.registry.get_type_for_model( + relationship_prop.mapper.entity + ) if not child_type._meta.connection: return Field(List(child_type), **field_kwargs) # TODO Allow override of connection_field_factory and resolver via ORMField if connection_field_factory is None: - connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship if batching else \ - default_connection_field_factory - - return connection_field_factory(relationship_prop, obj_type._meta.registry, **field_kwargs) + connection_field_factory = ( + BatchSQLAlchemyConnectionField.from_relationship + if batching + else default_connection_field_factory + ) + + return connection_field_factory( + relationship_prop, obj_type._meta.registry, **field_kwargs + ) def convert_sqlalchemy_hybrid_method(hybrid_prop, resolver, **field_kwargs): - if 'type_' not in field_kwargs: - field_kwargs['type_'] = convert_hybrid_property_return_type(hybrid_prop) + if "type_" not in field_kwargs: + field_kwargs["type_"] = convert_hybrid_property_return_type(hybrid_prop) - if 'description' not in field_kwargs: - field_kwargs['description'] = getattr(hybrid_prop, "__doc__", None) + if "description" not in field_kwargs: + field_kwargs["description"] = getattr(hybrid_prop, "__doc__", None) - return Field( - resolver=resolver, - **field_kwargs - ) + return Field(resolver=resolver, **field_kwargs) def convert_sqlalchemy_composite(composite_prop, registry, resolver): @@ -177,14 +218,14 @@ def inner(fn): def convert_sqlalchemy_column(column_prop, registry, resolver, **field_kwargs): column = column_prop.columns[0] - field_kwargs.setdefault('type_', convert_sqlalchemy_type(getattr(column, "type", None), column, registry)) - field_kwargs.setdefault('required', not is_column_nullable(column)) - field_kwargs.setdefault('description', get_column_doc(column)) - - return Field( - resolver=resolver, - **field_kwargs + field_kwargs.setdefault( + "type_", + convert_sqlalchemy_type(getattr(column, "type", None), column, registry), ) + field_kwargs.setdefault("required", not is_column_nullable(column)) + field_kwargs.setdefault("description", get_column_doc(column)) + + return Field(resolver=resolver, **field_kwargs) @singledispatch @@ -212,6 +253,7 @@ def convert_column_to_string(type, column, registry=None): @convert_sqlalchemy_type.register(types.DateTime) def convert_column_to_datetime(type, column, registry=None): from graphene.types.datetime import DateTime + return DateTime @@ -263,7 +305,9 @@ def init_array_list_recursive(inner_type, n): @convert_sqlalchemy_type.register(postgresql.ARRAY) def convert_array_to_list(_type, column, registry=None): inner_type = convert_sqlalchemy_type(column.type.item_type, column) - return List(init_array_list_recursive(inner_type, (column.type.dimensions or 1) - 1)) + return List( + init_array_list_recursive(inner_type, (column.type.dimensions or 1) - 1) + ) @convert_sqlalchemy_type.register(postgresql.HSTORE) @@ -286,8 +330,8 @@ def convert_sqlalchemy_hybrid_property_type(arg: Any): # No valid type found, warn and fall back to graphene.String warnings.warn( - (f"I don't know how to generate a GraphQL type out of a \"{arg}\" type." - "Falling back to \"graphene.String\"") + f'I don\'t know how to generate a GraphQL type out of a "{arg}" type.' + 'Falling back to "graphene.String"' ) return String @@ -335,7 +379,9 @@ def convert_sqlalchemy_hybrid_property_type_time(arg): return Time -@convert_sqlalchemy_hybrid_property_type.register(lambda x: getattr(x, '__origin__', None) == typing.Union) +@convert_sqlalchemy_hybrid_property_type.register( + lambda x: getattr(x, "__origin__", None) == typing.Union +) def convert_sqlalchemy_hybrid_property_type_option_t(arg): # Option is actually Union[T, ] @@ -347,7 +393,9 @@ def convert_sqlalchemy_hybrid_property_type_option_t(arg): return graphql_internal_type -@convert_sqlalchemy_hybrid_property_type.register(lambda x: getattr(x, '__origin__', None) in [list, typing.List]) +@convert_sqlalchemy_hybrid_property_type.register( + lambda x: getattr(x, "__origin__", None) in [list, typing.List] +) def convert_sqlalchemy_hybrid_property_type_list_t(arg): # type is either list[T] or List[T], generic argument at __args__[0] internal_type = arg.__args__[0] @@ -385,6 +433,6 @@ def convert_sqlalchemy_hybrid_property_bare_str(arg): def convert_hybrid_property_return_type(hybrid_prop): # Grab the original method's return type annotations from inside the hybrid property - return_type_annotation = hybrid_prop.fget.__annotations__.get('return', str) + return_type_annotation = hybrid_prop.fget.__annotations__.get("return", str) return convert_sqlalchemy_hybrid_property_type(return_type_annotation) diff --git a/graphene_sqlalchemy/enums.py b/graphene_sqlalchemy/enums.py index f100be19..7e91a055 100644 --- a/graphene_sqlalchemy/enums.py +++ b/graphene_sqlalchemy/enums.py @@ -18,9 +18,7 @@ def _convert_sa_to_graphene_enum(sa_enum, fallback_name=None): The Enum value names are converted to upper case if necessary. """ if not isinstance(sa_enum, SQLAlchemyEnumType): - raise TypeError( - "Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum) - ) + raise TypeError("Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum)) enum_class = sa_enum.enum_class if enum_class: if all(to_enum_value_name(key) == key for key in enum_class.__members__): @@ -45,9 +43,7 @@ def _convert_sa_to_graphene_enum(sa_enum, fallback_name=None): def enum_for_sa_enum(sa_enum, registry): """Return the Graphene Enum type for the specified SQLAlchemy Enum type.""" if not isinstance(sa_enum, SQLAlchemyEnumType): - raise TypeError( - "Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum) - ) + raise TypeError("Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum)) enum = registry.get_graphene_enum_for_sa_enum(sa_enum) if not enum: enum = _convert_sa_to_graphene_enum(sa_enum) @@ -60,11 +56,9 @@ def enum_for_field(obj_type, field_name): from .types import SQLAlchemyObjectType if not isinstance(obj_type, type) or not issubclass(obj_type, SQLAlchemyObjectType): - raise TypeError( - "Expected SQLAlchemyObjectType, but got: {!r}".format(obj_type)) + raise TypeError("Expected SQLAlchemyObjectType, but got: {!r}".format(obj_type)) if not field_name or not isinstance(field_name, str): - raise TypeError( - "Expected a field name, but got: {!r}".format(field_name)) + raise TypeError("Expected a field name, but got: {!r}".format(field_name)) registry = obj_type._meta.registry orm_field = registry.get_orm_field_for_graphene_field(obj_type, field_name) if orm_field is None: @@ -166,7 +160,7 @@ def sort_argument_for_object_type( get_symbol_name=None, has_default=True, ): - """"Returns Graphene Argument for sorting the given SQLAlchemyObjectType. + """ "Returns Graphene Argument for sorting the given SQLAlchemyObjectType. Parameters - obj_type : SQLAlchemyObjectType diff --git a/graphene_sqlalchemy/fields.py b/graphene_sqlalchemy/fields.py index d7a83392..6e4a1524 100644 --- a/graphene_sqlalchemy/fields.py +++ b/graphene_sqlalchemy/fields.py @@ -26,9 +26,7 @@ def type(self): assert issubclass(nullable_type, SQLAlchemyObjectType), ( "SQLALchemyConnectionField only accepts SQLAlchemyObjectType types, not {}" ).format(nullable_type.__name__) - assert ( - nullable_type.connection - ), "The type {} doesn't have a connection".format( + assert nullable_type.connection, "The type {} doesn't have a connection".format( nullable_type.__name__ ) assert type_ == nullable_type, ( @@ -148,7 +146,11 @@ def wrap_resolve(self, parent_resolver): def from_relationship(cls, relationship, registry, **field_kwargs): model = relationship.mapper.entity model_type = registry.get_type_for_model(model) - return cls(model_type.connection, resolver=get_batch_resolver(relationship), **field_kwargs) + return cls( + model_type.connection, + resolver=get_batch_resolver(relationship), + **field_kwargs + ) def default_connection_field_factory(relationship, registry, **field_kwargs): @@ -163,8 +165,8 @@ def default_connection_field_factory(relationship, registry, **field_kwargs): def createConnectionField(type_, **field_kwargs): warnings.warn( - 'createConnectionField is deprecated and will be removed in the next ' - 'major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.', + "createConnectionField is deprecated and will be removed in the next " + "major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.", DeprecationWarning, ) return __connectionFactory(type_, **field_kwargs) @@ -172,8 +174,8 @@ def createConnectionField(type_, **field_kwargs): def registerConnectionFieldFactory(factoryMethod): warnings.warn( - 'registerConnectionFieldFactory is deprecated and will be removed in the next ' - 'major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.', + "registerConnectionFieldFactory is deprecated and will be removed in the next " + "major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.", DeprecationWarning, ) global __connectionFactory @@ -182,8 +184,8 @@ def registerConnectionFieldFactory(factoryMethod): def unregisterConnectionFieldFactory(): warnings.warn( - 'registerConnectionFieldFactory is deprecated and will be removed in the next ' - 'major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.', + "registerConnectionFieldFactory is deprecated and will be removed in the next " + "major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.", DeprecationWarning, ) global __connectionFactory diff --git a/graphene_sqlalchemy/resolvers.py b/graphene_sqlalchemy/resolvers.py index 83a6e35d..e8e61911 100644 --- a/graphene_sqlalchemy/resolvers.py +++ b/graphene_sqlalchemy/resolvers.py @@ -7,7 +7,7 @@ def get_custom_resolver(obj_type, orm_field_name): does not have a `resolver`, we need to re-implement that logic here so users are able to override the default resolvers that we provide. """ - resolver = getattr(obj_type, 'resolve_{}'.format(orm_field_name), None) + resolver = getattr(obj_type, "resolve_{}".format(orm_field_name), None) if resolver: return get_unbound_function(resolver) diff --git a/graphene_sqlalchemy/tests/conftest.py b/graphene_sqlalchemy/tests/conftest.py index 34ba9d8a..357ad96e 100644 --- a/graphene_sqlalchemy/tests/conftest.py +++ b/graphene_sqlalchemy/tests/conftest.py @@ -8,7 +8,7 @@ from ..registry import reset_global_registry from .models import Base, CompositeFullName -test_db_url = 'sqlite://' # use in-memory database for tests +test_db_url = "sqlite://" # use in-memory database for tests @pytest.fixture(autouse=True) diff --git a/graphene_sqlalchemy/tests/models.py b/graphene_sqlalchemy/tests/models.py index e41adb51..92d87c84 100644 --- a/graphene_sqlalchemy/tests/models.py +++ b/graphene_sqlalchemy/tests/models.py @@ -5,8 +5,17 @@ from decimal import Decimal from typing import List, Optional, Tuple -from sqlalchemy import (Column, Date, Enum, ForeignKey, Integer, String, Table, - func, select) +from sqlalchemy import ( + Column, + Date, + Enum, + ForeignKey, + Integer, + String, + Table, + func, + select, +) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import column_property, composite, mapper, relationship @@ -15,8 +24,8 @@ class HairKind(enum.Enum): - LONG = 'long' - SHORT = 'short' + LONG = "long" + SHORT = "short" Base = declarative_base() @@ -64,7 +73,9 @@ class Reporter(Base): last_name = Column(String(30), doc="Last name") email = Column(String(), doc="Email") favorite_pet_kind = Column(PetKind) - pets = relationship("Pet", secondary=association_table, backref="reporters", order_by="Pet.id") + pets = relationship( + "Pet", secondary=association_table, backref="reporters", order_by="Pet.id" + ) articles = relationship("Article", backref="reporter") favorite_article = relationship("Article", uselist=False) @@ -101,7 +112,9 @@ def hybrid_prop_list(self) -> List[int]: select([func.cast(func.count(id), Integer)]), doc="Column property" ) - composite_prop = composite(CompositeFullName, first_name, last_name, doc="Composite") + composite_prop = composite( + CompositeFullName, first_name, last_name, doc="Composite" + ) class Article(Base): @@ -137,7 +150,7 @@ class ShoppingCartItem(Base): id = Column(Integer(), primary_key=True) @hybrid_property - def hybrid_prop_shopping_cart(self) -> List['ShoppingCart']: + def hybrid_prop_shopping_cart(self) -> List["ShoppingCart"]: return [ShoppingCart(id=1)] @@ -192,11 +205,17 @@ def hybrid_prop_list_date(self) -> List[datetime.date]: @hybrid_property def hybrid_prop_nested_list_int(self) -> List[List[int]]: - return [self.hybrid_prop_list_int, ] + return [ + self.hybrid_prop_list_int, + ] @hybrid_property def hybrid_prop_deeply_nested_list_int(self) -> List[List[List[int]]]: - return [[self.hybrid_prop_list_int, ], ] + return [ + [ + self.hybrid_prop_list_int, + ], + ] # Other SQLAlchemy Instances @hybrid_property @@ -216,15 +235,15 @@ def hybrid_prop_unsupported_type_tuple(self) -> Tuple[str, str]: # Self-references @hybrid_property - def hybrid_prop_self_referential(self) -> 'ShoppingCart': + def hybrid_prop_self_referential(self) -> "ShoppingCart": return ShoppingCart(id=1) @hybrid_property - def hybrid_prop_self_referential_list(self) -> List['ShoppingCart']: + def hybrid_prop_self_referential_list(self) -> List["ShoppingCart"]: return [ShoppingCart(id=1)] # Optional[T] @hybrid_property - def hybrid_prop_optional_self_referential(self) -> Optional['ShoppingCart']: + def hybrid_prop_optional_self_referential(self) -> Optional["ShoppingCart"]: return None diff --git a/graphene_sqlalchemy/tests/test_batching.py b/graphene_sqlalchemy/tests/test_batching.py index 1896900b..d942e3fc 100644 --- a/graphene_sqlalchemy/tests/test_batching.py +++ b/graphene_sqlalchemy/tests/test_batching.py @@ -7,8 +7,7 @@ import graphene from graphene import relay -from ..fields import (BatchSQLAlchemyConnectionField, - default_connection_field_factory) +from ..fields import BatchSQLAlchemyConnectionField, default_connection_field_factory from ..types import ORMField, SQLAlchemyObjectType from ..utils import is_sqlalchemy_version_less_than from .models import Article, HairKind, Pet, Reporter @@ -17,6 +16,7 @@ class MockLoggingHandler(logging.Handler): """Intercept and store log messages in a list.""" + def __init__(self, *args, **kwargs): self.messages = [] logging.Handler.__init__(self, *args, **kwargs) @@ -28,7 +28,7 @@ def emit(self, record): @contextlib.contextmanager def mock_sqlalchemy_logging_handler(): logging.basicConfig() - sql_logger = logging.getLogger('sqlalchemy.engine') + sql_logger = logging.getLogger("sqlalchemy.engine") previous_level = sql_logger.level sql_logger.setLevel(logging.INFO) @@ -65,16 +65,16 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_articles(self, info): - return info.context.get('session').query(Article).all() + return info.context.get("session").query(Article).all() def resolve_reporters(self, info): - return info.context.get('session').query(Reporter).all() + return info.context.get("session").query(Reporter).all() return graphene.Schema(query=Query) -if is_sqlalchemy_version_less_than('1.2'): - pytest.skip('SQL batching only works for SQLAlchemy 1.2+', allow_module_level=True) +if is_sqlalchemy_version_less_than("1.2"): + pytest.skip("SQL batching only works for SQLAlchemy 1.2+", allow_module_level=True) @pytest.mark.asyncio @@ -82,19 +82,19 @@ async def test_many_to_one(session_factory): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - article_1 = Article(headline='Article_1') + article_1 = Article(headline="Article_1") article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline='Article_2') + article_2 = Article(headline="Article_2") article_2.reporter = reporter_2 session.add(article_2) @@ -106,7 +106,8 @@ async def test_many_to_one(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async(""" + result = await schema.execute_async( + """ query { articles { headline @@ -115,20 +116,26 @@ async def test_many_to_one(session_factory): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than('1.3'): + if is_sqlalchemy_version_less_than("1.3"): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN reporters' in message] + sql_statements = [ + message + for message in messages + if "SELECT" in message and "JOIN reporters" in message + ] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than('1.4'): + if not is_sqlalchemy_version_less_than("1.4"): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -138,20 +145,20 @@ async def test_many_to_one(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "articles": [ - { - "headline": "Article_1", - "reporter": { - "firstName": "Reporter_1", - }, - }, - { - "headline": "Article_2", - "reporter": { - "firstName": "Reporter_2", - }, - }, - ], + "articles": [ + { + "headline": "Article_1", + "reporter": { + "firstName": "Reporter_1", + }, + }, + { + "headline": "Article_2", + "reporter": { + "firstName": "Reporter_2", + }, + }, + ], } @@ -160,19 +167,19 @@ async def test_one_to_one(session_factory): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - article_1 = Article(headline='Article_1') + article_1 = Article(headline="Article_1") article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline='Article_2') + article_2 = Article(headline="Article_2") article_2.reporter = reporter_2 session.add(article_2) @@ -184,7 +191,8 @@ async def test_one_to_one(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async(""" + result = await schema.execute_async( + """ query { reporters { firstName @@ -193,20 +201,26 @@ async def test_one_to_one(session_factory): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than('1.3'): + if is_sqlalchemy_version_less_than("1.3"): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN articles' in message] + sql_statements = [ + message + for message in messages + if "SELECT" in message and "JOIN articles" in message + ] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than('1.4'): + if not is_sqlalchemy_version_less_than("1.4"): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -216,20 +230,20 @@ async def test_one_to_one(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "reporters": [ - { - "firstName": "Reporter_1", - "favoriteArticle": { - "headline": "Article_1", - }, - }, - { - "firstName": "Reporter_2", - "favoriteArticle": { - "headline": "Article_2", - }, - }, - ], + "reporters": [ + { + "firstName": "Reporter_1", + "favoriteArticle": { + "headline": "Article_1", + }, + }, + { + "firstName": "Reporter_2", + "favoriteArticle": { + "headline": "Article_2", + }, + }, + ], } @@ -238,27 +252,27 @@ async def test_one_to_many(session_factory): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - article_1 = Article(headline='Article_1') + article_1 = Article(headline="Article_1") article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline='Article_2') + article_2 = Article(headline="Article_2") article_2.reporter = reporter_1 session.add(article_2) - article_3 = Article(headline='Article_3') + article_3 = Article(headline="Article_3") article_3.reporter = reporter_2 session.add(article_3) - article_4 = Article(headline='Article_4') + article_4 = Article(headline="Article_4") article_4.reporter = reporter_2 session.add(article_4) @@ -270,7 +284,8 @@ async def test_one_to_many(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async(""" + result = await schema.execute_async( + """ query { reporters { firstName @@ -283,20 +298,26 @@ async def test_one_to_many(session_factory): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than('1.3'): + if is_sqlalchemy_version_less_than("1.3"): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN articles' in message] + sql_statements = [ + message + for message in messages + if "SELECT" in message and "JOIN articles" in message + ] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than('1.4'): + if not is_sqlalchemy_version_less_than("1.4"): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -306,42 +327,42 @@ async def test_one_to_many(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "reporters": [ - { - "firstName": "Reporter_1", - "articles": { - "edges": [ - { - "node": { - "headline": "Article_1", + "reporters": [ + { + "firstName": "Reporter_1", + "articles": { + "edges": [ + { + "node": { + "headline": "Article_1", + }, + }, + { + "node": { + "headline": "Article_2", + }, + }, + ], }, - }, - { - "node": { - "headline": "Article_2", + }, + { + "firstName": "Reporter_2", + "articles": { + "edges": [ + { + "node": { + "headline": "Article_3", + }, + }, + { + "node": { + "headline": "Article_4", + }, + }, + ], }, - }, - ], - }, - }, - { - "firstName": "Reporter_2", - "articles": { - "edges": [ - { - "node": { - "headline": "Article_3", - }, - }, - { - "node": { - "headline": "Article_4", - }, - }, - ], - }, - }, - ], + }, + ], } @@ -350,27 +371,27 @@ async def test_many_to_many(session_factory): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - pet_1 = Pet(name='Pet_1', pet_kind='cat', hair_kind=HairKind.LONG) + pet_1 = Pet(name="Pet_1", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_1) - pet_2 = Pet(name='Pet_2', pet_kind='cat', hair_kind=HairKind.LONG) + pet_2 = Pet(name="Pet_2", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_2) reporter_1.pets.append(pet_1) reporter_1.pets.append(pet_2) - pet_3 = Pet(name='Pet_3', pet_kind='cat', hair_kind=HairKind.LONG) + pet_3 = Pet(name="Pet_3", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_3) - pet_4 = Pet(name='Pet_4', pet_kind='cat', hair_kind=HairKind.LONG) + pet_4 = Pet(name="Pet_4", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_4) reporter_2.pets.append(pet_3) @@ -384,7 +405,8 @@ async def test_many_to_many(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async(""" + result = await schema.execute_async( + """ query { reporters { firstName @@ -397,20 +419,26 @@ async def test_many_to_many(session_factory): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than('1.3'): + if is_sqlalchemy_version_less_than("1.3"): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN pets' in message] + sql_statements = [ + message + for message in messages + if "SELECT" in message and "JOIN pets" in message + ] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than('1.4'): + if not is_sqlalchemy_version_less_than("1.4"): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -420,50 +448,50 @@ async def test_many_to_many(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "reporters": [ - { - "firstName": "Reporter_1", - "pets": { - "edges": [ - { - "node": { - "name": "Pet_1", - }, - }, - { - "node": { - "name": "Pet_2", - }, - }, - ], - }, - }, - { - "firstName": "Reporter_2", - "pets": { - "edges": [ - { - "node": { - "name": "Pet_3", + "reporters": [ + { + "firstName": "Reporter_1", + "pets": { + "edges": [ + { + "node": { + "name": "Pet_1", + }, + }, + { + "node": { + "name": "Pet_2", + }, + }, + ], }, - }, - { - "node": { - "name": "Pet_4", + }, + { + "firstName": "Reporter_2", + "pets": { + "edges": [ + { + "node": { + "name": "Pet_3", + }, + }, + { + "node": { + "name": "Pet_4", + }, + }, + ], }, - }, - ], - }, - }, - ], + }, + ], } def test_disable_batching_via_ormfield(session_factory): session = session_factory() - reporter_1 = Reporter(first_name='Reporter_1') + reporter_1 = Reporter(first_name="Reporter_1") session.add(reporter_1) - reporter_2 = Reporter(first_name='Reporter_2') + reporter_2 = Reporter(first_name="Reporter_2") session.add(reporter_2) session.commit() session.close() @@ -486,7 +514,7 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_reporters(self, info): - return info.context.get('session').query(Reporter).all() + return info.context.get("session").query(Reporter).all() schema = graphene.Schema(query=Query) @@ -494,7 +522,8 @@ def resolve_reporters(self, info): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - schema.execute(""" + schema.execute( + """ query { reporters { favoriteArticle { @@ -502,17 +531,24 @@ def resolve_reporters(self, info): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages - select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] + select_statements = [ + message + for message in messages + if "SELECT" in message and "FROM articles" in message + ] assert len(select_statements) == 2 # Test one-to-many and many-to-many relationships with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - schema.execute(""" + schema.execute( + """ query { reporters { articles { @@ -524,19 +560,25 @@ def resolve_reporters(self, info): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages - select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] + select_statements = [ + message + for message in messages + if "SELECT" in message and "FROM articles" in message + ] assert len(select_statements) == 2 @pytest.mark.asyncio async def test_connection_factory_field_overrides_batching_is_false(session_factory): session = session_factory() - reporter_1 = Reporter(first_name='Reporter_1') + reporter_1 = Reporter(first_name="Reporter_1") session.add(reporter_1) - reporter_2 = Reporter(first_name='Reporter_2') + reporter_2 = Reporter(first_name="Reporter_2") session.add(reporter_2) session.commit() session.close() @@ -559,14 +601,15 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_reporters(self, info): - return info.context.get('session').query(Reporter).all() + return info.context.get("session").query(Reporter).all() schema = graphene.Schema(query=Query) with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - await schema.execute_async(""" + await schema.execute_async( + """ query { reporters { articles { @@ -578,24 +621,34 @@ def resolve_reporters(self, info): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages - if is_sqlalchemy_version_less_than('1.3'): + if is_sqlalchemy_version_less_than("1.3"): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - select_statements = [message for message in messages if 'SELECT' in message and 'JOIN articles' in message] + select_statements = [ + message + for message in messages + if "SELECT" in message and "JOIN articles" in message + ] else: - select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] + select_statements = [ + message + for message in messages + if "SELECT" in message and "FROM articles" in message + ] assert len(select_statements) == 1 def test_connection_factory_field_overrides_batching_is_true(session_factory): session = session_factory() - reporter_1 = Reporter(first_name='Reporter_1') + reporter_1 = Reporter(first_name="Reporter_1") session.add(reporter_1) - reporter_2 = Reporter(first_name='Reporter_2') + reporter_2 = Reporter(first_name="Reporter_2") session.add(reporter_2) session.commit() session.close() @@ -618,14 +671,15 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_reporters(self, info): - return info.context.get('session').query(Reporter).all() + return info.context.get("session").query(Reporter).all() schema = graphene.Schema(query=Query) with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - schema.execute(""" + schema.execute( + """ query { reporters { articles { @@ -637,8 +691,14 @@ def resolve_reporters(self, info): } } } - """, context_value={"session": session}) + """, + context_value={"session": session}, + ) messages = sqlalchemy_logging_handler.messages - select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] + select_statements = [ + message + for message in messages + if "SELECT" in message and "FROM articles" in message + ] assert len(select_statements) == 2 diff --git a/graphene_sqlalchemy/tests/test_benchmark.py b/graphene_sqlalchemy/tests/test_benchmark.py index 11e9d0e0..bb105edd 100644 --- a/graphene_sqlalchemy/tests/test_benchmark.py +++ b/graphene_sqlalchemy/tests/test_benchmark.py @@ -7,8 +7,8 @@ from ..utils import is_sqlalchemy_version_less_than from .models import Article, HairKind, Pet, Reporter -if is_sqlalchemy_version_less_than('1.2'): - pytest.skip('SQL batching only works for SQLAlchemy 1.2+', allow_module_level=True) +if is_sqlalchemy_version_less_than("1.2"): + pytest.skip("SQL batching only works for SQLAlchemy 1.2+", allow_module_level=True) def get_schema(): @@ -32,10 +32,10 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_articles(self, info): - return info.context.get('session').query(Article).all() + return info.context.get("session").query(Article).all() def resolve_reporters(self, info): - return info.context.get('session').query(Reporter).all() + return info.context.get("session").query(Reporter).all() return graphene.Schema(query=Query) @@ -46,8 +46,8 @@ def benchmark_query(session_factory, benchmark, query): @benchmark def execute_query(): result = schema.execute( - query, - context_value={"session": session_factory()}, + query, + context_value={"session": session_factory()}, ) assert not result.errors @@ -56,26 +56,29 @@ def test_one_to_one(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - article_1 = Article(headline='Article_1') + article_1 = Article(headline="Article_1") article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline='Article_2') + article_2 = Article(headline="Article_2") article_2.reporter = reporter_2 session.add(article_2) session.commit() session.close() - benchmark_query(session_factory, benchmark, """ + benchmark_query( + session_factory, + benchmark, + """ query { reporters { firstName @@ -84,33 +87,37 @@ def test_one_to_one(session_factory, benchmark): } } } - """) + """, + ) def test_many_to_one(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - article_1 = Article(headline='Article_1') + article_1 = Article(headline="Article_1") article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline='Article_2') + article_2 = Article(headline="Article_2") article_2.reporter = reporter_2 session.add(article_2) session.commit() session.close() - benchmark_query(session_factory, benchmark, """ + benchmark_query( + session_factory, + benchmark, + """ query { articles { headline @@ -119,41 +126,45 @@ def test_many_to_one(session_factory, benchmark): } } } - """) + """, + ) def test_one_to_many(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - article_1 = Article(headline='Article_1') + article_1 = Article(headline="Article_1") article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline='Article_2') + article_2 = Article(headline="Article_2") article_2.reporter = reporter_1 session.add(article_2) - article_3 = Article(headline='Article_3') + article_3 = Article(headline="Article_3") article_3.reporter = reporter_2 session.add(article_3) - article_4 = Article(headline='Article_4') + article_4 = Article(headline="Article_4") article_4.reporter = reporter_2 session.add(article_4) session.commit() session.close() - benchmark_query(session_factory, benchmark, """ + benchmark_query( + session_factory, + benchmark, + """ query { reporters { firstName @@ -166,34 +177,35 @@ def test_one_to_many(session_factory, benchmark): } } } - """) + """, + ) def test_many_to_many(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name='Reporter_1', + first_name="Reporter_1", ) session.add(reporter_1) reporter_2 = Reporter( - first_name='Reporter_2', + first_name="Reporter_2", ) session.add(reporter_2) - pet_1 = Pet(name='Pet_1', pet_kind='cat', hair_kind=HairKind.LONG) + pet_1 = Pet(name="Pet_1", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_1) - pet_2 = Pet(name='Pet_2', pet_kind='cat', hair_kind=HairKind.LONG) + pet_2 = Pet(name="Pet_2", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_2) reporter_1.pets.append(pet_1) reporter_1.pets.append(pet_2) - pet_3 = Pet(name='Pet_3', pet_kind='cat', hair_kind=HairKind.LONG) + pet_3 = Pet(name="Pet_3", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_3) - pet_4 = Pet(name='Pet_4', pet_kind='cat', hair_kind=HairKind.LONG) + pet_4 = Pet(name="Pet_4", pet_kind="cat", hair_kind=HairKind.LONG) session.add(pet_4) reporter_2.pets.append(pet_3) @@ -202,7 +214,10 @@ def test_many_to_many(session_factory, benchmark): session.commit() session.close() - benchmark_query(session_factory, benchmark, """ + benchmark_query( + session_factory, + benchmark, + """ query { reporters { firstName @@ -215,4 +230,5 @@ def test_many_to_many(session_factory, benchmark): } } } - """) + """, + ) diff --git a/graphene_sqlalchemy/tests/test_converter.py b/graphene_sqlalchemy/tests/test_converter.py index 70e11713..232a82cd 100644 --- a/graphene_sqlalchemy/tests/test_converter.py +++ b/graphene_sqlalchemy/tests/test_converter.py @@ -16,15 +16,22 @@ from graphene.types.json import JSONString from graphene.types.structures import List, Structure -from ..converter import (convert_sqlalchemy_column, - convert_sqlalchemy_composite, - convert_sqlalchemy_relationship) -from ..fields import (UnsortedSQLAlchemyConnectionField, - default_connection_field_factory) +from ..converter import ( + convert_sqlalchemy_column, + convert_sqlalchemy_composite, + convert_sqlalchemy_relationship, +) +from ..fields import UnsortedSQLAlchemyConnectionField, default_connection_field_factory from ..registry import Registry, get_global_registry from ..types import SQLAlchemyObjectType -from .models import (Article, CompositeFullName, Pet, Reporter, ShoppingCart, - ShoppingCartItem) +from .models import ( + Article, + CompositeFullName, + Pet, + Reporter, + ShoppingCart, + ShoppingCartItem, +) def mock_resolver(): @@ -33,21 +40,21 @@ def mock_resolver(): def get_field(sqlalchemy_type, **column_kwargs): class Model(declarative_base()): - __tablename__ = 'model' + __tablename__ = "model" id_ = Column(types.Integer, primary_key=True) column = Column(sqlalchemy_type, doc="Custom Help Text", **column_kwargs) - column_prop = inspect(Model).column_attrs['column'] + column_prop = inspect(Model).column_attrs["column"] return convert_sqlalchemy_column(column_prop, get_global_registry(), mock_resolver) def get_field_from_column(column_): class Model(declarative_base()): - __tablename__ = 'model' + __tablename__ = "model" id_ = Column(types.Integer, primary_key=True) column = column_ - column_prop = inspect(Model).column_attrs['column'] + column_prop = inspect(Model).column_attrs["column"] return convert_sqlalchemy_column(column_prop, get_global_registry(), mock_resolver) @@ -55,7 +62,7 @@ def test_should_unknown_sqlalchemy_field_raise_exception(): re_err = "Don't know how to convert the SQLAlchemy field" with pytest.raises(Exception, match=re_err): # support legacy Binary type and subsequent LargeBinary - get_field(getattr(types, 'LargeBinary', types.BINARY)()) + get_field(getattr(types, "LargeBinary", types.BINARY)()) def test_should_date_convert_string(): @@ -126,7 +133,9 @@ def test_should_integer_convert_int(): def test_should_primary_integer_convert_id(): - assert get_field(types.Integer(), primary_key=True).type == graphene.NonNull(graphene.ID) + assert get_field(types.Integer(), primary_key=True).type == graphene.NonNull( + graphene.ID + ) def test_should_boolean_convert_boolean(): @@ -142,7 +151,7 @@ def test_should_numeric_convert_float(): def test_should_choice_convert_enum(): - field = get_field(ChoiceType([(u"es", u"Spanish"), (u"en", u"English")])) + field = get_field(ChoiceType([("es", "Spanish"), ("en", "English")])) graphene_type = field.type assert issubclass(graphene_type, graphene.Enum) assert graphene_type._meta.name == "MODEL_COLUMN" @@ -152,8 +161,8 @@ def test_should_choice_convert_enum(): def test_should_enum_choice_convert_enum(): class TestEnum(enum.Enum): - es = u"Spanish" - en = u"English" + es = "Spanish" + en = "English" field = get_field(ChoiceType(TestEnum, impl=types.String())) graphene_type = field.type @@ -177,9 +186,9 @@ class TestEnum(enum.IntEnum): def test_should_columproperty_convert(): - field = get_field_from_column(column_property( - select([func.sum(func.cast(id, types.Integer))]).where(id == 1) - )) + field = get_field_from_column( + column_property(select([func.sum(func.cast(id, types.Integer))]).where(id == 1)) + ) assert field.type == graphene.Int @@ -200,7 +209,11 @@ class Meta: model = Article dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', + Reporter.pets.property, + A, + default_connection_field_factory, + True, + "orm_field_name", ) assert isinstance(dynamic_field, graphene.Dynamic) assert not dynamic_field.get_type() @@ -212,7 +225,11 @@ class Meta: model = Pet dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', + Reporter.pets.property, + A, + default_connection_field_factory, + True, + "orm_field_name", ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -228,7 +245,11 @@ class Meta: interfaces = (Node,) dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', + Reporter.pets.property, + A, + default_connection_field_factory, + True, + "orm_field_name", ) assert isinstance(dynamic_field, graphene.Dynamic) assert isinstance(dynamic_field.get_type(), UnsortedSQLAlchemyConnectionField) @@ -240,7 +261,11 @@ class Meta: model = Article dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', + Reporter.pets.property, + A, + default_connection_field_factory, + True, + "orm_field_name", ) assert isinstance(dynamic_field, graphene.Dynamic) assert not dynamic_field.get_type() @@ -252,7 +277,11 @@ class Meta: model = Reporter dynamic_field = convert_sqlalchemy_relationship( - Article.reporter.property, A, default_connection_field_factory, True, 'orm_field_name', + Article.reporter.property, + A, + default_connection_field_factory, + True, + "orm_field_name", ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -267,7 +296,11 @@ class Meta: interfaces = (Node,) dynamic_field = convert_sqlalchemy_relationship( - Article.reporter.property, A, default_connection_field_factory, True, 'orm_field_name', + Article.reporter.property, + A, + default_connection_field_factory, + True, + "orm_field_name", ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -282,7 +315,11 @@ class Meta: interfaces = (Node,) dynamic_field = convert_sqlalchemy_relationship( - Reporter.favorite_article.property, A, default_connection_field_factory, True, 'orm_field_name', + Reporter.favorite_article.property, + A, + default_connection_field_factory, + True, + "orm_field_name", ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -306,7 +343,9 @@ def test_should_postgresql_enum_convert(): def test_should_postgresql_py_enum_convert(): - field = get_field(postgresql.ENUM(enum.Enum("TwoNumbers", "one two"), name="two_numbers")) + field = get_field( + postgresql.ENUM(enum.Enum("TwoNumbers", "one two"), name="two_numbers") + ) field_type = field.type() assert field_type._meta.name == "TwoNumbers" assert isinstance(field_type, graphene.Enum) @@ -368,7 +407,11 @@ def convert_composite_class(composite, registry): return graphene.String(description=composite.doc) field = convert_sqlalchemy_composite( - composite(CompositeClass, (Column(types.Unicode(50)), Column(types.Unicode(50))), doc="Custom Help Text"), + composite( + CompositeClass, + (Column(types.Unicode(50)), Column(types.Unicode(50))), + doc="Custom Help Text", + ), registry, mock_resolver, ) @@ -384,7 +427,10 @@ def __init__(self, col1, col2): re_err = "Don't know how to convert the composite field" with pytest.raises(Exception, match=re_err): convert_sqlalchemy_composite( - composite(CompositeFullName, (Column(types.Unicode(50)), Column(types.Unicode(50)))), + composite( + CompositeFullName, + (Column(types.Unicode(50)), Column(types.Unicode(50))), + ), Registry(), mock_resolver, ) @@ -406,26 +452,33 @@ class Meta: ####################################################### shopping_cart_item_expected_types: Dict[str, Union[Scalar, Structure]] = { - 'hybrid_prop_shopping_cart': List(ShoppingCartType) + "hybrid_prop_shopping_cart": List(ShoppingCartType) } - assert sorted(list(ShoppingCartItemType._meta.fields.keys())) == sorted([ - # Columns - "id", - # Append Hybrid Properties from Above - *shopping_cart_item_expected_types.keys() - ]) + assert sorted(list(ShoppingCartItemType._meta.fields.keys())) == sorted( + [ + # Columns + "id", + # Append Hybrid Properties from Above + *shopping_cart_item_expected_types.keys(), + ] + ) - for hybrid_prop_name, hybrid_prop_expected_return_type in shopping_cart_item_expected_types.items(): + for ( + hybrid_prop_name, + hybrid_prop_expected_return_type, + ) in shopping_cart_item_expected_types.items(): hybrid_prop_field = ShoppingCartItemType._meta.fields[hybrid_prop_name] # this is a simple way of showing the failed property name # instead of having to unroll the loop. - assert ( - (hybrid_prop_name, str(hybrid_prop_field.type)) == - (hybrid_prop_name, str(hybrid_prop_expected_return_type)) + assert (hybrid_prop_name, str(hybrid_prop_field.type)) == ( + hybrid_prop_name, + str(hybrid_prop_expected_return_type), ) - assert hybrid_prop_field.description is None # "doc" is ignored by hybrid property + assert ( + hybrid_prop_field.description is None + ) # "doc" is ignored by hybrid property ################################################### # Check ShoppingCart's Properties and Return Types @@ -456,20 +509,27 @@ class Meta: "hybrid_prop_optional_self_referential": ShoppingCartType, } - assert sorted(list(ShoppingCartType._meta.fields.keys())) == sorted([ - # Columns - "id", - # Append Hybrid Properties from Above - *shopping_cart_expected_types.keys() - ]) + assert sorted(list(ShoppingCartType._meta.fields.keys())) == sorted( + [ + # Columns + "id", + # Append Hybrid Properties from Above + *shopping_cart_expected_types.keys(), + ] + ) - for hybrid_prop_name, hybrid_prop_expected_return_type in shopping_cart_expected_types.items(): + for ( + hybrid_prop_name, + hybrid_prop_expected_return_type, + ) in shopping_cart_expected_types.items(): hybrid_prop_field = ShoppingCartType._meta.fields[hybrid_prop_name] # this is a simple way of showing the failed property name # instead of having to unroll the loop. - assert ( - (hybrid_prop_name, str(hybrid_prop_field.type)) == - (hybrid_prop_name, str(hybrid_prop_expected_return_type)) + assert (hybrid_prop_name, str(hybrid_prop_field.type)) == ( + hybrid_prop_name, + str(hybrid_prop_expected_return_type), ) - assert hybrid_prop_field.description is None # "doc" is ignored by hybrid property + assert ( + hybrid_prop_field.description is None + ) # "doc" is ignored by hybrid property diff --git a/graphene_sqlalchemy/tests/test_enums.py b/graphene_sqlalchemy/tests/test_enums.py index ca376964..cd97a00e 100644 --- a/graphene_sqlalchemy/tests/test_enums.py +++ b/graphene_sqlalchemy/tests/test_enums.py @@ -54,7 +54,7 @@ def test_convert_sa_enum_to_graphene_enum_based_on_list_named(): assert [ (key, value.value) for key, value in graphene_enum._meta.enum.__members__.items() - ] == [("RED", 'red'), ("GREEN", 'green'), ("BLUE", 'blue')] + ] == [("RED", "red"), ("GREEN", "green"), ("BLUE", "blue")] def test_convert_sa_enum_to_graphene_enum_based_on_list_unnamed(): @@ -65,7 +65,7 @@ def test_convert_sa_enum_to_graphene_enum_based_on_list_unnamed(): assert [ (key, value.value) for key, value in graphene_enum._meta.enum.__members__.items() - ] == [("RED", 'red'), ("GREEN", 'green'), ("BLUE", 'blue')] + ] == [("RED", "red"), ("GREEN", "green"), ("BLUE", "blue")] def test_convert_sa_enum_to_graphene_enum_based_on_list_without_name(): @@ -80,36 +80,35 @@ class PetType(SQLAlchemyObjectType): class Meta: model = Pet - enum = enum_for_field(PetType, 'pet_kind') + enum = enum_for_field(PetType, "pet_kind") assert isinstance(enum, type(Enum)) assert enum._meta.name == "PetKind" assert [ - (key, value.value) - for key, value in enum._meta.enum.__members__.items() - ] == [("CAT", 'cat'), ("DOG", 'dog')] - enum2 = enum_for_field(PetType, 'pet_kind') + (key, value.value) for key, value in enum._meta.enum.__members__.items() + ] == [("CAT", "cat"), ("DOG", "dog")] + enum2 = enum_for_field(PetType, "pet_kind") assert enum2 is enum - enum2 = PetType.enum_for_field('pet_kind') + enum2 = PetType.enum_for_field("pet_kind") assert enum2 is enum - enum = enum_for_field(PetType, 'hair_kind') + enum = enum_for_field(PetType, "hair_kind") assert isinstance(enum, type(Enum)) assert enum._meta.name == "HairKind" assert enum._meta.enum is HairKind - enum2 = PetType.enum_for_field('hair_kind') + enum2 = PetType.enum_for_field("hair_kind") assert enum2 is enum re_err = r"Cannot get PetType\.other_kind" with pytest.raises(TypeError, match=re_err): - enum_for_field(PetType, 'other_kind') + enum_for_field(PetType, "other_kind") with pytest.raises(TypeError, match=re_err): - PetType.enum_for_field('other_kind') + PetType.enum_for_field("other_kind") re_err = r"PetType\.name does not map to enum column" with pytest.raises(TypeError, match=re_err): - enum_for_field(PetType, 'name') + enum_for_field(PetType, "name") with pytest.raises(TypeError, match=re_err): - PetType.enum_for_field('name') + PetType.enum_for_field("name") re_err = r"Expected a field name, but got: None" with pytest.raises(TypeError, match=re_err): @@ -119,4 +118,4 @@ class Meta: re_err = "Expected SQLAlchemyObjectType, but got: None" with pytest.raises(TypeError, match=re_err): - enum_for_field(None, 'other_kind') + enum_for_field(None, "other_kind") diff --git a/graphene_sqlalchemy/tests/test_fields.py b/graphene_sqlalchemy/tests/test_fields.py index 357055e3..0549e569 100644 --- a/graphene_sqlalchemy/tests/test_fields.py +++ b/graphene_sqlalchemy/tests/test_fields.py @@ -4,8 +4,7 @@ from graphene import NonNull, ObjectType from graphene.relay import Connection, Node -from ..fields import (SQLAlchemyConnectionField, - UnsortedSQLAlchemyConnectionField) +from ..fields import SQLAlchemyConnectionField, UnsortedSQLAlchemyConnectionField from ..types import SQLAlchemyObjectType from .models import Editor as EditorModel from .models import Pet as PetModel @@ -21,6 +20,7 @@ class Editor(SQLAlchemyObjectType): class Meta: model = EditorModel + ## # SQLAlchemyConnectionField ## @@ -59,6 +59,7 @@ def test_type_assert_object_has_connection(): with pytest.raises(AssertionError, match="doesn't have a connection"): SQLAlchemyConnectionField(Editor).type + ## # UnsortedSQLAlchemyConnectionField ## diff --git a/graphene_sqlalchemy/tests/test_query.py b/graphene_sqlalchemy/tests/test_query.py index 39140814..c7a173df 100644 --- a/graphene_sqlalchemy/tests/test_query.py +++ b/graphene_sqlalchemy/tests/test_query.py @@ -9,19 +9,17 @@ def add_test_data(session): - reporter = Reporter( - first_name='John', last_name='Doe', favorite_pet_kind='cat') + reporter = Reporter(first_name="John", last_name="Doe", favorite_pet_kind="cat") session.add(reporter) - pet = Pet(name='Garfield', pet_kind='cat', hair_kind=HairKind.SHORT) + pet = Pet(name="Garfield", pet_kind="cat", hair_kind=HairKind.SHORT) session.add(pet) pet.reporters.append(reporter) - article = Article(headline='Hi!') + article = Article(headline="Hi!") article.reporter = reporter session.add(article) - reporter = Reporter( - first_name='Jane', last_name='Roe', favorite_pet_kind='dog') + reporter = Reporter(first_name="Jane", last_name="Roe", favorite_pet_kind="dog") session.add(reporter) - pet = Pet(name='Lassie', pet_kind='dog', hair_kind=HairKind.LONG) + pet = Pet(name="Lassie", pet_kind="dog", hair_kind=HairKind.LONG) pet.reporters.append(reporter) session.add(pet) editor = Editor(name="Jack") @@ -163,12 +161,12 @@ class Meta: model = Reporter interfaces = (Node,) - first_name_v2 = ORMField(model_attr='first_name') - hybrid_prop_v2 = ORMField(model_attr='hybrid_prop') - column_prop_v2 = ORMField(model_attr='column_prop') + first_name_v2 = ORMField(model_attr="first_name") + hybrid_prop_v2 = ORMField(model_attr="hybrid_prop") + column_prop_v2 = ORMField(model_attr="column_prop") composite_prop = ORMField() - favorite_article_v2 = ORMField(model_attr='favorite_article') - articles_v2 = ORMField(model_attr='articles') + favorite_article_v2 = ORMField(model_attr="favorite_article") + articles_v2 = ORMField(model_attr="articles") class ArticleType(SQLAlchemyObjectType): class Meta: diff --git a/graphene_sqlalchemy/tests/test_query_enums.py b/graphene_sqlalchemy/tests/test_query_enums.py index 5166c45f..923bbed1 100644 --- a/graphene_sqlalchemy/tests/test_query_enums.py +++ b/graphene_sqlalchemy/tests/test_query_enums.py @@ -9,7 +9,6 @@ def test_query_pet_kinds(session): add_test_data(session) class PetType(SQLAlchemyObjectType): - class Meta: model = Pet @@ -20,8 +19,9 @@ class Meta: class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) reporters = graphene.List(ReporterType) - pets = graphene.List(PetType, kind=graphene.Argument( - PetType.enum_for_field('pet_kind'))) + pets = graphene.List( + PetType, kind=graphene.Argument(PetType.enum_for_field("pet_kind")) + ) def resolve_reporter(self, _info): return session.query(Reporter).first() @@ -58,27 +58,24 @@ def resolve_pets(self, _info, kind): } """ expected = { - 'reporter': { - 'firstName': 'John', - 'lastName': 'Doe', - 'email': None, - 'favoritePetKind': 'CAT', - 'pets': [{ - 'name': 'Garfield', - 'petKind': 'CAT' - }] + "reporter": { + "firstName": "John", + "lastName": "Doe", + "email": None, + "favoritePetKind": "CAT", + "pets": [{"name": "Garfield", "petKind": "CAT"}], }, - 'reporters': [{ - 'firstName': 'John', - 'favoritePetKind': 'CAT', - }, { - 'firstName': 'Jane', - 'favoritePetKind': 'DOG', - }], - 'pets': [{ - 'name': 'Lassie', - 'petKind': 'DOG' - }] + "reporters": [ + { + "firstName": "John", + "favoritePetKind": "CAT", + }, + { + "firstName": "Jane", + "favoritePetKind": "DOG", + }, + ], + "pets": [{"name": "Lassie", "petKind": "DOG"}], } schema = graphene.Schema(query=Query) result = schema.execute(query) @@ -125,8 +122,8 @@ class Meta: class Query(graphene.ObjectType): pet = graphene.Field( - PetType, - kind=graphene.Argument(PetType.enum_for_field('pet_kind'))) + PetType, kind=graphene.Argument(PetType.enum_for_field("pet_kind")) + ) def resolve_pet(self, info, kind=None): query = session.query(Pet) diff --git a/graphene_sqlalchemy/tests/test_reflected.py b/graphene_sqlalchemy/tests/test_reflected.py index 46e10de9..a3f6c4aa 100644 --- a/graphene_sqlalchemy/tests/test_reflected.py +++ b/graphene_sqlalchemy/tests/test_reflected.py @@ -1,4 +1,3 @@ - from graphene import ObjectType from ..registry import Registry diff --git a/graphene_sqlalchemy/tests/test_sort_enums.py b/graphene_sqlalchemy/tests/test_sort_enums.py index 6291d4f8..08de0b20 100644 --- a/graphene_sqlalchemy/tests/test_sort_enums.py +++ b/graphene_sqlalchemy/tests/test_sort_enums.py @@ -354,7 +354,7 @@ def makeNodes(nodeList): """ result = schema.execute(queryError, context_value={"session": session}) assert result.errors is not None - assert 'cannot represent non-enum value' in result.errors[0].message + assert "cannot represent non-enum value" in result.errors[0].message queryNoSort = """ query sortTest { diff --git a/graphene_sqlalchemy/tests/test_types.py b/graphene_sqlalchemy/tests/test_types.py index 9a2e992d..31caea3c 100644 --- a/graphene_sqlalchemy/tests/test_types.py +++ b/graphene_sqlalchemy/tests/test_types.py @@ -4,16 +4,31 @@ import sqlalchemy.exc import sqlalchemy.orm.exc -from graphene import (Boolean, Dynamic, Field, Float, GlobalID, Int, List, - Node, NonNull, ObjectType, Schema, String) +from graphene import ( + Boolean, + Dynamic, + Field, + Float, + GlobalID, + Int, + List, + Node, + NonNull, + ObjectType, + Schema, + String, +) from graphene.relay import Connection from .. import utils from ..converter import convert_sqlalchemy_composite -from ..fields import (SQLAlchemyConnectionField, - UnsortedSQLAlchemyConnectionField, createConnectionField, - registerConnectionFieldFactory, - unregisterConnectionFieldFactory) +from ..fields import ( + SQLAlchemyConnectionField, + UnsortedSQLAlchemyConnectionField, + createConnectionField, + registerConnectionFieldFactory, + unregisterConnectionFieldFactory, +) from ..types import ORMField, SQLAlchemyObjectType, SQLAlchemyObjectTypeOptions from .models import Article, CompositeFullName, Pet, Reporter @@ -21,6 +36,7 @@ def test_should_raise_if_no_model(): re_err = r"valid SQLAlchemy Model" with pytest.raises(Exception, match=re_err): + class Character1(SQLAlchemyObjectType): pass @@ -28,6 +44,7 @@ class Character1(SQLAlchemyObjectType): def test_should_raise_if_model_is_invalid(): re_err = r"valid SQLAlchemy Model" with pytest.raises(Exception, match=re_err): + class Character(SQLAlchemyObjectType): class Meta: model = 1 @@ -45,7 +62,7 @@ class Meta: reporter = Reporter() session.add(reporter) session.commit() - info = mock.Mock(context={'session': session}) + info = mock.Mock(context={"session": session}) reporter_node = ReporterType.get_node(info, reporter.id) assert reporter == reporter_node @@ -74,91 +91,93 @@ class Meta: model = Article interfaces = (Node,) - assert sorted(list(ReporterType._meta.fields.keys())) == sorted([ - # Columns - "column_prop", # SQLAlchemy retuns column properties first - "id", - "first_name", - "last_name", - "email", - "favorite_pet_kind", - # Composite - "composite_prop", - # Hybrid - "hybrid_prop_with_doc", - "hybrid_prop", - "hybrid_prop_str", - "hybrid_prop_int", - "hybrid_prop_float", - "hybrid_prop_bool", - "hybrid_prop_list", - # Relationship - "pets", - "articles", - "favorite_article", - ]) + assert sorted(list(ReporterType._meta.fields.keys())) == sorted( + [ + # Columns + "column_prop", # SQLAlchemy retuns column properties first + "id", + "first_name", + "last_name", + "email", + "favorite_pet_kind", + # Composite + "composite_prop", + # Hybrid + "hybrid_prop_with_doc", + "hybrid_prop", + "hybrid_prop_str", + "hybrid_prop_int", + "hybrid_prop_float", + "hybrid_prop_bool", + "hybrid_prop_list", + # Relationship + "pets", + "articles", + "favorite_article", + ] + ) # column - first_name_field = ReporterType._meta.fields['first_name'] + first_name_field = ReporterType._meta.fields["first_name"] assert first_name_field.type == String assert first_name_field.description == "First name" # column_property - column_prop_field = ReporterType._meta.fields['column_prop'] + column_prop_field = ReporterType._meta.fields["column_prop"] assert column_prop_field.type == Int # "doc" is ignored by column_property assert column_prop_field.description is None # composite - full_name_field = ReporterType._meta.fields['composite_prop'] + full_name_field = ReporterType._meta.fields["composite_prop"] assert full_name_field.type == String # "doc" is ignored by composite assert full_name_field.description is None # hybrid_property - hybrid_prop = ReporterType._meta.fields['hybrid_prop'] + hybrid_prop = ReporterType._meta.fields["hybrid_prop"] assert hybrid_prop.type == String # "doc" is ignored by hybrid_property assert hybrid_prop.description is None # hybrid_property_str - hybrid_prop_str = ReporterType._meta.fields['hybrid_prop_str'] + hybrid_prop_str = ReporterType._meta.fields["hybrid_prop_str"] assert hybrid_prop_str.type == String # "doc" is ignored by hybrid_property assert hybrid_prop_str.description is None # hybrid_property_int - hybrid_prop_int = ReporterType._meta.fields['hybrid_prop_int'] + hybrid_prop_int = ReporterType._meta.fields["hybrid_prop_int"] assert hybrid_prop_int.type == Int # "doc" is ignored by hybrid_property assert hybrid_prop_int.description is None # hybrid_property_float - hybrid_prop_float = ReporterType._meta.fields['hybrid_prop_float'] + hybrid_prop_float = ReporterType._meta.fields["hybrid_prop_float"] assert hybrid_prop_float.type == Float # "doc" is ignored by hybrid_property assert hybrid_prop_float.description is None # hybrid_property_bool - hybrid_prop_bool = ReporterType._meta.fields['hybrid_prop_bool'] + hybrid_prop_bool = ReporterType._meta.fields["hybrid_prop_bool"] assert hybrid_prop_bool.type == Boolean # "doc" is ignored by hybrid_property assert hybrid_prop_bool.description is None # hybrid_property_list - hybrid_prop_list = ReporterType._meta.fields['hybrid_prop_list'] + hybrid_prop_list = ReporterType._meta.fields["hybrid_prop_list"] assert hybrid_prop_list.type == List(Int) # "doc" is ignored by hybrid_property assert hybrid_prop_list.description is None # hybrid_prop_with_doc - hybrid_prop_with_doc = ReporterType._meta.fields['hybrid_prop_with_doc'] + hybrid_prop_with_doc = ReporterType._meta.fields["hybrid_prop_with_doc"] assert hybrid_prop_with_doc.type == String # docstring is picked up from hybrid_prop_with_doc assert hybrid_prop_with_doc.description == "Docstring test" # relationship - favorite_article_field = ReporterType._meta.fields['favorite_article'] + favorite_article_field = ReporterType._meta.fields["favorite_article"] assert isinstance(favorite_article_field, Dynamic) assert favorite_article_field.type().type == ArticleType assert favorite_article_field.type().description is None @@ -172,7 +191,7 @@ def convert_composite_class(composite, registry): class ReporterMixin(object): # columns first_name = ORMField(required=True) - last_name = ORMField(description='Overridden') + last_name = ORMField(description="Overridden") class ReporterType(SQLAlchemyObjectType, ReporterMixin): class Meta: @@ -180,8 +199,8 @@ class Meta: interfaces = (Node,) # columns - email = ORMField(deprecation_reason='Overridden') - email_v2 = ORMField(model_attr='email', type_=Int) + email = ORMField(deprecation_reason="Overridden") + email_v2 = ORMField(model_attr="email", type_=Int) # column_property column_prop = ORMField(type_=String) @@ -190,13 +209,13 @@ class Meta: composite_prop = ORMField() # hybrid_property - hybrid_prop_with_doc = ORMField(description='Overridden') - hybrid_prop = ORMField(description='Overridden') + hybrid_prop_with_doc = ORMField(description="Overridden") + hybrid_prop = ORMField(description="Overridden") # relationships - favorite_article = ORMField(description='Overridden') - articles = ORMField(deprecation_reason='Overridden') - pets = ORMField(description='Overridden') + favorite_article = ORMField(description="Overridden") + articles = ORMField(deprecation_reason="Overridden") + pets = ORMField(description="Overridden") class ArticleType(SQLAlchemyObjectType): class Meta: @@ -209,99 +228,101 @@ class Meta: interfaces = (Node,) use_connection = False - assert sorted(list(ReporterType._meta.fields.keys())) == sorted([ - # Fields from ReporterMixin - "first_name", - "last_name", - # Fields from ReporterType - "email", - "email_v2", - "column_prop", - "composite_prop", - "hybrid_prop_with_doc", - "hybrid_prop", - "favorite_article", - "articles", - "pets", - # Then the automatic SQLAlchemy fields - "id", - "favorite_pet_kind", - "hybrid_prop_str", - "hybrid_prop_int", - "hybrid_prop_float", - "hybrid_prop_bool", - "hybrid_prop_list", - ]) - - first_name_field = ReporterType._meta.fields['first_name'] + assert sorted(list(ReporterType._meta.fields.keys())) == sorted( + [ + # Fields from ReporterMixin + "first_name", + "last_name", + # Fields from ReporterType + "email", + "email_v2", + "column_prop", + "composite_prop", + "hybrid_prop_with_doc", + "hybrid_prop", + "favorite_article", + "articles", + "pets", + # Then the automatic SQLAlchemy fields + "id", + "favorite_pet_kind", + "hybrid_prop_str", + "hybrid_prop_int", + "hybrid_prop_float", + "hybrid_prop_bool", + "hybrid_prop_list", + ] + ) + + first_name_field = ReporterType._meta.fields["first_name"] assert isinstance(first_name_field.type, NonNull) assert first_name_field.type.of_type == String assert first_name_field.description == "First name" assert first_name_field.deprecation_reason is None - last_name_field = ReporterType._meta.fields['last_name'] + last_name_field = ReporterType._meta.fields["last_name"] assert last_name_field.type == String assert last_name_field.description == "Overridden" assert last_name_field.deprecation_reason is None - email_field = ReporterType._meta.fields['email'] + email_field = ReporterType._meta.fields["email"] assert email_field.type == String assert email_field.description == "Email" assert email_field.deprecation_reason == "Overridden" - email_field_v2 = ReporterType._meta.fields['email_v2'] + email_field_v2 = ReporterType._meta.fields["email_v2"] assert email_field_v2.type == Int assert email_field_v2.description == "Email" assert email_field_v2.deprecation_reason is None - hybrid_prop_field = ReporterType._meta.fields['hybrid_prop'] + hybrid_prop_field = ReporterType._meta.fields["hybrid_prop"] assert hybrid_prop_field.type == String assert hybrid_prop_field.description == "Overridden" assert hybrid_prop_field.deprecation_reason is None - hybrid_prop_with_doc_field = ReporterType._meta.fields['hybrid_prop_with_doc'] + hybrid_prop_with_doc_field = ReporterType._meta.fields["hybrid_prop_with_doc"] assert hybrid_prop_with_doc_field.type == String assert hybrid_prop_with_doc_field.description == "Overridden" assert hybrid_prop_with_doc_field.deprecation_reason is None - column_prop_field_v2 = ReporterType._meta.fields['column_prop'] + column_prop_field_v2 = ReporterType._meta.fields["column_prop"] assert column_prop_field_v2.type == String assert column_prop_field_v2.description is None assert column_prop_field_v2.deprecation_reason is None - composite_prop_field = ReporterType._meta.fields['composite_prop'] + composite_prop_field = ReporterType._meta.fields["composite_prop"] assert composite_prop_field.type == String assert composite_prop_field.description is None assert composite_prop_field.deprecation_reason is None - favorite_article_field = ReporterType._meta.fields['favorite_article'] + favorite_article_field = ReporterType._meta.fields["favorite_article"] assert isinstance(favorite_article_field, Dynamic) assert favorite_article_field.type().type == ArticleType - assert favorite_article_field.type().description == 'Overridden' + assert favorite_article_field.type().description == "Overridden" - articles_field = ReporterType._meta.fields['articles'] + articles_field = ReporterType._meta.fields["articles"] assert isinstance(articles_field, Dynamic) assert isinstance(articles_field.type(), UnsortedSQLAlchemyConnectionField) assert articles_field.type().deprecation_reason == "Overridden" - pets_field = ReporterType._meta.fields['pets'] + pets_field = ReporterType._meta.fields["pets"] assert isinstance(pets_field, Dynamic) assert isinstance(pets_field.type().type, List) assert pets_field.type().type.of_type == PetType - assert pets_field.type().description == 'Overridden' + assert pets_field.type().description == "Overridden" def test_invalid_model_attr(): err_msg = ( - "Cannot map ORMField to a model attribute.\n" - "Field: 'ReporterType.first_name'" + "Cannot map ORMField to a model attribute.\n" "Field: 'ReporterType.first_name'" ) with pytest.raises(ValueError, match=err_msg): + class ReporterType(SQLAlchemyObjectType): class Meta: model = Reporter - first_name = ORMField(model_attr='does_not_exist') + first_name = ORMField(model_attr="does_not_exist") def test_only_fields(): @@ -325,29 +346,32 @@ class Meta: first_name = ORMField() # Takes precedence last_name = ORMField() # Noop - assert sorted(list(ReporterType._meta.fields.keys())) == sorted([ - "first_name", - "last_name", - "column_prop", - "email", - "favorite_pet_kind", - "composite_prop", - "hybrid_prop_with_doc", - "hybrid_prop", - "hybrid_prop_str", - "hybrid_prop_int", - "hybrid_prop_float", - "hybrid_prop_bool", - "hybrid_prop_list", - "pets", - "articles", - "favorite_article", - ]) + assert sorted(list(ReporterType._meta.fields.keys())) == sorted( + [ + "first_name", + "last_name", + "column_prop", + "email", + "favorite_pet_kind", + "composite_prop", + "hybrid_prop_with_doc", + "hybrid_prop", + "hybrid_prop_str", + "hybrid_prop_int", + "hybrid_prop_float", + "hybrid_prop_bool", + "hybrid_prop_list", + "pets", + "articles", + "favorite_article", + ] + ) def test_only_and_exclude_fields(): re_err = r"'only_fields' and 'exclude_fields' cannot be both set" with pytest.raises(Exception, match=re_err): + class ReporterType(SQLAlchemyObjectType): class Meta: model = Reporter @@ -372,14 +396,14 @@ def test_resolvers(session): class ReporterMixin(object): def resolve_id(root, _info): - return 'ID' + return "ID" class ReporterType(ReporterMixin, SQLAlchemyObjectType): class Meta: model = Reporter email = ORMField() - email_v2 = ORMField(model_attr='email') + email_v2 = ORMField(model_attr="email") favorite_pet_kind = Field(String) favorite_pet_kind_v2 = Field(String) @@ -387,10 +411,10 @@ def resolve_last_name(root, _info): return root.last_name.upper() def resolve_email_v2(root, _info): - return root.email + '_V2' + return root.email + "_V2" def resolve_favorite_pet_kind_v2(root, _info): - return str(root.favorite_pet_kind) + '_V2' + return str(root.favorite_pet_kind) + "_V2" class Query(ObjectType): reporter = Field(ReporterType) @@ -398,12 +422,18 @@ class Query(ObjectType): def resolve_reporter(self, _info): return session.query(Reporter).first() - reporter = Reporter(first_name='first_name', last_name='last_name', email='email', favorite_pet_kind='cat') + reporter = Reporter( + first_name="first_name", + last_name="last_name", + email="email", + favorite_pet_kind="cat", + ) session.add(reporter) session.commit() schema = Schema(query=Query) - result = schema.execute(""" + result = schema.execute( + """ query { reporter { id @@ -415,27 +445,29 @@ def resolve_reporter(self, _info): favoritePetKindV2 } } - """) + """ + ) assert not result.errors # Custom resolver on a base class - assert result.data['reporter']['id'] == 'ID' + assert result.data["reporter"]["id"] == "ID" # Default field + default resolver - assert result.data['reporter']['firstName'] == 'first_name' + assert result.data["reporter"]["firstName"] == "first_name" # Default field + custom resolver - assert result.data['reporter']['lastName'] == 'LAST_NAME' + assert result.data["reporter"]["lastName"] == "LAST_NAME" # ORMField + default resolver - assert result.data['reporter']['email'] == 'email' + assert result.data["reporter"]["email"] == "email" # ORMField + custom resolver - assert result.data['reporter']['emailV2'] == 'email_V2' + assert result.data["reporter"]["emailV2"] == "email_V2" # Field + default resolver - assert result.data['reporter']['favoritePetKind'] == 'cat' + assert result.data["reporter"]["favoritePetKind"] == "cat" # Field + custom resolver - assert result.data['reporter']['favoritePetKindV2'] == 'cat_V2' + assert result.data["reporter"]["favoritePetKindV2"] == "cat_V2" # Test Custom SQLAlchemyObjectType Implementation + def test_custom_objecttype_registered(): class CustomSQLAlchemyObjectType(SQLAlchemyObjectType): class Meta: @@ -463,9 +495,9 @@ class Meta: def __init_subclass_with_meta__(cls, custom_option=None, **options): _meta = CustomOptions(cls) _meta.custom_option = custom_option - super(SQLAlchemyObjectTypeWithCustomOptions, cls).__init_subclass_with_meta__( - _meta=_meta, **options - ) + super( + SQLAlchemyObjectTypeWithCustomOptions, cls + ).__init_subclass_with_meta__(_meta=_meta, **options) class ReporterWithCustomOptions(SQLAlchemyObjectTypeWithCustomOptions): class Meta: @@ -479,6 +511,7 @@ class Meta: # Tests for connection_field_factory + class _TestSQLAlchemyConnectionField(SQLAlchemyConnectionField): pass @@ -494,7 +527,9 @@ class Meta: model = Article interfaces = (Node,) - assert isinstance(ReporterType._meta.fields['articles'].type(), UnsortedSQLAlchemyConnectionField) + assert isinstance( + ReporterType._meta.fields["articles"].type(), UnsortedSQLAlchemyConnectionField + ) def test_custom_connection_field_factory(): @@ -514,7 +549,9 @@ class Meta: model = Article interfaces = (Node,) - assert isinstance(ReporterType._meta.fields['articles'].type(), _TestSQLAlchemyConnectionField) + assert isinstance( + ReporterType._meta.fields["articles"].type(), _TestSQLAlchemyConnectionField + ) def test_deprecated_registerConnectionFieldFactory(): @@ -531,7 +568,9 @@ class Meta: model = Article interfaces = (Node,) - assert isinstance(ReporterType._meta.fields['articles'].type(), _TestSQLAlchemyConnectionField) + assert isinstance( + ReporterType._meta.fields["articles"].type(), _TestSQLAlchemyConnectionField + ) def test_deprecated_unregisterConnectionFieldFactory(): @@ -549,7 +588,9 @@ class Meta: model = Article interfaces = (Node,) - assert not isinstance(ReporterType._meta.fields['articles'].type(), _TestSQLAlchemyConnectionField) + assert not isinstance( + ReporterType._meta.fields["articles"].type(), _TestSQLAlchemyConnectionField + ) def test_deprecated_createConnectionField(): @@ -557,7 +598,7 @@ def test_deprecated_createConnectionField(): createConnectionField(None) -@mock.patch(utils.__name__ + '.class_mapper') +@mock.patch(utils.__name__ + ".class_mapper") def test_unique_errors_propagate(class_mapper_mock): # Define unique error to detect class UniqueError(Exception): @@ -569,9 +610,11 @@ class UniqueError(Exception): # Make sure that errors are propagated from class_mapper when instantiating new classes error = None try: + class ArticleOne(SQLAlchemyObjectType): class Meta(object): model = Article + except UniqueError as e: error = e @@ -580,7 +623,7 @@ class Meta(object): assert isinstance(error, UniqueError) -@mock.patch(utils.__name__ + '.class_mapper') +@mock.patch(utils.__name__ + ".class_mapper") def test_argument_errors_propagate(class_mapper_mock): # Mock class_mapper effect class_mapper_mock.side_effect = sqlalchemy.exc.ArgumentError @@ -588,9 +631,11 @@ def test_argument_errors_propagate(class_mapper_mock): # Make sure that errors are propagated from class_mapper when instantiating new classes error = None try: + class ArticleTwo(SQLAlchemyObjectType): class Meta(object): model = Article + except sqlalchemy.exc.ArgumentError as e: error = e @@ -599,7 +644,7 @@ class Meta(object): assert isinstance(error, sqlalchemy.exc.ArgumentError) -@mock.patch(utils.__name__ + '.class_mapper') +@mock.patch(utils.__name__ + ".class_mapper") def test_unmapped_errors_reformat(class_mapper_mock): # Mock class_mapper effect class_mapper_mock.side_effect = sqlalchemy.orm.exc.UnmappedClassError(object) @@ -607,9 +652,11 @@ def test_unmapped_errors_reformat(class_mapper_mock): # Make sure that errors are propagated from class_mapper when instantiating new classes error = None try: + class ArticleThree(SQLAlchemyObjectType): class Meta(object): model = Article + except ValueError as e: error = e diff --git a/graphene_sqlalchemy/tests/test_utils.py b/graphene_sqlalchemy/tests/test_utils.py index e13d919c..b0afe41c 100644 --- a/graphene_sqlalchemy/tests/test_utils.py +++ b/graphene_sqlalchemy/tests/test_utils.py @@ -3,8 +3,13 @@ from graphene import Enum, List, ObjectType, Schema, String -from ..utils import (get_session, sort_argument_for_model, sort_enum_for_model, - to_enum_value_name, to_type_name) +from ..utils import ( + get_session, + sort_argument_for_model, + sort_enum_for_model, + to_enum_value_name, + to_type_name, +) from .models import Base, Editor, Pet @@ -96,6 +101,7 @@ class MultiplePK(Base): with pytest.warns(DeprecationWarning): arg = sort_argument_for_model(MultiplePK) - assert set(arg.default_value) == set( - (MultiplePK.foo.name + "_asc", MultiplePK.bar.name + "_asc") - ) + assert set(arg.default_value) == { + MultiplePK.foo.name + "_asc", + MultiplePK.bar.name + "_asc", + } diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index ac69b697..fe48e9eb 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -2,8 +2,7 @@ import sqlalchemy from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.orm import (ColumnProperty, CompositeProperty, - RelationshipProperty) +from sqlalchemy.orm import ColumnProperty, CompositeProperty, RelationshipProperty from sqlalchemy.orm.exc import NoResultFound from graphene import Field @@ -12,12 +11,17 @@ from graphene.types.utils import yank_fields_from_attrs from graphene.utils.orderedtype import OrderedType -from .converter import (convert_sqlalchemy_column, - convert_sqlalchemy_composite, - convert_sqlalchemy_hybrid_method, - convert_sqlalchemy_relationship) -from .enums import (enum_for_field, sort_argument_for_object_type, - sort_enum_for_object_type) +from .converter import ( + convert_sqlalchemy_column, + convert_sqlalchemy_composite, + convert_sqlalchemy_hybrid_method, + convert_sqlalchemy_relationship, +) +from .enums import ( + enum_for_field, + sort_argument_for_object_type, + sort_enum_for_object_type, +) from .registry import Registry, get_global_registry from .resolvers import get_attr_resolver, get_custom_resolver from .utils import get_query, is_mapped_class, is_mapped_instance @@ -76,20 +80,28 @@ class Meta: super(ORMField, self).__init__(_creation_counter=_creation_counter) # The is only useful for documentation and auto-completion common_kwargs = { - 'model_attr': model_attr, - 'type_': type_, - 'required': required, - 'description': description, - 'deprecation_reason': deprecation_reason, - 'batching': batching, + "model_attr": model_attr, + "type_": type_, + "required": required, + "description": description, + "deprecation_reason": deprecation_reason, + "batching": batching, + } + common_kwargs = { + kwarg: value for kwarg, value in common_kwargs.items() if value is not None } - common_kwargs = {kwarg: value for kwarg, value in common_kwargs.items() if value is not None} self.kwargs = field_kwargs self.kwargs.update(common_kwargs) def construct_fields( - obj_type, model, registry, only_fields, exclude_fields, batching, connection_field_factory + obj_type, + model, + registry, + only_fields, + exclude_fields, + batching, + connection_field_factory, ): """ Construct all the fields for a SQLAlchemyObjectType. @@ -110,17 +122,22 @@ def construct_fields( inspected_model = sqlalchemy.inspect(model) # Gather all the relevant attributes from the SQLAlchemy model in order all_model_attrs = OrderedDict( - inspected_model.column_attrs.items() + - inspected_model.composites.items() + - [(name, item) for name, item in inspected_model.all_orm_descriptors.items() - if isinstance(item, hybrid_property)] + - inspected_model.relationships.items() + inspected_model.column_attrs.items() + + inspected_model.composites.items() + + [ + (name, item) + for name, item in inspected_model.all_orm_descriptors.items() + if isinstance(item, hybrid_property) + ] + + inspected_model.relationships.items() ) # Filter out excluded fields auto_orm_field_names = [] for attr_name, attr in all_model_attrs.items(): - if (only_fields and attr_name not in only_fields) or (attr_name in exclude_fields): + if (only_fields and attr_name not in only_fields) or ( + attr_name in exclude_fields + ): continue auto_orm_field_names.append(attr_name) @@ -135,13 +152,15 @@ def construct_fields( # Set the model_attr if not set for orm_field_name, orm_field in custom_orm_fields_items: - attr_name = orm_field.kwargs.get('model_attr', orm_field_name) + attr_name = orm_field.kwargs.get("model_attr", orm_field_name) if attr_name not in all_model_attrs: - raise ValueError(( - "Cannot map ORMField to a model attribute.\n" - "Field: '{}.{}'" - ).format(obj_type.__name__, orm_field_name,)) - orm_field.kwargs['model_attr'] = attr_name + raise ValueError( + ("Cannot map ORMField to a model attribute.\n" "Field: '{}.{}'").format( + obj_type.__name__, + orm_field_name, + ) + ) + orm_field.kwargs["model_attr"] = attr_name # Merge automatic fields with custom ORM fields orm_fields = OrderedDict(custom_orm_fields_items) @@ -153,27 +172,38 @@ def construct_fields( # Build all the field dictionary fields = OrderedDict() for orm_field_name, orm_field in orm_fields.items(): - attr_name = orm_field.kwargs.pop('model_attr') + attr_name = orm_field.kwargs.pop("model_attr") attr = all_model_attrs[attr_name] - resolver = get_custom_resolver(obj_type, orm_field_name) or get_attr_resolver(obj_type, attr_name) + resolver = get_custom_resolver(obj_type, orm_field_name) or get_attr_resolver( + obj_type, attr_name + ) if isinstance(attr, ColumnProperty): - field = convert_sqlalchemy_column(attr, registry, resolver, **orm_field.kwargs) + field = convert_sqlalchemy_column( + attr, registry, resolver, **orm_field.kwargs + ) elif isinstance(attr, RelationshipProperty): - batching_ = orm_field.kwargs.pop('batching', batching) + batching_ = orm_field.kwargs.pop("batching", batching) field = convert_sqlalchemy_relationship( - attr, obj_type, connection_field_factory, batching_, orm_field_name, **orm_field.kwargs) + attr, + obj_type, + connection_field_factory, + batching_, + orm_field_name, + **orm_field.kwargs + ) elif isinstance(attr, CompositeProperty): if attr_name != orm_field_name or orm_field.kwargs: # TODO Add a way to override composite property fields raise ValueError( "ORMField kwargs for composite fields must be empty. " - "Field: {}.{}".format(obj_type.__name__, orm_field_name)) + "Field: {}.{}".format(obj_type.__name__, orm_field_name) + ) field = convert_sqlalchemy_composite(attr, registry, resolver) elif isinstance(attr, hybrid_property): field = convert_sqlalchemy_hybrid_method(attr, resolver, **orm_field.kwargs) else: - raise Exception('Property type is not supported') # Should never happen + raise Exception("Property type is not supported") # Should never happen registry.register_orm_field(obj_type, orm_field_name, attr) fields[orm_field_name] = field @@ -210,7 +240,8 @@ def __init_subclass_with_meta__( # Make sure model is a valid SQLAlchemy model if not is_mapped_class(model): raise ValueError( - "You need to pass a valid SQLAlchemy Model in " '{}.Meta, received "{}".'.format(cls.__name__, model) + "You need to pass a valid SQLAlchemy Model in " + '{}.Meta, received "{}".'.format(cls.__name__, model) ) if not registry: @@ -222,7 +253,9 @@ def __init_subclass_with_meta__( ).format(cls.__name__, registry) if only_fields and exclude_fields: - raise ValueError("The options 'only_fields' and 'exclude_fields' cannot be both set on the same type.") + raise ValueError( + "The options 'only_fields' and 'exclude_fields' cannot be both set on the same type." + ) sqla_fields = yank_fields_from_attrs( construct_fields( @@ -240,7 +273,7 @@ def __init_subclass_with_meta__( if use_connection is None and interfaces: use_connection = any( - (issubclass(interface, Node) for interface in interfaces) + issubclass(interface, Node) for interface in interfaces ) if use_connection and not connection: diff --git a/graphene_sqlalchemy/utils.py b/graphene_sqlalchemy/utils.py index 084f9b86..b42580b1 100644 --- a/graphene_sqlalchemy/utils.py +++ b/graphene_sqlalchemy/utils.py @@ -155,7 +155,9 @@ def sort_argument_for_model(cls, has_default=True): def is_sqlalchemy_version_less_than(version_string): """Check the installed SQLAlchemy version""" - return pkg_resources.get_distribution('SQLAlchemy').parsed_version < pkg_resources.parse_version(version_string) + return pkg_resources.get_distribution( + "SQLAlchemy" + ).parsed_version < pkg_resources.parse_version(version_string) class singledispatchbymatchfunction: @@ -179,7 +181,6 @@ def __call__(self, *args, **kwargs): return self.default(*args, **kwargs) def register(self, matcher_function: Callable[[Any], bool]): - def grab_function_from_outside(f): self.registry[matcher_function] = f return self @@ -189,7 +190,7 @@ def grab_function_from_outside(f): def value_equals(value): """A simple function that makes the equality based matcher functions for - SingleDispatchByMatchFunction prettier""" + SingleDispatchByMatchFunction prettier""" return lambda x: x == value @@ -199,11 +200,17 @@ def safe_isinstance_checker(arg): return isinstance(arg, cls) except TypeError: pass + return safe_isinstance_checker def registry_sqlalchemy_model_from_str(model_name: str) -> Optional[Any]: try: - return next(filter(lambda x: x.__name__ == model_name, list(get_global_registry()._registry.keys()))) + return next( + filter( + lambda x: x.__name__ == model_name, + list(get_global_registry()._registry.keys()), + ) + ) except StopIteration: pass From 8abff09832bcafc3e0304f316e90c7e13ea90eba Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Tue, 31 May 2022 12:05:53 +0200 Subject: [PATCH 06/10] Add flake8 --- .flake8 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..ccded588 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +ignore = E203,W503 +exclude = .git,.mypy_cache,.pytest_cache,.tox,.venv,__pycache__,build,dist,docs +max-line-length = 88 From 924c1fb781a992431cc8ef8fb964bce88f1a1ea4 Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Fri, 3 Jun 2022 12:01:48 +0200 Subject: [PATCH 07/10] Revert "Add Black, pyupgrade to pre-commit & apply to all files" This reverts commit ec7daecc285b53b92cc5459f94e875bb533ca5d0. --- .pre-commit-config.yaml | 15 +- docs/conf.py | 87 ++-- examples/flask_sqlalchemy/database.py | 23 +- examples/flask_sqlalchemy/models.py | 22 +- examples/flask_sqlalchemy/schema.py | 9 +- examples/nameko_sqlalchemy/app.py | 76 ++-- examples/nameko_sqlalchemy/database.py | 23 +- examples/nameko_sqlalchemy/models.py | 22 +- examples/nameko_sqlalchemy/service.py | 4 +- graphene_sqlalchemy/batching.py | 12 +- graphene_sqlalchemy/converter.py | 142 +++---- graphene_sqlalchemy/enums.py | 16 +- graphene_sqlalchemy/fields.py | 22 +- graphene_sqlalchemy/resolvers.py | 2 +- graphene_sqlalchemy/tests/conftest.py | 2 +- graphene_sqlalchemy/tests/models.py | 43 +- graphene_sqlalchemy/tests/test_batching.py | 392 ++++++++---------- graphene_sqlalchemy/tests/test_benchmark.py | 84 ++-- graphene_sqlalchemy/tests/test_converter.py | 164 +++----- graphene_sqlalchemy/tests/test_enums.py | 29 +- graphene_sqlalchemy/tests/test_fields.py | 5 +- graphene_sqlalchemy/tests/test_query.py | 22 +- graphene_sqlalchemy/tests/test_query_enums.py | 47 ++- graphene_sqlalchemy/tests/test_reflected.py | 1 + graphene_sqlalchemy/tests/test_sort_enums.py | 2 +- graphene_sqlalchemy/tests/test_types.py | 309 ++++++-------- graphene_sqlalchemy/tests/test_utils.py | 16 +- graphene_sqlalchemy/types.py | 109 ++--- graphene_sqlalchemy/utils.py | 15 +- 29 files changed, 710 insertions(+), 1005 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00082f7b..d76c4a0a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,15 +11,12 @@ repos: exclude: ^docs/.*$ - id: trailing-whitespace exclude: README.md - - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 - hooks: - - id: pyupgrade - - repo: https://github.com/ambv/black - rev: 22.3.0 - hooks: - - id: black - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + rev: 4.0.0 hooks: - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.10.1 + hooks: + - id: isort + name: isort (python) diff --git a/docs/conf.py b/docs/conf.py index 9c9fc1d7..3fa6391d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ import os -on_rtd = os.environ.get("READTHEDOCS", None) == "True" +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # -*- coding: utf-8 -*- # @@ -34,46 +34,46 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.viewcode", + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', ] if not on_rtd: extensions += [ - "sphinx.ext.githubpages", + 'sphinx.ext.githubpages', ] # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = ".rst" +source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = "index" +master_doc = 'index' # General information about the project. -project = "Graphene Django" -copyright = "Graphene 2016" -author = "Syrus Akbary" +project = u'Graphene Django' +copyright = u'Graphene 2016' +author = u'Syrus Akbary' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = "1.0" +version = u'1.0' # The full version, including alpha/beta/rc tags. -release = "1.0.dev" +release = u'1.0.dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -94,7 +94,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -116,7 +116,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" +pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -175,7 +175,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -255,30 +255,34 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = "Graphenedoc" +htmlhelp_basename = 'Graphenedoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "Graphene.tex", "Graphene Documentation", "Syrus Akbary", "manual"), + (master_doc, 'Graphene.tex', u'Graphene Documentation', + u'Syrus Akbary', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -319,7 +323,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, "graphene_django", "Graphene Django Documentation", [author], 1) + (master_doc, 'graphene_django', u'Graphene Django Documentation', + [author], 1) ] # If true, show URL addresses after external links. @@ -333,15 +338,9 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ( - master_doc, - "Graphene-Django", - "Graphene Django Documentation", - author, - "Graphene Django", - "One line description of project.", - "Miscellaneous", - ), + (master_doc, 'Graphene-Django', u'Graphene Django Documentation', + author, 'Graphene Django', 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. @@ -415,7 +414,7 @@ # epub_post_files = [] # A list of files that should not be packed into the epub file. -epub_exclude_files = ["search.html"] +epub_exclude_files = ['search.html'] # The depth of the table of contents in toc.ncx. # @@ -447,4 +446,4 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"https://docs.python.org/": None} +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/examples/flask_sqlalchemy/database.py b/examples/flask_sqlalchemy/database.py index 74ec7ca9..ca4d4122 100644 --- a/examples/flask_sqlalchemy/database.py +++ b/examples/flask_sqlalchemy/database.py @@ -2,10 +2,10 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker -engine = create_engine("sqlite:///database.sqlite3", convert_unicode=True) -db_session = scoped_session( - sessionmaker(autocommit=False, autoflush=False, bind=engine) -) +engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True) +db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) Base = declarative_base() Base.query = db_session.query_property() @@ -15,25 +15,24 @@ def init_db(): # they will be registered properly on the metadata. Otherwise # you will have to import them first before calling init_db() from models import Department, Employee, Role - Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) # Create the fixtures - engineering = Department(name="Engineering") + engineering = Department(name='Engineering') db_session.add(engineering) - hr = Department(name="Human Resources") + hr = Department(name='Human Resources') db_session.add(hr) - manager = Role(name="manager") + manager = Role(name='manager') db_session.add(manager) - engineer = Role(name="engineer") + engineer = Role(name='engineer') db_session.add(engineer) - peter = Employee(name="Peter", department=engineering, role=engineer) + peter = Employee(name='Peter', department=engineering, role=engineer) db_session.add(peter) - roy = Employee(name="Roy", department=engineering, role=engineer) + roy = Employee(name='Roy', department=engineering, role=engineer) db_session.add(roy) - tracy = Employee(name="Tracy", department=hr, role=manager) + tracy = Employee(name='Tracy', department=hr, role=manager) db_session.add(tracy) db_session.commit() diff --git a/examples/flask_sqlalchemy/models.py b/examples/flask_sqlalchemy/models.py index 38f0fd0a..efbbe690 100644 --- a/examples/flask_sqlalchemy/models.py +++ b/examples/flask_sqlalchemy/models.py @@ -4,31 +4,35 @@ class Department(Base): - __tablename__ = "department" + __tablename__ = 'department' id = Column(Integer, primary_key=True) name = Column(String) class Role(Base): - __tablename__ = "roles" + __tablename__ = 'roles' role_id = Column(Integer, primary_key=True) name = Column(String) class Employee(Base): - __tablename__ = "employee" + __tablename__ = 'employee' id = Column(Integer, primary_key=True) name = Column(String) # Use default=func.now() to set the default hiring time # of an Employee to be the current time when an # Employee record was created hired_on = Column(DateTime, default=func.now()) - department_id = Column(Integer, ForeignKey("department.id")) - role_id = Column(Integer, ForeignKey("roles.role_id")) + department_id = Column(Integer, ForeignKey('department.id')) + role_id = Column(Integer, ForeignKey('roles.role_id')) # Use cascade='delete,all' to propagate the deletion of a Department onto its Employees department = relationship( - Department, backref=backref("employees", uselist=True, cascade="delete,all") - ) + Department, + backref=backref('employees', + uselist=True, + cascade='delete,all')) role = relationship( - Role, backref=backref("roles", uselist=True, cascade="delete,all") - ) + Role, + backref=backref('roles', + uselist=True, + cascade='delete,all')) diff --git a/examples/flask_sqlalchemy/schema.py b/examples/flask_sqlalchemy/schema.py index c4a91e63..ea525e3b 100644 --- a/examples/flask_sqlalchemy/schema.py +++ b/examples/flask_sqlalchemy/schema.py @@ -10,27 +10,26 @@ class Department(SQLAlchemyObjectType): class Meta: model = DepartmentModel - interfaces = (relay.Node,) + interfaces = (relay.Node, ) class Employee(SQLAlchemyObjectType): class Meta: model = EmployeeModel - interfaces = (relay.Node,) + interfaces = (relay.Node, ) class Role(SQLAlchemyObjectType): class Meta: model = RoleModel - interfaces = (relay.Node,) + interfaces = (relay.Node, ) class Query(graphene.ObjectType): node = relay.Node.Field() # Allow only single column sorting all_employees = SQLAlchemyConnectionField( - Employee.connection, sort=Employee.sort_argument() - ) + Employee.connection, sort=Employee.sort_argument()) # Allows sorting over multiple columns, by default over the primary key all_roles = SQLAlchemyConnectionField(Role.connection) # Disable sorting over this field diff --git a/examples/nameko_sqlalchemy/app.py b/examples/nameko_sqlalchemy/app.py index 64d305ea..05352529 100755 --- a/examples/nameko_sqlalchemy/app.py +++ b/examples/nameko_sqlalchemy/app.py @@ -1,45 +1,37 @@ from database import db_session, init_db from schema import schema -from graphql_server import ( - HttpQueryError, - default_format_error, - encode_execution_results, - json_encode, - load_json_body, - run_http_query, -) - - -class App: - def __init__(self): - init_db() - - def query(self, request): - data = self.parse_body(request) - execution_results, params = run_http_query(schema, "post", data) - result, status_code = encode_execution_results( - execution_results, - format_error=default_format_error, - is_batch=False, - encode=json_encode, - ) - return result - - def parse_body(self, request): - # We use mimetype here since we don't need the other - # information provided by content_type - content_type = request.mimetype - if content_type == "application/graphql": - return {"query": request.data.decode("utf8")} - - elif content_type == "application/json": - return load_json_body(request.data.decode("utf8")) - - elif content_type in ( - "application/x-www-form-urlencoded", - "multipart/form-data", - ): - return request.form - - return {} +from graphql_server import (HttpQueryError, default_format_error, + encode_execution_results, json_encode, + load_json_body, run_http_query) + + +class App(): + def __init__(self): + init_db() + + def query(self, request): + data = self.parse_body(request) + execution_results, params = run_http_query( + schema, + 'post', + data) + result, status_code = encode_execution_results( + execution_results, + format_error=default_format_error,is_batch=False, encode=json_encode) + return result + + def parse_body(self,request): + # We use mimetype here since we don't need the other + # information provided by content_type + content_type = request.mimetype + if content_type == 'application/graphql': + return {'query': request.data.decode('utf8')} + + elif content_type == 'application/json': + return load_json_body(request.data.decode('utf8')) + + elif content_type in ('application/x-www-form-urlencoded', 'multipart/form-data'): + return request.form + + return {} diff --git a/examples/nameko_sqlalchemy/database.py b/examples/nameko_sqlalchemy/database.py index 74ec7ca9..ca4d4122 100644 --- a/examples/nameko_sqlalchemy/database.py +++ b/examples/nameko_sqlalchemy/database.py @@ -2,10 +2,10 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker -engine = create_engine("sqlite:///database.sqlite3", convert_unicode=True) -db_session = scoped_session( - sessionmaker(autocommit=False, autoflush=False, bind=engine) -) +engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True) +db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) Base = declarative_base() Base.query = db_session.query_property() @@ -15,25 +15,24 @@ def init_db(): # they will be registered properly on the metadata. Otherwise # you will have to import them first before calling init_db() from models import Department, Employee, Role - Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) # Create the fixtures - engineering = Department(name="Engineering") + engineering = Department(name='Engineering') db_session.add(engineering) - hr = Department(name="Human Resources") + hr = Department(name='Human Resources') db_session.add(hr) - manager = Role(name="manager") + manager = Role(name='manager') db_session.add(manager) - engineer = Role(name="engineer") + engineer = Role(name='engineer') db_session.add(engineer) - peter = Employee(name="Peter", department=engineering, role=engineer) + peter = Employee(name='Peter', department=engineering, role=engineer) db_session.add(peter) - roy = Employee(name="Roy", department=engineering, role=engineer) + roy = Employee(name='Roy', department=engineering, role=engineer) db_session.add(roy) - tracy = Employee(name="Tracy", department=hr, role=manager) + tracy = Employee(name='Tracy', department=hr, role=manager) db_session.add(tracy) db_session.commit() diff --git a/examples/nameko_sqlalchemy/models.py b/examples/nameko_sqlalchemy/models.py index 38f0fd0a..efbbe690 100644 --- a/examples/nameko_sqlalchemy/models.py +++ b/examples/nameko_sqlalchemy/models.py @@ -4,31 +4,35 @@ class Department(Base): - __tablename__ = "department" + __tablename__ = 'department' id = Column(Integer, primary_key=True) name = Column(String) class Role(Base): - __tablename__ = "roles" + __tablename__ = 'roles' role_id = Column(Integer, primary_key=True) name = Column(String) class Employee(Base): - __tablename__ = "employee" + __tablename__ = 'employee' id = Column(Integer, primary_key=True) name = Column(String) # Use default=func.now() to set the default hiring time # of an Employee to be the current time when an # Employee record was created hired_on = Column(DateTime, default=func.now()) - department_id = Column(Integer, ForeignKey("department.id")) - role_id = Column(Integer, ForeignKey("roles.role_id")) + department_id = Column(Integer, ForeignKey('department.id')) + role_id = Column(Integer, ForeignKey('roles.role_id')) # Use cascade='delete,all' to propagate the deletion of a Department onto its Employees department = relationship( - Department, backref=backref("employees", uselist=True, cascade="delete,all") - ) + Department, + backref=backref('employees', + uselist=True, + cascade='delete,all')) role = relationship( - Role, backref=backref("roles", uselist=True, cascade="delete,all") - ) + Role, + backref=backref('roles', + uselist=True, + cascade='delete,all')) diff --git a/examples/nameko_sqlalchemy/service.py b/examples/nameko_sqlalchemy/service.py index 7f4c5078..d9c519c9 100644 --- a/examples/nameko_sqlalchemy/service.py +++ b/examples/nameko_sqlalchemy/service.py @@ -4,8 +4,8 @@ class DepartmentService: - name = "department" + name = 'department' - @http("POST", "/graphql") + @http('POST', '/graphql') def query(self, request): return App().query(request) diff --git a/graphene_sqlalchemy/batching.py b/graphene_sqlalchemy/batching.py index f4a80e20..85cc8855 100644 --- a/graphene_sqlalchemy/batching.py +++ b/graphene_sqlalchemy/batching.py @@ -10,9 +10,7 @@ def get_batch_resolver(relationship_prop): # Cache this across `batch_load_fn` calls # This is so SQL string generation is cached under-the-hood via `bakery` - selectin_loader = strategies.SelectInLoader( - relationship_prop, (("lazy", "selectin"),) - ) + selectin_loader = strategies.SelectInLoader(relationship_prop, (('lazy', 'selectin'),)) class RelationshipLoader(aiodataloader.DataLoader): cache = False @@ -57,19 +55,19 @@ async def batch_load_fn(self, parents): # For our purposes, the query_context will only used to get the session query_context = None - if is_sqlalchemy_version_less_than("1.4"): + if is_sqlalchemy_version_less_than('1.4'): query_context = QueryContext(session.query(parent_mapper.entity)) else: parent_mapper_query = session.query(parent_mapper.entity) query_context = parent_mapper_query._compile_context() - if is_sqlalchemy_version_less_than("1.4"): + if is_sqlalchemy_version_less_than('1.4'): selectin_loader._load_for_path( query_context, parent_mapper._path_registry, states, None, - child_mapper, + child_mapper ) else: selectin_loader._load_for_path( @@ -78,7 +76,7 @@ async def batch_load_fn(self, parents): states, None, child_mapper, - None, + None ) return [getattr(parent, relationship_prop.key) for parent in parents] diff --git a/graphene_sqlalchemy/converter.py b/graphene_sqlalchemy/converter.py index 26fb0d58..60e14ddd 100644 --- a/graphene_sqlalchemy/converter.py +++ b/graphene_sqlalchemy/converter.py @@ -9,33 +9,18 @@ from sqlalchemy.dialects import postgresql from sqlalchemy.orm import interfaces, strategies -from graphene import ( - ID, - Boolean, - Date, - DateTime, - Dynamic, - Enum, - Field, - Float, - Int, - List, - String, - Time, -) +from graphene import (ID, Boolean, Date, DateTime, Dynamic, Enum, Field, Float, + Int, List, String, Time) from graphene.types.json import JSONString from .batching import get_batch_resolver from .enums import enum_for_sa_enum -from .fields import BatchSQLAlchemyConnectionField, default_connection_field_factory +from .fields import (BatchSQLAlchemyConnectionField, + default_connection_field_factory) from .registry import get_global_registry from .resolvers import get_attr_resolver, get_custom_resolver -from .utils import ( - registry_sqlalchemy_model_from_str, - safe_isinstance, - singledispatchbymatchfunction, - value_equals, -) +from .utils import (registry_sqlalchemy_model_from_str, safe_isinstance, + singledispatchbymatchfunction, value_equals) try: from typing import ForwardRef @@ -44,7 +29,8 @@ from typing import _ForwardRef as ForwardRef try: - from sqlalchemy_utils import ChoiceType, JSONType, ScalarListType, TSVectorType + from sqlalchemy_utils import (ChoiceType, JSONType, ScalarListType, + TSVectorType) except ImportError: ChoiceType = JSONType = ScalarListType = TSVectorType = object @@ -53,7 +39,7 @@ except ImportError: EnumTypeImpl = object -is_selectin_available = getattr(strategies, "SelectInLoader", None) +is_selectin_available = getattr(strategies, 'SelectInLoader', None) def get_column_doc(column): @@ -64,14 +50,8 @@ def is_column_nullable(column): return bool(getattr(column, "nullable", True)) -def convert_sqlalchemy_relationship( - relationship_prop, - obj_type, - connection_field_factory, - batching, - orm_field_name, - **field_kwargs, -): +def convert_sqlalchemy_relationship(relationship_prop, obj_type, connection_field_factory, batching, + orm_field_name, **field_kwargs): """ :param sqlalchemy.RelationshipProperty relationship_prop: :param SQLAlchemyObjectType obj_type: @@ -85,34 +65,24 @@ def convert_sqlalchemy_relationship( def dynamic_type(): """:rtype: Field|None""" direction = relationship_prop.direction - child_type = obj_type._meta.registry.get_type_for_model( - relationship_prop.mapper.entity - ) + child_type = obj_type._meta.registry.get_type_for_model(relationship_prop.mapper.entity) batching_ = batching if is_selectin_available else False if not child_type: return None if direction == interfaces.MANYTOONE or not relationship_prop.uselist: - return _convert_o2o_or_m2o_relationship( - relationship_prop, obj_type, batching_, orm_field_name, **field_kwargs - ) + return _convert_o2o_or_m2o_relationship(relationship_prop, obj_type, batching_, orm_field_name, + **field_kwargs) if direction in (interfaces.ONETOMANY, interfaces.MANYTOMANY): - return _convert_o2m_or_m2m_relationship( - relationship_prop, - obj_type, - batching_, - connection_field_factory, - **field_kwargs, - ) + return _convert_o2m_or_m2m_relationship(relationship_prop, obj_type, batching_, + connection_field_factory, **field_kwargs) return Dynamic(dynamic_type) -def _convert_o2o_or_m2o_relationship( - relationship_prop, obj_type, batching, orm_field_name, **field_kwargs -): +def _convert_o2o_or_m2o_relationship(relationship_prop, obj_type, batching, orm_field_name, **field_kwargs): """ Convert one-to-one or many-to-one relationshsip. Return an object field. @@ -123,24 +93,17 @@ def _convert_o2o_or_m2o_relationship( :param dict field_kwargs: :rtype: Field """ - child_type = obj_type._meta.registry.get_type_for_model( - relationship_prop.mapper.entity - ) + child_type = obj_type._meta.registry.get_type_for_model(relationship_prop.mapper.entity) resolver = get_custom_resolver(obj_type, orm_field_name) if resolver is None: - resolver = ( - get_batch_resolver(relationship_prop) - if batching - else get_attr_resolver(obj_type, relationship_prop.key) - ) + resolver = get_batch_resolver(relationship_prop) if batching else \ + get_attr_resolver(obj_type, relationship_prop.key) return Field(child_type, resolver=resolver, **field_kwargs) -def _convert_o2m_or_m2m_relationship( - relationship_prop, obj_type, batching, connection_field_factory, **field_kwargs -): +def _convert_o2m_or_m2m_relationship(relationship_prop, obj_type, batching, connection_field_factory, **field_kwargs): """ Convert one-to-many or many-to-many relationshsip. Return a list field or a connection field. @@ -151,34 +114,30 @@ def _convert_o2m_or_m2m_relationship( :param dict field_kwargs: :rtype: Field """ - child_type = obj_type._meta.registry.get_type_for_model( - relationship_prop.mapper.entity - ) + child_type = obj_type._meta.registry.get_type_for_model(relationship_prop.mapper.entity) if not child_type._meta.connection: return Field(List(child_type), **field_kwargs) # TODO Allow override of connection_field_factory and resolver via ORMField if connection_field_factory is None: - connection_field_factory = ( - BatchSQLAlchemyConnectionField.from_relationship - if batching - else default_connection_field_factory - ) - - return connection_field_factory( - relationship_prop, obj_type._meta.registry, **field_kwargs - ) + connection_field_factory = BatchSQLAlchemyConnectionField.from_relationship if batching else \ + default_connection_field_factory + + return connection_field_factory(relationship_prop, obj_type._meta.registry, **field_kwargs) def convert_sqlalchemy_hybrid_method(hybrid_prop, resolver, **field_kwargs): - if "type_" not in field_kwargs: - field_kwargs["type_"] = convert_hybrid_property_return_type(hybrid_prop) + if 'type_' not in field_kwargs: + field_kwargs['type_'] = convert_hybrid_property_return_type(hybrid_prop) - if "description" not in field_kwargs: - field_kwargs["description"] = getattr(hybrid_prop, "__doc__", None) + if 'description' not in field_kwargs: + field_kwargs['description'] = getattr(hybrid_prop, "__doc__", None) - return Field(resolver=resolver, **field_kwargs) + return Field( + resolver=resolver, + **field_kwargs + ) def convert_sqlalchemy_composite(composite_prop, registry, resolver): @@ -218,14 +177,14 @@ def inner(fn): def convert_sqlalchemy_column(column_prop, registry, resolver, **field_kwargs): column = column_prop.columns[0] - field_kwargs.setdefault( - "type_", - convert_sqlalchemy_type(getattr(column, "type", None), column, registry), - ) - field_kwargs.setdefault("required", not is_column_nullable(column)) - field_kwargs.setdefault("description", get_column_doc(column)) + field_kwargs.setdefault('type_', convert_sqlalchemy_type(getattr(column, "type", None), column, registry)) + field_kwargs.setdefault('required', not is_column_nullable(column)) + field_kwargs.setdefault('description', get_column_doc(column)) - return Field(resolver=resolver, **field_kwargs) + return Field( + resolver=resolver, + **field_kwargs + ) @singledispatch @@ -253,7 +212,6 @@ def convert_column_to_string(type, column, registry=None): @convert_sqlalchemy_type.register(types.DateTime) def convert_column_to_datetime(type, column, registry=None): from graphene.types.datetime import DateTime - return DateTime @@ -305,9 +263,7 @@ def init_array_list_recursive(inner_type, n): @convert_sqlalchemy_type.register(postgresql.ARRAY) def convert_array_to_list(_type, column, registry=None): inner_type = convert_sqlalchemy_type(column.type.item_type, column) - return List( - init_array_list_recursive(inner_type, (column.type.dimensions or 1) - 1) - ) + return List(init_array_list_recursive(inner_type, (column.type.dimensions or 1) - 1)) @convert_sqlalchemy_type.register(postgresql.HSTORE) @@ -330,8 +286,8 @@ def convert_sqlalchemy_hybrid_property_type(arg: Any): # No valid type found, warn and fall back to graphene.String warnings.warn( - f'I don\'t know how to generate a GraphQL type out of a "{arg}" type.' - 'Falling back to "graphene.String"' + (f"I don't know how to generate a GraphQL type out of a \"{arg}\" type." + "Falling back to \"graphene.String\"") ) return String @@ -379,9 +335,7 @@ def convert_sqlalchemy_hybrid_property_type_time(arg): return Time -@convert_sqlalchemy_hybrid_property_type.register( - lambda x: getattr(x, "__origin__", None) == typing.Union -) +@convert_sqlalchemy_hybrid_property_type.register(lambda x: getattr(x, '__origin__', None) == typing.Union) def convert_sqlalchemy_hybrid_property_type_option_t(arg): # Option is actually Union[T, ] @@ -393,9 +347,7 @@ def convert_sqlalchemy_hybrid_property_type_option_t(arg): return graphql_internal_type -@convert_sqlalchemy_hybrid_property_type.register( - lambda x: getattr(x, "__origin__", None) in [list, typing.List] -) +@convert_sqlalchemy_hybrid_property_type.register(lambda x: getattr(x, '__origin__', None) in [list, typing.List]) def convert_sqlalchemy_hybrid_property_type_list_t(arg): # type is either list[T] or List[T], generic argument at __args__[0] internal_type = arg.__args__[0] @@ -433,6 +385,6 @@ def convert_sqlalchemy_hybrid_property_bare_str(arg): def convert_hybrid_property_return_type(hybrid_prop): # Grab the original method's return type annotations from inside the hybrid property - return_type_annotation = hybrid_prop.fget.__annotations__.get("return", str) + return_type_annotation = hybrid_prop.fget.__annotations__.get('return', str) return convert_sqlalchemy_hybrid_property_type(return_type_annotation) diff --git a/graphene_sqlalchemy/enums.py b/graphene_sqlalchemy/enums.py index 7e91a055..f100be19 100644 --- a/graphene_sqlalchemy/enums.py +++ b/graphene_sqlalchemy/enums.py @@ -18,7 +18,9 @@ def _convert_sa_to_graphene_enum(sa_enum, fallback_name=None): The Enum value names are converted to upper case if necessary. """ if not isinstance(sa_enum, SQLAlchemyEnumType): - raise TypeError("Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum)) + raise TypeError( + "Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum) + ) enum_class = sa_enum.enum_class if enum_class: if all(to_enum_value_name(key) == key for key in enum_class.__members__): @@ -43,7 +45,9 @@ def _convert_sa_to_graphene_enum(sa_enum, fallback_name=None): def enum_for_sa_enum(sa_enum, registry): """Return the Graphene Enum type for the specified SQLAlchemy Enum type.""" if not isinstance(sa_enum, SQLAlchemyEnumType): - raise TypeError("Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum)) + raise TypeError( + "Expected sqlalchemy.types.Enum, but got: {!r}".format(sa_enum) + ) enum = registry.get_graphene_enum_for_sa_enum(sa_enum) if not enum: enum = _convert_sa_to_graphene_enum(sa_enum) @@ -56,9 +60,11 @@ def enum_for_field(obj_type, field_name): from .types import SQLAlchemyObjectType if not isinstance(obj_type, type) or not issubclass(obj_type, SQLAlchemyObjectType): - raise TypeError("Expected SQLAlchemyObjectType, but got: {!r}".format(obj_type)) + raise TypeError( + "Expected SQLAlchemyObjectType, but got: {!r}".format(obj_type)) if not field_name or not isinstance(field_name, str): - raise TypeError("Expected a field name, but got: {!r}".format(field_name)) + raise TypeError( + "Expected a field name, but got: {!r}".format(field_name)) registry = obj_type._meta.registry orm_field = registry.get_orm_field_for_graphene_field(obj_type, field_name) if orm_field is None: @@ -160,7 +166,7 @@ def sort_argument_for_object_type( get_symbol_name=None, has_default=True, ): - """ "Returns Graphene Argument for sorting the given SQLAlchemyObjectType. + """"Returns Graphene Argument for sorting the given SQLAlchemyObjectType. Parameters - obj_type : SQLAlchemyObjectType diff --git a/graphene_sqlalchemy/fields.py b/graphene_sqlalchemy/fields.py index 6e4a1524..d7a83392 100644 --- a/graphene_sqlalchemy/fields.py +++ b/graphene_sqlalchemy/fields.py @@ -26,7 +26,9 @@ def type(self): assert issubclass(nullable_type, SQLAlchemyObjectType), ( "SQLALchemyConnectionField only accepts SQLAlchemyObjectType types, not {}" ).format(nullable_type.__name__) - assert nullable_type.connection, "The type {} doesn't have a connection".format( + assert ( + nullable_type.connection + ), "The type {} doesn't have a connection".format( nullable_type.__name__ ) assert type_ == nullable_type, ( @@ -146,11 +148,7 @@ def wrap_resolve(self, parent_resolver): def from_relationship(cls, relationship, registry, **field_kwargs): model = relationship.mapper.entity model_type = registry.get_type_for_model(model) - return cls( - model_type.connection, - resolver=get_batch_resolver(relationship), - **field_kwargs - ) + return cls(model_type.connection, resolver=get_batch_resolver(relationship), **field_kwargs) def default_connection_field_factory(relationship, registry, **field_kwargs): @@ -165,8 +163,8 @@ def default_connection_field_factory(relationship, registry, **field_kwargs): def createConnectionField(type_, **field_kwargs): warnings.warn( - "createConnectionField is deprecated and will be removed in the next " - "major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.", + 'createConnectionField is deprecated and will be removed in the next ' + 'major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.', DeprecationWarning, ) return __connectionFactory(type_, **field_kwargs) @@ -174,8 +172,8 @@ def createConnectionField(type_, **field_kwargs): def registerConnectionFieldFactory(factoryMethod): warnings.warn( - "registerConnectionFieldFactory is deprecated and will be removed in the next " - "major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.", + 'registerConnectionFieldFactory is deprecated and will be removed in the next ' + 'major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.', DeprecationWarning, ) global __connectionFactory @@ -184,8 +182,8 @@ def registerConnectionFieldFactory(factoryMethod): def unregisterConnectionFieldFactory(): warnings.warn( - "registerConnectionFieldFactory is deprecated and will be removed in the next " - "major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.", + 'registerConnectionFieldFactory is deprecated and will be removed in the next ' + 'major version. Use SQLAlchemyObjectType.Meta.connection_field_factory instead.', DeprecationWarning, ) global __connectionFactory diff --git a/graphene_sqlalchemy/resolvers.py b/graphene_sqlalchemy/resolvers.py index e8e61911..83a6e35d 100644 --- a/graphene_sqlalchemy/resolvers.py +++ b/graphene_sqlalchemy/resolvers.py @@ -7,7 +7,7 @@ def get_custom_resolver(obj_type, orm_field_name): does not have a `resolver`, we need to re-implement that logic here so users are able to override the default resolvers that we provide. """ - resolver = getattr(obj_type, "resolve_{}".format(orm_field_name), None) + resolver = getattr(obj_type, 'resolve_{}'.format(orm_field_name), None) if resolver: return get_unbound_function(resolver) diff --git a/graphene_sqlalchemy/tests/conftest.py b/graphene_sqlalchemy/tests/conftest.py index 357ad96e..34ba9d8a 100644 --- a/graphene_sqlalchemy/tests/conftest.py +++ b/graphene_sqlalchemy/tests/conftest.py @@ -8,7 +8,7 @@ from ..registry import reset_global_registry from .models import Base, CompositeFullName -test_db_url = "sqlite://" # use in-memory database for tests +test_db_url = 'sqlite://' # use in-memory database for tests @pytest.fixture(autouse=True) diff --git a/graphene_sqlalchemy/tests/models.py b/graphene_sqlalchemy/tests/models.py index 92d87c84..e41adb51 100644 --- a/graphene_sqlalchemy/tests/models.py +++ b/graphene_sqlalchemy/tests/models.py @@ -5,17 +5,8 @@ from decimal import Decimal from typing import List, Optional, Tuple -from sqlalchemy import ( - Column, - Date, - Enum, - ForeignKey, - Integer, - String, - Table, - func, - select, -) +from sqlalchemy import (Column, Date, Enum, ForeignKey, Integer, String, Table, + func, select) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import column_property, composite, mapper, relationship @@ -24,8 +15,8 @@ class HairKind(enum.Enum): - LONG = "long" - SHORT = "short" + LONG = 'long' + SHORT = 'short' Base = declarative_base() @@ -73,9 +64,7 @@ class Reporter(Base): last_name = Column(String(30), doc="Last name") email = Column(String(), doc="Email") favorite_pet_kind = Column(PetKind) - pets = relationship( - "Pet", secondary=association_table, backref="reporters", order_by="Pet.id" - ) + pets = relationship("Pet", secondary=association_table, backref="reporters", order_by="Pet.id") articles = relationship("Article", backref="reporter") favorite_article = relationship("Article", uselist=False) @@ -112,9 +101,7 @@ def hybrid_prop_list(self) -> List[int]: select([func.cast(func.count(id), Integer)]), doc="Column property" ) - composite_prop = composite( - CompositeFullName, first_name, last_name, doc="Composite" - ) + composite_prop = composite(CompositeFullName, first_name, last_name, doc="Composite") class Article(Base): @@ -150,7 +137,7 @@ class ShoppingCartItem(Base): id = Column(Integer(), primary_key=True) @hybrid_property - def hybrid_prop_shopping_cart(self) -> List["ShoppingCart"]: + def hybrid_prop_shopping_cart(self) -> List['ShoppingCart']: return [ShoppingCart(id=1)] @@ -205,17 +192,11 @@ def hybrid_prop_list_date(self) -> List[datetime.date]: @hybrid_property def hybrid_prop_nested_list_int(self) -> List[List[int]]: - return [ - self.hybrid_prop_list_int, - ] + return [self.hybrid_prop_list_int, ] @hybrid_property def hybrid_prop_deeply_nested_list_int(self) -> List[List[List[int]]]: - return [ - [ - self.hybrid_prop_list_int, - ], - ] + return [[self.hybrid_prop_list_int, ], ] # Other SQLAlchemy Instances @hybrid_property @@ -235,15 +216,15 @@ def hybrid_prop_unsupported_type_tuple(self) -> Tuple[str, str]: # Self-references @hybrid_property - def hybrid_prop_self_referential(self) -> "ShoppingCart": + def hybrid_prop_self_referential(self) -> 'ShoppingCart': return ShoppingCart(id=1) @hybrid_property - def hybrid_prop_self_referential_list(self) -> List["ShoppingCart"]: + def hybrid_prop_self_referential_list(self) -> List['ShoppingCart']: return [ShoppingCart(id=1)] # Optional[T] @hybrid_property - def hybrid_prop_optional_self_referential(self) -> Optional["ShoppingCart"]: + def hybrid_prop_optional_self_referential(self) -> Optional['ShoppingCart']: return None diff --git a/graphene_sqlalchemy/tests/test_batching.py b/graphene_sqlalchemy/tests/test_batching.py index d942e3fc..1896900b 100644 --- a/graphene_sqlalchemy/tests/test_batching.py +++ b/graphene_sqlalchemy/tests/test_batching.py @@ -7,7 +7,8 @@ import graphene from graphene import relay -from ..fields import BatchSQLAlchemyConnectionField, default_connection_field_factory +from ..fields import (BatchSQLAlchemyConnectionField, + default_connection_field_factory) from ..types import ORMField, SQLAlchemyObjectType from ..utils import is_sqlalchemy_version_less_than from .models import Article, HairKind, Pet, Reporter @@ -16,7 +17,6 @@ class MockLoggingHandler(logging.Handler): """Intercept and store log messages in a list.""" - def __init__(self, *args, **kwargs): self.messages = [] logging.Handler.__init__(self, *args, **kwargs) @@ -28,7 +28,7 @@ def emit(self, record): @contextlib.contextmanager def mock_sqlalchemy_logging_handler(): logging.basicConfig() - sql_logger = logging.getLogger("sqlalchemy.engine") + sql_logger = logging.getLogger('sqlalchemy.engine') previous_level = sql_logger.level sql_logger.setLevel(logging.INFO) @@ -65,16 +65,16 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_articles(self, info): - return info.context.get("session").query(Article).all() + return info.context.get('session').query(Article).all() def resolve_reporters(self, info): - return info.context.get("session").query(Reporter).all() + return info.context.get('session').query(Reporter).all() return graphene.Schema(query=Query) -if is_sqlalchemy_version_less_than("1.2"): - pytest.skip("SQL batching only works for SQLAlchemy 1.2+", allow_module_level=True) +if is_sqlalchemy_version_less_than('1.2'): + pytest.skip('SQL batching only works for SQLAlchemy 1.2+', allow_module_level=True) @pytest.mark.asyncio @@ -82,19 +82,19 @@ async def test_many_to_one(session_factory): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - article_1 = Article(headline="Article_1") + article_1 = Article(headline='Article_1') article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline="Article_2") + article_2 = Article(headline='Article_2') article_2.reporter = reporter_2 session.add(article_2) @@ -106,8 +106,7 @@ async def test_many_to_one(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async( - """ + result = await schema.execute_async(""" query { articles { headline @@ -116,26 +115,20 @@ async def test_many_to_one(session_factory): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than("1.3"): + if is_sqlalchemy_version_less_than('1.3'): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [ - message - for message in messages - if "SELECT" in message and "JOIN reporters" in message - ] + sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN reporters' in message] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than("1.4"): + if not is_sqlalchemy_version_less_than('1.4'): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -145,20 +138,20 @@ async def test_many_to_one(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "articles": [ - { - "headline": "Article_1", - "reporter": { - "firstName": "Reporter_1", - }, - }, - { - "headline": "Article_2", - "reporter": { - "firstName": "Reporter_2", - }, - }, - ], + "articles": [ + { + "headline": "Article_1", + "reporter": { + "firstName": "Reporter_1", + }, + }, + { + "headline": "Article_2", + "reporter": { + "firstName": "Reporter_2", + }, + }, + ], } @@ -167,19 +160,19 @@ async def test_one_to_one(session_factory): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - article_1 = Article(headline="Article_1") + article_1 = Article(headline='Article_1') article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline="Article_2") + article_2 = Article(headline='Article_2') article_2.reporter = reporter_2 session.add(article_2) @@ -191,8 +184,7 @@ async def test_one_to_one(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async( - """ + result = await schema.execute_async(""" query { reporters { firstName @@ -201,26 +193,20 @@ async def test_one_to_one(session_factory): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than("1.3"): + if is_sqlalchemy_version_less_than('1.3'): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [ - message - for message in messages - if "SELECT" in message and "JOIN articles" in message - ] + sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN articles' in message] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than("1.4"): + if not is_sqlalchemy_version_less_than('1.4'): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -230,20 +216,20 @@ async def test_one_to_one(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "reporters": [ - { - "firstName": "Reporter_1", - "favoriteArticle": { - "headline": "Article_1", - }, - }, - { - "firstName": "Reporter_2", - "favoriteArticle": { - "headline": "Article_2", - }, - }, - ], + "reporters": [ + { + "firstName": "Reporter_1", + "favoriteArticle": { + "headline": "Article_1", + }, + }, + { + "firstName": "Reporter_2", + "favoriteArticle": { + "headline": "Article_2", + }, + }, + ], } @@ -252,27 +238,27 @@ async def test_one_to_many(session_factory): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - article_1 = Article(headline="Article_1") + article_1 = Article(headline='Article_1') article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline="Article_2") + article_2 = Article(headline='Article_2') article_2.reporter = reporter_1 session.add(article_2) - article_3 = Article(headline="Article_3") + article_3 = Article(headline='Article_3') article_3.reporter = reporter_2 session.add(article_3) - article_4 = Article(headline="Article_4") + article_4 = Article(headline='Article_4') article_4.reporter = reporter_2 session.add(article_4) @@ -284,8 +270,7 @@ async def test_one_to_many(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async( - """ + result = await schema.execute_async(""" query { reporters { firstName @@ -298,26 +283,20 @@ async def test_one_to_many(session_factory): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than("1.3"): + if is_sqlalchemy_version_less_than('1.3'): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [ - message - for message in messages - if "SELECT" in message and "JOIN articles" in message - ] + sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN articles' in message] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than("1.4"): + if not is_sqlalchemy_version_less_than('1.4'): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -327,42 +306,42 @@ async def test_one_to_many(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "reporters": [ - { - "firstName": "Reporter_1", - "articles": { - "edges": [ - { - "node": { - "headline": "Article_1", - }, - }, - { - "node": { - "headline": "Article_2", - }, - }, - ], + "reporters": [ + { + "firstName": "Reporter_1", + "articles": { + "edges": [ + { + "node": { + "headline": "Article_1", }, - }, - { - "firstName": "Reporter_2", - "articles": { - "edges": [ - { - "node": { - "headline": "Article_3", - }, - }, - { - "node": { - "headline": "Article_4", - }, - }, - ], + }, + { + "node": { + "headline": "Article_2", }, - }, - ], + }, + ], + }, + }, + { + "firstName": "Reporter_2", + "articles": { + "edges": [ + { + "node": { + "headline": "Article_3", + }, + }, + { + "node": { + "headline": "Article_4", + }, + }, + ], + }, + }, + ], } @@ -371,27 +350,27 @@ async def test_many_to_many(session_factory): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - pet_1 = Pet(name="Pet_1", pet_kind="cat", hair_kind=HairKind.LONG) + pet_1 = Pet(name='Pet_1', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_1) - pet_2 = Pet(name="Pet_2", pet_kind="cat", hair_kind=HairKind.LONG) + pet_2 = Pet(name='Pet_2', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_2) reporter_1.pets.append(pet_1) reporter_1.pets.append(pet_2) - pet_3 = Pet(name="Pet_3", pet_kind="cat", hair_kind=HairKind.LONG) + pet_3 = Pet(name='Pet_3', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_3) - pet_4 = Pet(name="Pet_4", pet_kind="cat", hair_kind=HairKind.LONG) + pet_4 = Pet(name='Pet_4', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_4) reporter_2.pets.append(pet_3) @@ -405,8 +384,7 @@ async def test_many_to_many(session_factory): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - result = await schema.execute_async( - """ + result = await schema.execute_async(""" query { reporters { firstName @@ -419,26 +397,20 @@ async def test_many_to_many(session_factory): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages assert len(messages) == 5 - if is_sqlalchemy_version_less_than("1.3"): + if is_sqlalchemy_version_less_than('1.3'): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - sql_statements = [ - message - for message in messages - if "SELECT" in message and "JOIN pets" in message - ] + sql_statements = [message for message in messages if 'SELECT' in message and 'JOIN pets' in message] assert len(sql_statements) == 1 return - if not is_sqlalchemy_version_less_than("1.4"): + if not is_sqlalchemy_version_less_than('1.4'): messages[2] = remove_cache_miss_stat(messages[2]) messages[4] = remove_cache_miss_stat(messages[4]) @@ -448,50 +420,50 @@ async def test_many_to_many(session_factory): assert not result.errors result = to_std_dicts(result.data) assert result == { - "reporters": [ - { - "firstName": "Reporter_1", - "pets": { - "edges": [ - { - "node": { - "name": "Pet_1", - }, - }, - { - "node": { - "name": "Pet_2", - }, - }, - ], + "reporters": [ + { + "firstName": "Reporter_1", + "pets": { + "edges": [ + { + "node": { + "name": "Pet_1", + }, + }, + { + "node": { + "name": "Pet_2", + }, + }, + ], + }, + }, + { + "firstName": "Reporter_2", + "pets": { + "edges": [ + { + "node": { + "name": "Pet_3", }, - }, - { - "firstName": "Reporter_2", - "pets": { - "edges": [ - { - "node": { - "name": "Pet_3", - }, - }, - { - "node": { - "name": "Pet_4", - }, - }, - ], + }, + { + "node": { + "name": "Pet_4", }, - }, - ], + }, + ], + }, + }, + ], } def test_disable_batching_via_ormfield(session_factory): session = session_factory() - reporter_1 = Reporter(first_name="Reporter_1") + reporter_1 = Reporter(first_name='Reporter_1') session.add(reporter_1) - reporter_2 = Reporter(first_name="Reporter_2") + reporter_2 = Reporter(first_name='Reporter_2') session.add(reporter_2) session.commit() session.close() @@ -514,7 +486,7 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_reporters(self, info): - return info.context.get("session").query(Reporter).all() + return info.context.get('session').query(Reporter).all() schema = graphene.Schema(query=Query) @@ -522,8 +494,7 @@ def resolve_reporters(self, info): with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - schema.execute( - """ + schema.execute(""" query { reporters { favoriteArticle { @@ -531,24 +502,17 @@ def resolve_reporters(self, info): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages - select_statements = [ - message - for message in messages - if "SELECT" in message and "FROM articles" in message - ] + select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] assert len(select_statements) == 2 # Test one-to-many and many-to-many relationships with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - schema.execute( - """ + schema.execute(""" query { reporters { articles { @@ -560,25 +524,19 @@ def resolve_reporters(self, info): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages - select_statements = [ - message - for message in messages - if "SELECT" in message and "FROM articles" in message - ] + select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] assert len(select_statements) == 2 @pytest.mark.asyncio async def test_connection_factory_field_overrides_batching_is_false(session_factory): session = session_factory() - reporter_1 = Reporter(first_name="Reporter_1") + reporter_1 = Reporter(first_name='Reporter_1') session.add(reporter_1) - reporter_2 = Reporter(first_name="Reporter_2") + reporter_2 = Reporter(first_name='Reporter_2') session.add(reporter_2) session.commit() session.close() @@ -601,15 +559,14 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_reporters(self, info): - return info.context.get("session").query(Reporter).all() + return info.context.get('session').query(Reporter).all() schema = graphene.Schema(query=Query) with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - await schema.execute_async( - """ + await schema.execute_async(""" query { reporters { articles { @@ -621,34 +578,24 @@ def resolve_reporters(self, info): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages - if is_sqlalchemy_version_less_than("1.3"): + if is_sqlalchemy_version_less_than('1.3'): # The batched SQL statement generated is different in 1.2.x # SQLAlchemy 1.3+ optimizes out a JOIN statement in `selectin` # See https://git.io/JewQu - select_statements = [ - message - for message in messages - if "SELECT" in message and "JOIN articles" in message - ] + select_statements = [message for message in messages if 'SELECT' in message and 'JOIN articles' in message] else: - select_statements = [ - message - for message in messages - if "SELECT" in message and "FROM articles" in message - ] + select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] assert len(select_statements) == 1 def test_connection_factory_field_overrides_batching_is_true(session_factory): session = session_factory() - reporter_1 = Reporter(first_name="Reporter_1") + reporter_1 = Reporter(first_name='Reporter_1') session.add(reporter_1) - reporter_2 = Reporter(first_name="Reporter_2") + reporter_2 = Reporter(first_name='Reporter_2') session.add(reporter_2) session.commit() session.close() @@ -671,15 +618,14 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_reporters(self, info): - return info.context.get("session").query(Reporter).all() + return info.context.get('session').query(Reporter).all() schema = graphene.Schema(query=Query) with mock_sqlalchemy_logging_handler() as sqlalchemy_logging_handler: # Starts new session to fully reset the engine / connection logging level session = session_factory() - schema.execute( - """ + schema.execute(""" query { reporters { articles { @@ -691,14 +637,8 @@ def resolve_reporters(self, info): } } } - """, - context_value={"session": session}, - ) + """, context_value={"session": session}) messages = sqlalchemy_logging_handler.messages - select_statements = [ - message - for message in messages - if "SELECT" in message and "FROM articles" in message - ] + select_statements = [message for message in messages if 'SELECT' in message and 'FROM articles' in message] assert len(select_statements) == 2 diff --git a/graphene_sqlalchemy/tests/test_benchmark.py b/graphene_sqlalchemy/tests/test_benchmark.py index bb105edd..11e9d0e0 100644 --- a/graphene_sqlalchemy/tests/test_benchmark.py +++ b/graphene_sqlalchemy/tests/test_benchmark.py @@ -7,8 +7,8 @@ from ..utils import is_sqlalchemy_version_less_than from .models import Article, HairKind, Pet, Reporter -if is_sqlalchemy_version_less_than("1.2"): - pytest.skip("SQL batching only works for SQLAlchemy 1.2+", allow_module_level=True) +if is_sqlalchemy_version_less_than('1.2'): + pytest.skip('SQL batching only works for SQLAlchemy 1.2+', allow_module_level=True) def get_schema(): @@ -32,10 +32,10 @@ class Query(graphene.ObjectType): reporters = graphene.Field(graphene.List(ReporterType)) def resolve_articles(self, info): - return info.context.get("session").query(Article).all() + return info.context.get('session').query(Article).all() def resolve_reporters(self, info): - return info.context.get("session").query(Reporter).all() + return info.context.get('session').query(Reporter).all() return graphene.Schema(query=Query) @@ -46,8 +46,8 @@ def benchmark_query(session_factory, benchmark, query): @benchmark def execute_query(): result = schema.execute( - query, - context_value={"session": session_factory()}, + query, + context_value={"session": session_factory()}, ) assert not result.errors @@ -56,29 +56,26 @@ def test_one_to_one(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - article_1 = Article(headline="Article_1") + article_1 = Article(headline='Article_1') article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline="Article_2") + article_2 = Article(headline='Article_2') article_2.reporter = reporter_2 session.add(article_2) session.commit() session.close() - benchmark_query( - session_factory, - benchmark, - """ + benchmark_query(session_factory, benchmark, """ query { reporters { firstName @@ -87,37 +84,33 @@ def test_one_to_one(session_factory, benchmark): } } } - """, - ) + """) def test_many_to_one(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - article_1 = Article(headline="Article_1") + article_1 = Article(headline='Article_1') article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline="Article_2") + article_2 = Article(headline='Article_2') article_2.reporter = reporter_2 session.add(article_2) session.commit() session.close() - benchmark_query( - session_factory, - benchmark, - """ + benchmark_query(session_factory, benchmark, """ query { articles { headline @@ -126,45 +119,41 @@ def test_many_to_one(session_factory, benchmark): } } } - """, - ) + """) def test_one_to_many(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - article_1 = Article(headline="Article_1") + article_1 = Article(headline='Article_1') article_1.reporter = reporter_1 session.add(article_1) - article_2 = Article(headline="Article_2") + article_2 = Article(headline='Article_2') article_2.reporter = reporter_1 session.add(article_2) - article_3 = Article(headline="Article_3") + article_3 = Article(headline='Article_3') article_3.reporter = reporter_2 session.add(article_3) - article_4 = Article(headline="Article_4") + article_4 = Article(headline='Article_4') article_4.reporter = reporter_2 session.add(article_4) session.commit() session.close() - benchmark_query( - session_factory, - benchmark, - """ + benchmark_query(session_factory, benchmark, """ query { reporters { firstName @@ -177,35 +166,34 @@ def test_one_to_many(session_factory, benchmark): } } } - """, - ) + """) def test_many_to_many(session_factory, benchmark): session = session_factory() reporter_1 = Reporter( - first_name="Reporter_1", + first_name='Reporter_1', ) session.add(reporter_1) reporter_2 = Reporter( - first_name="Reporter_2", + first_name='Reporter_2', ) session.add(reporter_2) - pet_1 = Pet(name="Pet_1", pet_kind="cat", hair_kind=HairKind.LONG) + pet_1 = Pet(name='Pet_1', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_1) - pet_2 = Pet(name="Pet_2", pet_kind="cat", hair_kind=HairKind.LONG) + pet_2 = Pet(name='Pet_2', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_2) reporter_1.pets.append(pet_1) reporter_1.pets.append(pet_2) - pet_3 = Pet(name="Pet_3", pet_kind="cat", hair_kind=HairKind.LONG) + pet_3 = Pet(name='Pet_3', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_3) - pet_4 = Pet(name="Pet_4", pet_kind="cat", hair_kind=HairKind.LONG) + pet_4 = Pet(name='Pet_4', pet_kind='cat', hair_kind=HairKind.LONG) session.add(pet_4) reporter_2.pets.append(pet_3) @@ -214,10 +202,7 @@ def test_many_to_many(session_factory, benchmark): session.commit() session.close() - benchmark_query( - session_factory, - benchmark, - """ + benchmark_query(session_factory, benchmark, """ query { reporters { firstName @@ -230,5 +215,4 @@ def test_many_to_many(session_factory, benchmark): } } } - """, - ) + """) diff --git a/graphene_sqlalchemy/tests/test_converter.py b/graphene_sqlalchemy/tests/test_converter.py index 232a82cd..70e11713 100644 --- a/graphene_sqlalchemy/tests/test_converter.py +++ b/graphene_sqlalchemy/tests/test_converter.py @@ -16,22 +16,15 @@ from graphene.types.json import JSONString from graphene.types.structures import List, Structure -from ..converter import ( - convert_sqlalchemy_column, - convert_sqlalchemy_composite, - convert_sqlalchemy_relationship, -) -from ..fields import UnsortedSQLAlchemyConnectionField, default_connection_field_factory +from ..converter import (convert_sqlalchemy_column, + convert_sqlalchemy_composite, + convert_sqlalchemy_relationship) +from ..fields import (UnsortedSQLAlchemyConnectionField, + default_connection_field_factory) from ..registry import Registry, get_global_registry from ..types import SQLAlchemyObjectType -from .models import ( - Article, - CompositeFullName, - Pet, - Reporter, - ShoppingCart, - ShoppingCartItem, -) +from .models import (Article, CompositeFullName, Pet, Reporter, ShoppingCart, + ShoppingCartItem) def mock_resolver(): @@ -40,21 +33,21 @@ def mock_resolver(): def get_field(sqlalchemy_type, **column_kwargs): class Model(declarative_base()): - __tablename__ = "model" + __tablename__ = 'model' id_ = Column(types.Integer, primary_key=True) column = Column(sqlalchemy_type, doc="Custom Help Text", **column_kwargs) - column_prop = inspect(Model).column_attrs["column"] + column_prop = inspect(Model).column_attrs['column'] return convert_sqlalchemy_column(column_prop, get_global_registry(), mock_resolver) def get_field_from_column(column_): class Model(declarative_base()): - __tablename__ = "model" + __tablename__ = 'model' id_ = Column(types.Integer, primary_key=True) column = column_ - column_prop = inspect(Model).column_attrs["column"] + column_prop = inspect(Model).column_attrs['column'] return convert_sqlalchemy_column(column_prop, get_global_registry(), mock_resolver) @@ -62,7 +55,7 @@ def test_should_unknown_sqlalchemy_field_raise_exception(): re_err = "Don't know how to convert the SQLAlchemy field" with pytest.raises(Exception, match=re_err): # support legacy Binary type and subsequent LargeBinary - get_field(getattr(types, "LargeBinary", types.BINARY)()) + get_field(getattr(types, 'LargeBinary', types.BINARY)()) def test_should_date_convert_string(): @@ -133,9 +126,7 @@ def test_should_integer_convert_int(): def test_should_primary_integer_convert_id(): - assert get_field(types.Integer(), primary_key=True).type == graphene.NonNull( - graphene.ID - ) + assert get_field(types.Integer(), primary_key=True).type == graphene.NonNull(graphene.ID) def test_should_boolean_convert_boolean(): @@ -151,7 +142,7 @@ def test_should_numeric_convert_float(): def test_should_choice_convert_enum(): - field = get_field(ChoiceType([("es", "Spanish"), ("en", "English")])) + field = get_field(ChoiceType([(u"es", u"Spanish"), (u"en", u"English")])) graphene_type = field.type assert issubclass(graphene_type, graphene.Enum) assert graphene_type._meta.name == "MODEL_COLUMN" @@ -161,8 +152,8 @@ def test_should_choice_convert_enum(): def test_should_enum_choice_convert_enum(): class TestEnum(enum.Enum): - es = "Spanish" - en = "English" + es = u"Spanish" + en = u"English" field = get_field(ChoiceType(TestEnum, impl=types.String())) graphene_type = field.type @@ -186,9 +177,9 @@ class TestEnum(enum.IntEnum): def test_should_columproperty_convert(): - field = get_field_from_column( - column_property(select([func.sum(func.cast(id, types.Integer))]).where(id == 1)) - ) + field = get_field_from_column(column_property( + select([func.sum(func.cast(id, types.Integer))]).where(id == 1) + )) assert field.type == graphene.Int @@ -209,11 +200,7 @@ class Meta: model = Article dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, - A, - default_connection_field_factory, - True, - "orm_field_name", + Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', ) assert isinstance(dynamic_field, graphene.Dynamic) assert not dynamic_field.get_type() @@ -225,11 +212,7 @@ class Meta: model = Pet dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, - A, - default_connection_field_factory, - True, - "orm_field_name", + Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -245,11 +228,7 @@ class Meta: interfaces = (Node,) dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, - A, - default_connection_field_factory, - True, - "orm_field_name", + Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', ) assert isinstance(dynamic_field, graphene.Dynamic) assert isinstance(dynamic_field.get_type(), UnsortedSQLAlchemyConnectionField) @@ -261,11 +240,7 @@ class Meta: model = Article dynamic_field = convert_sqlalchemy_relationship( - Reporter.pets.property, - A, - default_connection_field_factory, - True, - "orm_field_name", + Reporter.pets.property, A, default_connection_field_factory, True, 'orm_field_name', ) assert isinstance(dynamic_field, graphene.Dynamic) assert not dynamic_field.get_type() @@ -277,11 +252,7 @@ class Meta: model = Reporter dynamic_field = convert_sqlalchemy_relationship( - Article.reporter.property, - A, - default_connection_field_factory, - True, - "orm_field_name", + Article.reporter.property, A, default_connection_field_factory, True, 'orm_field_name', ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -296,11 +267,7 @@ class Meta: interfaces = (Node,) dynamic_field = convert_sqlalchemy_relationship( - Article.reporter.property, - A, - default_connection_field_factory, - True, - "orm_field_name", + Article.reporter.property, A, default_connection_field_factory, True, 'orm_field_name', ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -315,11 +282,7 @@ class Meta: interfaces = (Node,) dynamic_field = convert_sqlalchemy_relationship( - Reporter.favorite_article.property, - A, - default_connection_field_factory, - True, - "orm_field_name", + Reporter.favorite_article.property, A, default_connection_field_factory, True, 'orm_field_name', ) assert isinstance(dynamic_field, graphene.Dynamic) graphene_type = dynamic_field.get_type() @@ -343,9 +306,7 @@ def test_should_postgresql_enum_convert(): def test_should_postgresql_py_enum_convert(): - field = get_field( - postgresql.ENUM(enum.Enum("TwoNumbers", "one two"), name="two_numbers") - ) + field = get_field(postgresql.ENUM(enum.Enum("TwoNumbers", "one two"), name="two_numbers")) field_type = field.type() assert field_type._meta.name == "TwoNumbers" assert isinstance(field_type, graphene.Enum) @@ -407,11 +368,7 @@ def convert_composite_class(composite, registry): return graphene.String(description=composite.doc) field = convert_sqlalchemy_composite( - composite( - CompositeClass, - (Column(types.Unicode(50)), Column(types.Unicode(50))), - doc="Custom Help Text", - ), + composite(CompositeClass, (Column(types.Unicode(50)), Column(types.Unicode(50))), doc="Custom Help Text"), registry, mock_resolver, ) @@ -427,10 +384,7 @@ def __init__(self, col1, col2): re_err = "Don't know how to convert the composite field" with pytest.raises(Exception, match=re_err): convert_sqlalchemy_composite( - composite( - CompositeFullName, - (Column(types.Unicode(50)), Column(types.Unicode(50))), - ), + composite(CompositeFullName, (Column(types.Unicode(50)), Column(types.Unicode(50)))), Registry(), mock_resolver, ) @@ -452,33 +406,26 @@ class Meta: ####################################################### shopping_cart_item_expected_types: Dict[str, Union[Scalar, Structure]] = { - "hybrid_prop_shopping_cart": List(ShoppingCartType) + 'hybrid_prop_shopping_cart': List(ShoppingCartType) } - assert sorted(list(ShoppingCartItemType._meta.fields.keys())) == sorted( - [ - # Columns - "id", - # Append Hybrid Properties from Above - *shopping_cart_item_expected_types.keys(), - ] - ) + assert sorted(list(ShoppingCartItemType._meta.fields.keys())) == sorted([ + # Columns + "id", + # Append Hybrid Properties from Above + *shopping_cart_item_expected_types.keys() + ]) - for ( - hybrid_prop_name, - hybrid_prop_expected_return_type, - ) in shopping_cart_item_expected_types.items(): + for hybrid_prop_name, hybrid_prop_expected_return_type in shopping_cart_item_expected_types.items(): hybrid_prop_field = ShoppingCartItemType._meta.fields[hybrid_prop_name] # this is a simple way of showing the failed property name # instead of having to unroll the loop. - assert (hybrid_prop_name, str(hybrid_prop_field.type)) == ( - hybrid_prop_name, - str(hybrid_prop_expected_return_type), - ) assert ( - hybrid_prop_field.description is None - ) # "doc" is ignored by hybrid property + (hybrid_prop_name, str(hybrid_prop_field.type)) == + (hybrid_prop_name, str(hybrid_prop_expected_return_type)) + ) + assert hybrid_prop_field.description is None # "doc" is ignored by hybrid property ################################################### # Check ShoppingCart's Properties and Return Types @@ -509,27 +456,20 @@ class Meta: "hybrid_prop_optional_self_referential": ShoppingCartType, } - assert sorted(list(ShoppingCartType._meta.fields.keys())) == sorted( - [ - # Columns - "id", - # Append Hybrid Properties from Above - *shopping_cart_expected_types.keys(), - ] - ) + assert sorted(list(ShoppingCartType._meta.fields.keys())) == sorted([ + # Columns + "id", + # Append Hybrid Properties from Above + *shopping_cart_expected_types.keys() + ]) - for ( - hybrid_prop_name, - hybrid_prop_expected_return_type, - ) in shopping_cart_expected_types.items(): + for hybrid_prop_name, hybrid_prop_expected_return_type in shopping_cart_expected_types.items(): hybrid_prop_field = ShoppingCartType._meta.fields[hybrid_prop_name] # this is a simple way of showing the failed property name # instead of having to unroll the loop. - assert (hybrid_prop_name, str(hybrid_prop_field.type)) == ( - hybrid_prop_name, - str(hybrid_prop_expected_return_type), - ) assert ( - hybrid_prop_field.description is None - ) # "doc" is ignored by hybrid property + (hybrid_prop_name, str(hybrid_prop_field.type)) == + (hybrid_prop_name, str(hybrid_prop_expected_return_type)) + ) + assert hybrid_prop_field.description is None # "doc" is ignored by hybrid property diff --git a/graphene_sqlalchemy/tests/test_enums.py b/graphene_sqlalchemy/tests/test_enums.py index cd97a00e..ca376964 100644 --- a/graphene_sqlalchemy/tests/test_enums.py +++ b/graphene_sqlalchemy/tests/test_enums.py @@ -54,7 +54,7 @@ def test_convert_sa_enum_to_graphene_enum_based_on_list_named(): assert [ (key, value.value) for key, value in graphene_enum._meta.enum.__members__.items() - ] == [("RED", "red"), ("GREEN", "green"), ("BLUE", "blue")] + ] == [("RED", 'red'), ("GREEN", 'green'), ("BLUE", 'blue')] def test_convert_sa_enum_to_graphene_enum_based_on_list_unnamed(): @@ -65,7 +65,7 @@ def test_convert_sa_enum_to_graphene_enum_based_on_list_unnamed(): assert [ (key, value.value) for key, value in graphene_enum._meta.enum.__members__.items() - ] == [("RED", "red"), ("GREEN", "green"), ("BLUE", "blue")] + ] == [("RED", 'red'), ("GREEN", 'green'), ("BLUE", 'blue')] def test_convert_sa_enum_to_graphene_enum_based_on_list_without_name(): @@ -80,35 +80,36 @@ class PetType(SQLAlchemyObjectType): class Meta: model = Pet - enum = enum_for_field(PetType, "pet_kind") + enum = enum_for_field(PetType, 'pet_kind') assert isinstance(enum, type(Enum)) assert enum._meta.name == "PetKind" assert [ - (key, value.value) for key, value in enum._meta.enum.__members__.items() - ] == [("CAT", "cat"), ("DOG", "dog")] - enum2 = enum_for_field(PetType, "pet_kind") + (key, value.value) + for key, value in enum._meta.enum.__members__.items() + ] == [("CAT", 'cat'), ("DOG", 'dog')] + enum2 = enum_for_field(PetType, 'pet_kind') assert enum2 is enum - enum2 = PetType.enum_for_field("pet_kind") + enum2 = PetType.enum_for_field('pet_kind') assert enum2 is enum - enum = enum_for_field(PetType, "hair_kind") + enum = enum_for_field(PetType, 'hair_kind') assert isinstance(enum, type(Enum)) assert enum._meta.name == "HairKind" assert enum._meta.enum is HairKind - enum2 = PetType.enum_for_field("hair_kind") + enum2 = PetType.enum_for_field('hair_kind') assert enum2 is enum re_err = r"Cannot get PetType\.other_kind" with pytest.raises(TypeError, match=re_err): - enum_for_field(PetType, "other_kind") + enum_for_field(PetType, 'other_kind') with pytest.raises(TypeError, match=re_err): - PetType.enum_for_field("other_kind") + PetType.enum_for_field('other_kind') re_err = r"PetType\.name does not map to enum column" with pytest.raises(TypeError, match=re_err): - enum_for_field(PetType, "name") + enum_for_field(PetType, 'name') with pytest.raises(TypeError, match=re_err): - PetType.enum_for_field("name") + PetType.enum_for_field('name') re_err = r"Expected a field name, but got: None" with pytest.raises(TypeError, match=re_err): @@ -118,4 +119,4 @@ class Meta: re_err = "Expected SQLAlchemyObjectType, but got: None" with pytest.raises(TypeError, match=re_err): - enum_for_field(None, "other_kind") + enum_for_field(None, 'other_kind') diff --git a/graphene_sqlalchemy/tests/test_fields.py b/graphene_sqlalchemy/tests/test_fields.py index 0549e569..357055e3 100644 --- a/graphene_sqlalchemy/tests/test_fields.py +++ b/graphene_sqlalchemy/tests/test_fields.py @@ -4,7 +4,8 @@ from graphene import NonNull, ObjectType from graphene.relay import Connection, Node -from ..fields import SQLAlchemyConnectionField, UnsortedSQLAlchemyConnectionField +from ..fields import (SQLAlchemyConnectionField, + UnsortedSQLAlchemyConnectionField) from ..types import SQLAlchemyObjectType from .models import Editor as EditorModel from .models import Pet as PetModel @@ -20,7 +21,6 @@ class Editor(SQLAlchemyObjectType): class Meta: model = EditorModel - ## # SQLAlchemyConnectionField ## @@ -59,7 +59,6 @@ def test_type_assert_object_has_connection(): with pytest.raises(AssertionError, match="doesn't have a connection"): SQLAlchemyConnectionField(Editor).type - ## # UnsortedSQLAlchemyConnectionField ## diff --git a/graphene_sqlalchemy/tests/test_query.py b/graphene_sqlalchemy/tests/test_query.py index c7a173df..39140814 100644 --- a/graphene_sqlalchemy/tests/test_query.py +++ b/graphene_sqlalchemy/tests/test_query.py @@ -9,17 +9,19 @@ def add_test_data(session): - reporter = Reporter(first_name="John", last_name="Doe", favorite_pet_kind="cat") + reporter = Reporter( + first_name='John', last_name='Doe', favorite_pet_kind='cat') session.add(reporter) - pet = Pet(name="Garfield", pet_kind="cat", hair_kind=HairKind.SHORT) + pet = Pet(name='Garfield', pet_kind='cat', hair_kind=HairKind.SHORT) session.add(pet) pet.reporters.append(reporter) - article = Article(headline="Hi!") + article = Article(headline='Hi!') article.reporter = reporter session.add(article) - reporter = Reporter(first_name="Jane", last_name="Roe", favorite_pet_kind="dog") + reporter = Reporter( + first_name='Jane', last_name='Roe', favorite_pet_kind='dog') session.add(reporter) - pet = Pet(name="Lassie", pet_kind="dog", hair_kind=HairKind.LONG) + pet = Pet(name='Lassie', pet_kind='dog', hair_kind=HairKind.LONG) pet.reporters.append(reporter) session.add(pet) editor = Editor(name="Jack") @@ -161,12 +163,12 @@ class Meta: model = Reporter interfaces = (Node,) - first_name_v2 = ORMField(model_attr="first_name") - hybrid_prop_v2 = ORMField(model_attr="hybrid_prop") - column_prop_v2 = ORMField(model_attr="column_prop") + first_name_v2 = ORMField(model_attr='first_name') + hybrid_prop_v2 = ORMField(model_attr='hybrid_prop') + column_prop_v2 = ORMField(model_attr='column_prop') composite_prop = ORMField() - favorite_article_v2 = ORMField(model_attr="favorite_article") - articles_v2 = ORMField(model_attr="articles") + favorite_article_v2 = ORMField(model_attr='favorite_article') + articles_v2 = ORMField(model_attr='articles') class ArticleType(SQLAlchemyObjectType): class Meta: diff --git a/graphene_sqlalchemy/tests/test_query_enums.py b/graphene_sqlalchemy/tests/test_query_enums.py index 923bbed1..5166c45f 100644 --- a/graphene_sqlalchemy/tests/test_query_enums.py +++ b/graphene_sqlalchemy/tests/test_query_enums.py @@ -9,6 +9,7 @@ def test_query_pet_kinds(session): add_test_data(session) class PetType(SQLAlchemyObjectType): + class Meta: model = Pet @@ -19,9 +20,8 @@ class Meta: class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) reporters = graphene.List(ReporterType) - pets = graphene.List( - PetType, kind=graphene.Argument(PetType.enum_for_field("pet_kind")) - ) + pets = graphene.List(PetType, kind=graphene.Argument( + PetType.enum_for_field('pet_kind'))) def resolve_reporter(self, _info): return session.query(Reporter).first() @@ -58,24 +58,27 @@ def resolve_pets(self, _info, kind): } """ expected = { - "reporter": { - "firstName": "John", - "lastName": "Doe", - "email": None, - "favoritePetKind": "CAT", - "pets": [{"name": "Garfield", "petKind": "CAT"}], + 'reporter': { + 'firstName': 'John', + 'lastName': 'Doe', + 'email': None, + 'favoritePetKind': 'CAT', + 'pets': [{ + 'name': 'Garfield', + 'petKind': 'CAT' + }] }, - "reporters": [ - { - "firstName": "John", - "favoritePetKind": "CAT", - }, - { - "firstName": "Jane", - "favoritePetKind": "DOG", - }, - ], - "pets": [{"name": "Lassie", "petKind": "DOG"}], + 'reporters': [{ + 'firstName': 'John', + 'favoritePetKind': 'CAT', + }, { + 'firstName': 'Jane', + 'favoritePetKind': 'DOG', + }], + 'pets': [{ + 'name': 'Lassie', + 'petKind': 'DOG' + }] } schema = graphene.Schema(query=Query) result = schema.execute(query) @@ -122,8 +125,8 @@ class Meta: class Query(graphene.ObjectType): pet = graphene.Field( - PetType, kind=graphene.Argument(PetType.enum_for_field("pet_kind")) - ) + PetType, + kind=graphene.Argument(PetType.enum_for_field('pet_kind'))) def resolve_pet(self, info, kind=None): query = session.query(Pet) diff --git a/graphene_sqlalchemy/tests/test_reflected.py b/graphene_sqlalchemy/tests/test_reflected.py index a3f6c4aa..46e10de9 100644 --- a/graphene_sqlalchemy/tests/test_reflected.py +++ b/graphene_sqlalchemy/tests/test_reflected.py @@ -1,3 +1,4 @@ + from graphene import ObjectType from ..registry import Registry diff --git a/graphene_sqlalchemy/tests/test_sort_enums.py b/graphene_sqlalchemy/tests/test_sort_enums.py index 08de0b20..6291d4f8 100644 --- a/graphene_sqlalchemy/tests/test_sort_enums.py +++ b/graphene_sqlalchemy/tests/test_sort_enums.py @@ -354,7 +354,7 @@ def makeNodes(nodeList): """ result = schema.execute(queryError, context_value={"session": session}) assert result.errors is not None - assert "cannot represent non-enum value" in result.errors[0].message + assert 'cannot represent non-enum value' in result.errors[0].message queryNoSort = """ query sortTest { diff --git a/graphene_sqlalchemy/tests/test_types.py b/graphene_sqlalchemy/tests/test_types.py index 31caea3c..9a2e992d 100644 --- a/graphene_sqlalchemy/tests/test_types.py +++ b/graphene_sqlalchemy/tests/test_types.py @@ -4,31 +4,16 @@ import sqlalchemy.exc import sqlalchemy.orm.exc -from graphene import ( - Boolean, - Dynamic, - Field, - Float, - GlobalID, - Int, - List, - Node, - NonNull, - ObjectType, - Schema, - String, -) +from graphene import (Boolean, Dynamic, Field, Float, GlobalID, Int, List, + Node, NonNull, ObjectType, Schema, String) from graphene.relay import Connection from .. import utils from ..converter import convert_sqlalchemy_composite -from ..fields import ( - SQLAlchemyConnectionField, - UnsortedSQLAlchemyConnectionField, - createConnectionField, - registerConnectionFieldFactory, - unregisterConnectionFieldFactory, -) +from ..fields import (SQLAlchemyConnectionField, + UnsortedSQLAlchemyConnectionField, createConnectionField, + registerConnectionFieldFactory, + unregisterConnectionFieldFactory) from ..types import ORMField, SQLAlchemyObjectType, SQLAlchemyObjectTypeOptions from .models import Article, CompositeFullName, Pet, Reporter @@ -36,7 +21,6 @@ def test_should_raise_if_no_model(): re_err = r"valid SQLAlchemy Model" with pytest.raises(Exception, match=re_err): - class Character1(SQLAlchemyObjectType): pass @@ -44,7 +28,6 @@ class Character1(SQLAlchemyObjectType): def test_should_raise_if_model_is_invalid(): re_err = r"valid SQLAlchemy Model" with pytest.raises(Exception, match=re_err): - class Character(SQLAlchemyObjectType): class Meta: model = 1 @@ -62,7 +45,7 @@ class Meta: reporter = Reporter() session.add(reporter) session.commit() - info = mock.Mock(context={"session": session}) + info = mock.Mock(context={'session': session}) reporter_node = ReporterType.get_node(info, reporter.id) assert reporter == reporter_node @@ -91,93 +74,91 @@ class Meta: model = Article interfaces = (Node,) - assert sorted(list(ReporterType._meta.fields.keys())) == sorted( - [ - # Columns - "column_prop", # SQLAlchemy retuns column properties first - "id", - "first_name", - "last_name", - "email", - "favorite_pet_kind", - # Composite - "composite_prop", - # Hybrid - "hybrid_prop_with_doc", - "hybrid_prop", - "hybrid_prop_str", - "hybrid_prop_int", - "hybrid_prop_float", - "hybrid_prop_bool", - "hybrid_prop_list", - # Relationship - "pets", - "articles", - "favorite_article", - ] - ) + assert sorted(list(ReporterType._meta.fields.keys())) == sorted([ + # Columns + "column_prop", # SQLAlchemy retuns column properties first + "id", + "first_name", + "last_name", + "email", + "favorite_pet_kind", + # Composite + "composite_prop", + # Hybrid + "hybrid_prop_with_doc", + "hybrid_prop", + "hybrid_prop_str", + "hybrid_prop_int", + "hybrid_prop_float", + "hybrid_prop_bool", + "hybrid_prop_list", + # Relationship + "pets", + "articles", + "favorite_article", + ]) # column - first_name_field = ReporterType._meta.fields["first_name"] + first_name_field = ReporterType._meta.fields['first_name'] assert first_name_field.type == String assert first_name_field.description == "First name" # column_property - column_prop_field = ReporterType._meta.fields["column_prop"] + column_prop_field = ReporterType._meta.fields['column_prop'] assert column_prop_field.type == Int # "doc" is ignored by column_property assert column_prop_field.description is None # composite - full_name_field = ReporterType._meta.fields["composite_prop"] + full_name_field = ReporterType._meta.fields['composite_prop'] assert full_name_field.type == String # "doc" is ignored by composite assert full_name_field.description is None # hybrid_property - hybrid_prop = ReporterType._meta.fields["hybrid_prop"] + hybrid_prop = ReporterType._meta.fields['hybrid_prop'] assert hybrid_prop.type == String # "doc" is ignored by hybrid_property assert hybrid_prop.description is None # hybrid_property_str - hybrid_prop_str = ReporterType._meta.fields["hybrid_prop_str"] + hybrid_prop_str = ReporterType._meta.fields['hybrid_prop_str'] assert hybrid_prop_str.type == String # "doc" is ignored by hybrid_property assert hybrid_prop_str.description is None # hybrid_property_int - hybrid_prop_int = ReporterType._meta.fields["hybrid_prop_int"] + hybrid_prop_int = ReporterType._meta.fields['hybrid_prop_int'] assert hybrid_prop_int.type == Int # "doc" is ignored by hybrid_property assert hybrid_prop_int.description is None # hybrid_property_float - hybrid_prop_float = ReporterType._meta.fields["hybrid_prop_float"] + hybrid_prop_float = ReporterType._meta.fields['hybrid_prop_float'] assert hybrid_prop_float.type == Float # "doc" is ignored by hybrid_property assert hybrid_prop_float.description is None # hybrid_property_bool - hybrid_prop_bool = ReporterType._meta.fields["hybrid_prop_bool"] + hybrid_prop_bool = ReporterType._meta.fields['hybrid_prop_bool'] assert hybrid_prop_bool.type == Boolean # "doc" is ignored by hybrid_property assert hybrid_prop_bool.description is None # hybrid_property_list - hybrid_prop_list = ReporterType._meta.fields["hybrid_prop_list"] + hybrid_prop_list = ReporterType._meta.fields['hybrid_prop_list'] assert hybrid_prop_list.type == List(Int) # "doc" is ignored by hybrid_property assert hybrid_prop_list.description is None # hybrid_prop_with_doc - hybrid_prop_with_doc = ReporterType._meta.fields["hybrid_prop_with_doc"] + hybrid_prop_with_doc = ReporterType._meta.fields['hybrid_prop_with_doc'] assert hybrid_prop_with_doc.type == String # docstring is picked up from hybrid_prop_with_doc assert hybrid_prop_with_doc.description == "Docstring test" # relationship - favorite_article_field = ReporterType._meta.fields["favorite_article"] + favorite_article_field = ReporterType._meta.fields['favorite_article'] assert isinstance(favorite_article_field, Dynamic) assert favorite_article_field.type().type == ArticleType assert favorite_article_field.type().description is None @@ -191,7 +172,7 @@ def convert_composite_class(composite, registry): class ReporterMixin(object): # columns first_name = ORMField(required=True) - last_name = ORMField(description="Overridden") + last_name = ORMField(description='Overridden') class ReporterType(SQLAlchemyObjectType, ReporterMixin): class Meta: @@ -199,8 +180,8 @@ class Meta: interfaces = (Node,) # columns - email = ORMField(deprecation_reason="Overridden") - email_v2 = ORMField(model_attr="email", type_=Int) + email = ORMField(deprecation_reason='Overridden') + email_v2 = ORMField(model_attr='email', type_=Int) # column_property column_prop = ORMField(type_=String) @@ -209,13 +190,13 @@ class Meta: composite_prop = ORMField() # hybrid_property - hybrid_prop_with_doc = ORMField(description="Overridden") - hybrid_prop = ORMField(description="Overridden") + hybrid_prop_with_doc = ORMField(description='Overridden') + hybrid_prop = ORMField(description='Overridden') # relationships - favorite_article = ORMField(description="Overridden") - articles = ORMField(deprecation_reason="Overridden") - pets = ORMField(description="Overridden") + favorite_article = ORMField(description='Overridden') + articles = ORMField(deprecation_reason='Overridden') + pets = ORMField(description='Overridden') class ArticleType(SQLAlchemyObjectType): class Meta: @@ -228,101 +209,99 @@ class Meta: interfaces = (Node,) use_connection = False - assert sorted(list(ReporterType._meta.fields.keys())) == sorted( - [ - # Fields from ReporterMixin - "first_name", - "last_name", - # Fields from ReporterType - "email", - "email_v2", - "column_prop", - "composite_prop", - "hybrid_prop_with_doc", - "hybrid_prop", - "favorite_article", - "articles", - "pets", - # Then the automatic SQLAlchemy fields - "id", - "favorite_pet_kind", - "hybrid_prop_str", - "hybrid_prop_int", - "hybrid_prop_float", - "hybrid_prop_bool", - "hybrid_prop_list", - ] - ) - - first_name_field = ReporterType._meta.fields["first_name"] + assert sorted(list(ReporterType._meta.fields.keys())) == sorted([ + # Fields from ReporterMixin + "first_name", + "last_name", + # Fields from ReporterType + "email", + "email_v2", + "column_prop", + "composite_prop", + "hybrid_prop_with_doc", + "hybrid_prop", + "favorite_article", + "articles", + "pets", + # Then the automatic SQLAlchemy fields + "id", + "favorite_pet_kind", + "hybrid_prop_str", + "hybrid_prop_int", + "hybrid_prop_float", + "hybrid_prop_bool", + "hybrid_prop_list", + ]) + + first_name_field = ReporterType._meta.fields['first_name'] assert isinstance(first_name_field.type, NonNull) assert first_name_field.type.of_type == String assert first_name_field.description == "First name" assert first_name_field.deprecation_reason is None - last_name_field = ReporterType._meta.fields["last_name"] + last_name_field = ReporterType._meta.fields['last_name'] assert last_name_field.type == String assert last_name_field.description == "Overridden" assert last_name_field.deprecation_reason is None - email_field = ReporterType._meta.fields["email"] + email_field = ReporterType._meta.fields['email'] assert email_field.type == String assert email_field.description == "Email" assert email_field.deprecation_reason == "Overridden" - email_field_v2 = ReporterType._meta.fields["email_v2"] + email_field_v2 = ReporterType._meta.fields['email_v2'] assert email_field_v2.type == Int assert email_field_v2.description == "Email" assert email_field_v2.deprecation_reason is None - hybrid_prop_field = ReporterType._meta.fields["hybrid_prop"] + hybrid_prop_field = ReporterType._meta.fields['hybrid_prop'] assert hybrid_prop_field.type == String assert hybrid_prop_field.description == "Overridden" assert hybrid_prop_field.deprecation_reason is None - hybrid_prop_with_doc_field = ReporterType._meta.fields["hybrid_prop_with_doc"] + hybrid_prop_with_doc_field = ReporterType._meta.fields['hybrid_prop_with_doc'] assert hybrid_prop_with_doc_field.type == String assert hybrid_prop_with_doc_field.description == "Overridden" assert hybrid_prop_with_doc_field.deprecation_reason is None - column_prop_field_v2 = ReporterType._meta.fields["column_prop"] + column_prop_field_v2 = ReporterType._meta.fields['column_prop'] assert column_prop_field_v2.type == String assert column_prop_field_v2.description is None assert column_prop_field_v2.deprecation_reason is None - composite_prop_field = ReporterType._meta.fields["composite_prop"] + composite_prop_field = ReporterType._meta.fields['composite_prop'] assert composite_prop_field.type == String assert composite_prop_field.description is None assert composite_prop_field.deprecation_reason is None - favorite_article_field = ReporterType._meta.fields["favorite_article"] + favorite_article_field = ReporterType._meta.fields['favorite_article'] assert isinstance(favorite_article_field, Dynamic) assert favorite_article_field.type().type == ArticleType - assert favorite_article_field.type().description == "Overridden" + assert favorite_article_field.type().description == 'Overridden' - articles_field = ReporterType._meta.fields["articles"] + articles_field = ReporterType._meta.fields['articles'] assert isinstance(articles_field, Dynamic) assert isinstance(articles_field.type(), UnsortedSQLAlchemyConnectionField) assert articles_field.type().deprecation_reason == "Overridden" - pets_field = ReporterType._meta.fields["pets"] + pets_field = ReporterType._meta.fields['pets'] assert isinstance(pets_field, Dynamic) assert isinstance(pets_field.type().type, List) assert pets_field.type().type.of_type == PetType - assert pets_field.type().description == "Overridden" + assert pets_field.type().description == 'Overridden' def test_invalid_model_attr(): err_msg = ( - "Cannot map ORMField to a model attribute.\n" "Field: 'ReporterType.first_name'" + "Cannot map ORMField to a model attribute.\n" + "Field: 'ReporterType.first_name'" ) with pytest.raises(ValueError, match=err_msg): - class ReporterType(SQLAlchemyObjectType): class Meta: model = Reporter - first_name = ORMField(model_attr="does_not_exist") + first_name = ORMField(model_attr='does_not_exist') def test_only_fields(): @@ -346,32 +325,29 @@ class Meta: first_name = ORMField() # Takes precedence last_name = ORMField() # Noop - assert sorted(list(ReporterType._meta.fields.keys())) == sorted( - [ - "first_name", - "last_name", - "column_prop", - "email", - "favorite_pet_kind", - "composite_prop", - "hybrid_prop_with_doc", - "hybrid_prop", - "hybrid_prop_str", - "hybrid_prop_int", - "hybrid_prop_float", - "hybrid_prop_bool", - "hybrid_prop_list", - "pets", - "articles", - "favorite_article", - ] - ) + assert sorted(list(ReporterType._meta.fields.keys())) == sorted([ + "first_name", + "last_name", + "column_prop", + "email", + "favorite_pet_kind", + "composite_prop", + "hybrid_prop_with_doc", + "hybrid_prop", + "hybrid_prop_str", + "hybrid_prop_int", + "hybrid_prop_float", + "hybrid_prop_bool", + "hybrid_prop_list", + "pets", + "articles", + "favorite_article", + ]) def test_only_and_exclude_fields(): re_err = r"'only_fields' and 'exclude_fields' cannot be both set" with pytest.raises(Exception, match=re_err): - class ReporterType(SQLAlchemyObjectType): class Meta: model = Reporter @@ -396,14 +372,14 @@ def test_resolvers(session): class ReporterMixin(object): def resolve_id(root, _info): - return "ID" + return 'ID' class ReporterType(ReporterMixin, SQLAlchemyObjectType): class Meta: model = Reporter email = ORMField() - email_v2 = ORMField(model_attr="email") + email_v2 = ORMField(model_attr='email') favorite_pet_kind = Field(String) favorite_pet_kind_v2 = Field(String) @@ -411,10 +387,10 @@ def resolve_last_name(root, _info): return root.last_name.upper() def resolve_email_v2(root, _info): - return root.email + "_V2" + return root.email + '_V2' def resolve_favorite_pet_kind_v2(root, _info): - return str(root.favorite_pet_kind) + "_V2" + return str(root.favorite_pet_kind) + '_V2' class Query(ObjectType): reporter = Field(ReporterType) @@ -422,18 +398,12 @@ class Query(ObjectType): def resolve_reporter(self, _info): return session.query(Reporter).first() - reporter = Reporter( - first_name="first_name", - last_name="last_name", - email="email", - favorite_pet_kind="cat", - ) + reporter = Reporter(first_name='first_name', last_name='last_name', email='email', favorite_pet_kind='cat') session.add(reporter) session.commit() schema = Schema(query=Query) - result = schema.execute( - """ + result = schema.execute(""" query { reporter { id @@ -445,29 +415,27 @@ def resolve_reporter(self, _info): favoritePetKindV2 } } - """ - ) + """) assert not result.errors # Custom resolver on a base class - assert result.data["reporter"]["id"] == "ID" + assert result.data['reporter']['id'] == 'ID' # Default field + default resolver - assert result.data["reporter"]["firstName"] == "first_name" + assert result.data['reporter']['firstName'] == 'first_name' # Default field + custom resolver - assert result.data["reporter"]["lastName"] == "LAST_NAME" + assert result.data['reporter']['lastName'] == 'LAST_NAME' # ORMField + default resolver - assert result.data["reporter"]["email"] == "email" + assert result.data['reporter']['email'] == 'email' # ORMField + custom resolver - assert result.data["reporter"]["emailV2"] == "email_V2" + assert result.data['reporter']['emailV2'] == 'email_V2' # Field + default resolver - assert result.data["reporter"]["favoritePetKind"] == "cat" + assert result.data['reporter']['favoritePetKind'] == 'cat' # Field + custom resolver - assert result.data["reporter"]["favoritePetKindV2"] == "cat_V2" + assert result.data['reporter']['favoritePetKindV2'] == 'cat_V2' # Test Custom SQLAlchemyObjectType Implementation - def test_custom_objecttype_registered(): class CustomSQLAlchemyObjectType(SQLAlchemyObjectType): class Meta: @@ -495,9 +463,9 @@ class Meta: def __init_subclass_with_meta__(cls, custom_option=None, **options): _meta = CustomOptions(cls) _meta.custom_option = custom_option - super( - SQLAlchemyObjectTypeWithCustomOptions, cls - ).__init_subclass_with_meta__(_meta=_meta, **options) + super(SQLAlchemyObjectTypeWithCustomOptions, cls).__init_subclass_with_meta__( + _meta=_meta, **options + ) class ReporterWithCustomOptions(SQLAlchemyObjectTypeWithCustomOptions): class Meta: @@ -511,7 +479,6 @@ class Meta: # Tests for connection_field_factory - class _TestSQLAlchemyConnectionField(SQLAlchemyConnectionField): pass @@ -527,9 +494,7 @@ class Meta: model = Article interfaces = (Node,) - assert isinstance( - ReporterType._meta.fields["articles"].type(), UnsortedSQLAlchemyConnectionField - ) + assert isinstance(ReporterType._meta.fields['articles'].type(), UnsortedSQLAlchemyConnectionField) def test_custom_connection_field_factory(): @@ -549,9 +514,7 @@ class Meta: model = Article interfaces = (Node,) - assert isinstance( - ReporterType._meta.fields["articles"].type(), _TestSQLAlchemyConnectionField - ) + assert isinstance(ReporterType._meta.fields['articles'].type(), _TestSQLAlchemyConnectionField) def test_deprecated_registerConnectionFieldFactory(): @@ -568,9 +531,7 @@ class Meta: model = Article interfaces = (Node,) - assert isinstance( - ReporterType._meta.fields["articles"].type(), _TestSQLAlchemyConnectionField - ) + assert isinstance(ReporterType._meta.fields['articles'].type(), _TestSQLAlchemyConnectionField) def test_deprecated_unregisterConnectionFieldFactory(): @@ -588,9 +549,7 @@ class Meta: model = Article interfaces = (Node,) - assert not isinstance( - ReporterType._meta.fields["articles"].type(), _TestSQLAlchemyConnectionField - ) + assert not isinstance(ReporterType._meta.fields['articles'].type(), _TestSQLAlchemyConnectionField) def test_deprecated_createConnectionField(): @@ -598,7 +557,7 @@ def test_deprecated_createConnectionField(): createConnectionField(None) -@mock.patch(utils.__name__ + ".class_mapper") +@mock.patch(utils.__name__ + '.class_mapper') def test_unique_errors_propagate(class_mapper_mock): # Define unique error to detect class UniqueError(Exception): @@ -610,11 +569,9 @@ class UniqueError(Exception): # Make sure that errors are propagated from class_mapper when instantiating new classes error = None try: - class ArticleOne(SQLAlchemyObjectType): class Meta(object): model = Article - except UniqueError as e: error = e @@ -623,7 +580,7 @@ class Meta(object): assert isinstance(error, UniqueError) -@mock.patch(utils.__name__ + ".class_mapper") +@mock.patch(utils.__name__ + '.class_mapper') def test_argument_errors_propagate(class_mapper_mock): # Mock class_mapper effect class_mapper_mock.side_effect = sqlalchemy.exc.ArgumentError @@ -631,11 +588,9 @@ def test_argument_errors_propagate(class_mapper_mock): # Make sure that errors are propagated from class_mapper when instantiating new classes error = None try: - class ArticleTwo(SQLAlchemyObjectType): class Meta(object): model = Article - except sqlalchemy.exc.ArgumentError as e: error = e @@ -644,7 +599,7 @@ class Meta(object): assert isinstance(error, sqlalchemy.exc.ArgumentError) -@mock.patch(utils.__name__ + ".class_mapper") +@mock.patch(utils.__name__ + '.class_mapper') def test_unmapped_errors_reformat(class_mapper_mock): # Mock class_mapper effect class_mapper_mock.side_effect = sqlalchemy.orm.exc.UnmappedClassError(object) @@ -652,11 +607,9 @@ def test_unmapped_errors_reformat(class_mapper_mock): # Make sure that errors are propagated from class_mapper when instantiating new classes error = None try: - class ArticleThree(SQLAlchemyObjectType): class Meta(object): model = Article - except ValueError as e: error = e diff --git a/graphene_sqlalchemy/tests/test_utils.py b/graphene_sqlalchemy/tests/test_utils.py index b0afe41c..e13d919c 100644 --- a/graphene_sqlalchemy/tests/test_utils.py +++ b/graphene_sqlalchemy/tests/test_utils.py @@ -3,13 +3,8 @@ from graphene import Enum, List, ObjectType, Schema, String -from ..utils import ( - get_session, - sort_argument_for_model, - sort_enum_for_model, - to_enum_value_name, - to_type_name, -) +from ..utils import (get_session, sort_argument_for_model, sort_enum_for_model, + to_enum_value_name, to_type_name) from .models import Base, Editor, Pet @@ -101,7 +96,6 @@ class MultiplePK(Base): with pytest.warns(DeprecationWarning): arg = sort_argument_for_model(MultiplePK) - assert set(arg.default_value) == { - MultiplePK.foo.name + "_asc", - MultiplePK.bar.name + "_asc", - } + assert set(arg.default_value) == set( + (MultiplePK.foo.name + "_asc", MultiplePK.bar.name + "_asc") + ) diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index fe48e9eb..ac69b697 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -2,7 +2,8 @@ import sqlalchemy from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.orm import ColumnProperty, CompositeProperty, RelationshipProperty +from sqlalchemy.orm import (ColumnProperty, CompositeProperty, + RelationshipProperty) from sqlalchemy.orm.exc import NoResultFound from graphene import Field @@ -11,17 +12,12 @@ from graphene.types.utils import yank_fields_from_attrs from graphene.utils.orderedtype import OrderedType -from .converter import ( - convert_sqlalchemy_column, - convert_sqlalchemy_composite, - convert_sqlalchemy_hybrid_method, - convert_sqlalchemy_relationship, -) -from .enums import ( - enum_for_field, - sort_argument_for_object_type, - sort_enum_for_object_type, -) +from .converter import (convert_sqlalchemy_column, + convert_sqlalchemy_composite, + convert_sqlalchemy_hybrid_method, + convert_sqlalchemy_relationship) +from .enums import (enum_for_field, sort_argument_for_object_type, + sort_enum_for_object_type) from .registry import Registry, get_global_registry from .resolvers import get_attr_resolver, get_custom_resolver from .utils import get_query, is_mapped_class, is_mapped_instance @@ -80,28 +76,20 @@ class Meta: super(ORMField, self).__init__(_creation_counter=_creation_counter) # The is only useful for documentation and auto-completion common_kwargs = { - "model_attr": model_attr, - "type_": type_, - "required": required, - "description": description, - "deprecation_reason": deprecation_reason, - "batching": batching, - } - common_kwargs = { - kwarg: value for kwarg, value in common_kwargs.items() if value is not None + 'model_attr': model_attr, + 'type_': type_, + 'required': required, + 'description': description, + 'deprecation_reason': deprecation_reason, + 'batching': batching, } + common_kwargs = {kwarg: value for kwarg, value in common_kwargs.items() if value is not None} self.kwargs = field_kwargs self.kwargs.update(common_kwargs) def construct_fields( - obj_type, - model, - registry, - only_fields, - exclude_fields, - batching, - connection_field_factory, + obj_type, model, registry, only_fields, exclude_fields, batching, connection_field_factory ): """ Construct all the fields for a SQLAlchemyObjectType. @@ -122,22 +110,17 @@ def construct_fields( inspected_model = sqlalchemy.inspect(model) # Gather all the relevant attributes from the SQLAlchemy model in order all_model_attrs = OrderedDict( - inspected_model.column_attrs.items() - + inspected_model.composites.items() - + [ - (name, item) - for name, item in inspected_model.all_orm_descriptors.items() - if isinstance(item, hybrid_property) - ] - + inspected_model.relationships.items() + inspected_model.column_attrs.items() + + inspected_model.composites.items() + + [(name, item) for name, item in inspected_model.all_orm_descriptors.items() + if isinstance(item, hybrid_property)] + + inspected_model.relationships.items() ) # Filter out excluded fields auto_orm_field_names = [] for attr_name, attr in all_model_attrs.items(): - if (only_fields and attr_name not in only_fields) or ( - attr_name in exclude_fields - ): + if (only_fields and attr_name not in only_fields) or (attr_name in exclude_fields): continue auto_orm_field_names.append(attr_name) @@ -152,15 +135,13 @@ def construct_fields( # Set the model_attr if not set for orm_field_name, orm_field in custom_orm_fields_items: - attr_name = orm_field.kwargs.get("model_attr", orm_field_name) + attr_name = orm_field.kwargs.get('model_attr', orm_field_name) if attr_name not in all_model_attrs: - raise ValueError( - ("Cannot map ORMField to a model attribute.\n" "Field: '{}.{}'").format( - obj_type.__name__, - orm_field_name, - ) - ) - orm_field.kwargs["model_attr"] = attr_name + raise ValueError(( + "Cannot map ORMField to a model attribute.\n" + "Field: '{}.{}'" + ).format(obj_type.__name__, orm_field_name,)) + orm_field.kwargs['model_attr'] = attr_name # Merge automatic fields with custom ORM fields orm_fields = OrderedDict(custom_orm_fields_items) @@ -172,38 +153,27 @@ def construct_fields( # Build all the field dictionary fields = OrderedDict() for orm_field_name, orm_field in orm_fields.items(): - attr_name = orm_field.kwargs.pop("model_attr") + attr_name = orm_field.kwargs.pop('model_attr') attr = all_model_attrs[attr_name] - resolver = get_custom_resolver(obj_type, orm_field_name) or get_attr_resolver( - obj_type, attr_name - ) + resolver = get_custom_resolver(obj_type, orm_field_name) or get_attr_resolver(obj_type, attr_name) if isinstance(attr, ColumnProperty): - field = convert_sqlalchemy_column( - attr, registry, resolver, **orm_field.kwargs - ) + field = convert_sqlalchemy_column(attr, registry, resolver, **orm_field.kwargs) elif isinstance(attr, RelationshipProperty): - batching_ = orm_field.kwargs.pop("batching", batching) + batching_ = orm_field.kwargs.pop('batching', batching) field = convert_sqlalchemy_relationship( - attr, - obj_type, - connection_field_factory, - batching_, - orm_field_name, - **orm_field.kwargs - ) + attr, obj_type, connection_field_factory, batching_, orm_field_name, **orm_field.kwargs) elif isinstance(attr, CompositeProperty): if attr_name != orm_field_name or orm_field.kwargs: # TODO Add a way to override composite property fields raise ValueError( "ORMField kwargs for composite fields must be empty. " - "Field: {}.{}".format(obj_type.__name__, orm_field_name) - ) + "Field: {}.{}".format(obj_type.__name__, orm_field_name)) field = convert_sqlalchemy_composite(attr, registry, resolver) elif isinstance(attr, hybrid_property): field = convert_sqlalchemy_hybrid_method(attr, resolver, **orm_field.kwargs) else: - raise Exception("Property type is not supported") # Should never happen + raise Exception('Property type is not supported') # Should never happen registry.register_orm_field(obj_type, orm_field_name, attr) fields[orm_field_name] = field @@ -240,8 +210,7 @@ def __init_subclass_with_meta__( # Make sure model is a valid SQLAlchemy model if not is_mapped_class(model): raise ValueError( - "You need to pass a valid SQLAlchemy Model in " - '{}.Meta, received "{}".'.format(cls.__name__, model) + "You need to pass a valid SQLAlchemy Model in " '{}.Meta, received "{}".'.format(cls.__name__, model) ) if not registry: @@ -253,9 +222,7 @@ def __init_subclass_with_meta__( ).format(cls.__name__, registry) if only_fields and exclude_fields: - raise ValueError( - "The options 'only_fields' and 'exclude_fields' cannot be both set on the same type." - ) + raise ValueError("The options 'only_fields' and 'exclude_fields' cannot be both set on the same type.") sqla_fields = yank_fields_from_attrs( construct_fields( @@ -273,7 +240,7 @@ def __init_subclass_with_meta__( if use_connection is None and interfaces: use_connection = any( - issubclass(interface, Node) for interface in interfaces + (issubclass(interface, Node) for interface in interfaces) ) if use_connection and not connection: diff --git a/graphene_sqlalchemy/utils.py b/graphene_sqlalchemy/utils.py index b42580b1..084f9b86 100644 --- a/graphene_sqlalchemy/utils.py +++ b/graphene_sqlalchemy/utils.py @@ -155,9 +155,7 @@ def sort_argument_for_model(cls, has_default=True): def is_sqlalchemy_version_less_than(version_string): """Check the installed SQLAlchemy version""" - return pkg_resources.get_distribution( - "SQLAlchemy" - ).parsed_version < pkg_resources.parse_version(version_string) + return pkg_resources.get_distribution('SQLAlchemy').parsed_version < pkg_resources.parse_version(version_string) class singledispatchbymatchfunction: @@ -181,6 +179,7 @@ def __call__(self, *args, **kwargs): return self.default(*args, **kwargs) def register(self, matcher_function: Callable[[Any], bool]): + def grab_function_from_outside(f): self.registry[matcher_function] = f return self @@ -190,7 +189,7 @@ def grab_function_from_outside(f): def value_equals(value): """A simple function that makes the equality based matcher functions for - SingleDispatchByMatchFunction prettier""" + SingleDispatchByMatchFunction prettier""" return lambda x: x == value @@ -200,17 +199,11 @@ def safe_isinstance_checker(arg): return isinstance(arg, cls) except TypeError: pass - return safe_isinstance_checker def registry_sqlalchemy_model_from_str(model_name: str) -> Optional[Any]: try: - return next( - filter( - lambda x: x.__name__ == model_name, - list(get_global_registry()._registry.keys()), - ) - ) + return next(filter(lambda x: x.__name__ == model_name, list(get_global_registry()._registry.keys()))) except StopIteration: pass From 664745a0d794c62ec6e0f289a18de933b0f2100d Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Fri, 3 Jun 2022 12:03:14 +0200 Subject: [PATCH 08/10] Push back Black integration until current PRs are merged. --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d76c4a0a..66db3814 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,12 +11,12 @@ repos: exclude: ^docs/.*$ - id: trailing-whitespace exclude: README.md - - repo: https://github.com/PyCQA/flake8 - rev: 4.0.0 - hooks: - - id: flake8 - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: - id: isort name: isort (python) + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.0 + hooks: + - id: flake8 From c5624037d9c839f2e68237bc14ae77613f5a5ebf Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Fri, 3 Jun 2022 12:20:10 +0200 Subject: [PATCH 09/10] Fix Flake8 --- .flake8 | 2 +- graphene_sqlalchemy/types.py | 58 ++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.flake8 b/.flake8 index ccded588..30f6dedd 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,4 @@ [flake8] ignore = E203,W503 exclude = .git,.mypy_cache,.pytest_cache,.tox,.venv,__pycache__,build,dist,docs -max-line-length = 88 +max-line-length = 120 diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index ac69b697..d90984cc 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -25,15 +25,15 @@ class ORMField(OrderedType): def __init__( - self, - model_attr=None, - type_=None, - required=None, - description=None, - deprecation_reason=None, - batching=None, - _creation_counter=None, - **field_kwargs + self, + model_attr=None, + type_=None, + required=None, + description=None, + deprecation_reason=None, + batching=None, + _creation_counter=None, + **field_kwargs ): """ Use this to override fields automatically generated by SQLAlchemyObjectType. @@ -89,7 +89,7 @@ class Meta: def construct_fields( - obj_type, model, registry, only_fields, exclude_fields, batching, connection_field_factory + obj_type, model, registry, only_fields, exclude_fields, batching, connection_field_factory ): """ Construct all the fields for a SQLAlchemyObjectType. @@ -111,10 +111,10 @@ def construct_fields( # Gather all the relevant attributes from the SQLAlchemy model in order all_model_attrs = OrderedDict( inspected_model.column_attrs.items() + - inspected_model.composites.items() + - [(name, item) for name, item in inspected_model.all_orm_descriptors.items() - if isinstance(item, hybrid_property)] + - inspected_model.relationships.items() + + inspected_model.composites.items() + + [(name, item) for name, item in inspected_model.all_orm_descriptors.items() + if isinstance(item, hybrid_property)] + + inspected_model.relationships.items() ) # Filter out excluded fields @@ -191,21 +191,21 @@ class SQLAlchemyObjectTypeOptions(ObjectTypeOptions): class SQLAlchemyObjectType(ObjectType): @classmethod def __init_subclass_with_meta__( - cls, - model=None, - registry=None, - skip_registry=False, - only_fields=(), - exclude_fields=(), - connection=None, - connection_class=None, - use_connection=None, - interfaces=(), - id=None, - batching=False, - connection_field_factory=None, - _meta=None, - **options + cls, + model=None, + registry=None, + skip_registry=False, + only_fields=(), + exclude_fields=(), + connection=None, + connection_class=None, + use_connection=None, + interfaces=(), + id=None, + batching=False, + connection_field_factory=None, + _meta=None, + **options ): # Make sure model is a valid SQLAlchemy model if not is_mapped_class(model): From 7f1a71742480c819aa43eacca95aca0b19bab4cc Mon Sep 17 00:00:00 2001 From: Erik Wrede Date: Fri, 3 Jun 2022 12:23:26 +0200 Subject: [PATCH 10/10] Fix typo --- graphene_sqlalchemy/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index d90984cc..e6c3d14c 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -110,7 +110,7 @@ def construct_fields( inspected_model = sqlalchemy.inspect(model) # Gather all the relevant attributes from the SQLAlchemy model in order all_model_attrs = OrderedDict( - inspected_model.column_attrs.items() + + inspected_model.column_attrs.items() + inspected_model.composites.items() + [(name, item) for name, item in inspected_model.all_orm_descriptors.items() if isinstance(item, hybrid_property)]