mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-25 19:13:57 +03:00
commit
f039af2810
25
.github/workflows/coveralls.yml
vendored
Normal file
25
.github/workflows/coveralls.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
name: 📊 Check Coverage
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- '*.x'
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- '*.md'
|
||||||
|
- '*.rst'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- '*.x'
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- '*.md'
|
||||||
|
- '*.rst'
|
||||||
|
jobs:
|
||||||
|
coveralls_finish:
|
||||||
|
# check coverage increase/decrease
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Coveralls Finished
|
||||||
|
uses: AndreMiras/coveralls-python-action@develop
|
26
.github/workflows/deploy.yml
vendored
Normal file
26
.github/workflows/deploy.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
name: 🚀 Deploy to PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python 3.8
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
- name: Build wheel and source tarball
|
||||||
|
run: |
|
||||||
|
pip install wheel
|
||||||
|
python setup.py sdist bdist_wheel
|
||||||
|
- name: Publish a Python distribution to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@v1.1.0
|
||||||
|
with:
|
||||||
|
user: __token__
|
||||||
|
password: ${{ secrets.pypi_password }}
|
26
.github/workflows/lint.yml
vendored
Normal file
26
.github/workflows/lint.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
name: 💅 Lint
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python 3.8
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install tox
|
||||||
|
- name: Run lint
|
||||||
|
run: tox
|
||||||
|
env:
|
||||||
|
TOXENV: pre-commit
|
||||||
|
- name: Run mypy
|
||||||
|
run: tox
|
||||||
|
env:
|
||||||
|
TOXENV: mypy
|
29
.github/workflows/tests.yml
vendored
29
.github/workflows/tests.yml
vendored
|
@ -52,32 +52,3 @@ jobs:
|
||||||
|
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox -e ${{ matrix.tox }}
|
- run: tox -e ${{ matrix.tox }}
|
||||||
|
|
||||||
coveralls_finish:
|
|
||||||
# check coverage increase/decrease
|
|
||||||
needs: tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Coveralls Finished
|
|
||||||
uses: AndreMiras/coveralls-python-action@develop
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
# builds and publishes to PyPi
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: '3.7'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install build
|
|
||||||
- name: Build package
|
|
||||||
run: python -m build
|
|
||||||
- name: Publish package
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
user: __token__
|
|
||||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
default_language_version:
|
||||||
|
python: python3.8
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v2.1.0
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-json
|
- id: check-json
|
||||||
|
@ -13,16 +16,15 @@ repos:
|
||||||
- --autofix
|
- --autofix
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude: README.md
|
exclude: README.md
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: git://github.com/asottile/pyupgrade
|
||||||
rev: v1.12.0
|
rev: v2.24.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
- repo: https://github.com/ambv/black
|
- repo: git://github.com/ambv/black
|
||||||
rev: 19.10b0
|
rev: 19.3b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3
|
- repo: git://github.com/PyCQA/flake8
|
||||||
- repo: https://github.com/PyCQA/flake8
|
rev: 3.8.4
|
||||||
rev: 3.7.8
|
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
|
@ -291,14 +291,7 @@ class Field:
|
||||||
|
|
||||||
|
|
||||||
class _DataclassParams:
|
class _DataclassParams:
|
||||||
__slots__ = (
|
__slots__ = ("init", "repr", "eq", "order", "unsafe_hash", "frozen")
|
||||||
"init",
|
|
||||||
"repr",
|
|
||||||
"eq",
|
|
||||||
"order",
|
|
||||||
"unsafe_hash",
|
|
||||||
"frozen",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, init, repr, eq, order, unsafe_hash, frozen):
|
def __init__(self, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
self.init = init
|
self.init = init
|
||||||
|
@ -442,13 +435,11 @@ def _field_init(f, frozen, globals, self_name):
|
||||||
# This field does not need initialization. Signify that
|
# This field does not need initialization. Signify that
|
||||||
# to the caller by returning None.
|
# to the caller by returning None.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Only test this now, so that we can create variables for the
|
# Only test this now, so that we can create variables for the
|
||||||
# default. However, return None to signify that we're not going
|
# default. However, return None to signify that we're not going
|
||||||
# to actually do the assignment statement for InitVars.
|
# to actually do the assignment statement for InitVars.
|
||||||
if f._field_type == _FIELD_INITVAR:
|
if f._field_type == _FIELD_INITVAR:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Now, actually generate the field assignment.
|
# Now, actually generate the field assignment.
|
||||||
return _field_assign(frozen, f.name, value, self_name)
|
return _field_assign(frozen, f.name, value, self_name)
|
||||||
|
|
||||||
|
@ -490,7 +481,6 @@ def _init_fn(fields, frozen, has_post_init, self_name):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"non-default argument {f.name!r} " "follows default argument"
|
f"non-default argument {f.name!r} " "follows default argument"
|
||||||
)
|
)
|
||||||
|
|
||||||
globals = {"MISSING": MISSING, "_HAS_DEFAULT_FACTORY": _HAS_DEFAULT_FACTORY}
|
globals = {"MISSING": MISSING, "_HAS_DEFAULT_FACTORY": _HAS_DEFAULT_FACTORY}
|
||||||
|
|
||||||
body_lines = []
|
body_lines = []
|
||||||
|
@ -500,16 +490,13 @@ def _init_fn(fields, frozen, has_post_init, self_name):
|
||||||
# initialization (it's a pseudo-field). Just skip it.
|
# initialization (it's a pseudo-field). Just skip it.
|
||||||
if line:
|
if line:
|
||||||
body_lines.append(line)
|
body_lines.append(line)
|
||||||
|
|
||||||
# Does this class have a post-init function?
|
# Does this class have a post-init function?
|
||||||
if has_post_init:
|
if has_post_init:
|
||||||
params_str = ",".join(f.name for f in fields if f._field_type is _FIELD_INITVAR)
|
params_str = ",".join(f.name for f in fields if f._field_type is _FIELD_INITVAR)
|
||||||
body_lines.append(f"{self_name}.{_POST_INIT_NAME}({params_str})")
|
body_lines.append(f"{self_name}.{_POST_INIT_NAME}({params_str})")
|
||||||
|
|
||||||
# If no body lines, use 'pass'.
|
# If no body lines, use 'pass'.
|
||||||
if not body_lines:
|
if not body_lines:
|
||||||
body_lines = ["pass"]
|
body_lines = ["pass"]
|
||||||
|
|
||||||
locals = {f"_type_{f.name}": f.type for f in fields}
|
locals = {f"_type_{f.name}": f.type for f in fields}
|
||||||
return _create_fn(
|
return _create_fn(
|
||||||
"__init__",
|
"__init__",
|
||||||
|
@ -674,7 +661,6 @@ def _get_field(cls, a_name, a_type):
|
||||||
# This is a field in __slots__, so it has no default value.
|
# This is a field in __slots__, so it has no default value.
|
||||||
default = MISSING
|
default = MISSING
|
||||||
f = field(default=default)
|
f = field(default=default)
|
||||||
|
|
||||||
# Only at this point do we know the name and the type. Set them.
|
# Only at this point do we know the name and the type. Set them.
|
||||||
f.name = a_name
|
f.name = a_name
|
||||||
f.type = a_type
|
f.type = a_type
|
||||||
|
@ -705,7 +691,6 @@ def _get_field(cls, a_name, a_type):
|
||||||
and _is_type(f.type, cls, typing, typing.ClassVar, _is_classvar)
|
and _is_type(f.type, cls, typing, typing.ClassVar, _is_classvar)
|
||||||
):
|
):
|
||||||
f._field_type = _FIELD_CLASSVAR
|
f._field_type = _FIELD_CLASSVAR
|
||||||
|
|
||||||
# If the type is InitVar, or if it's a matching string annotation,
|
# If the type is InitVar, or if it's a matching string annotation,
|
||||||
# then it's an InitVar.
|
# then it's an InitVar.
|
||||||
if f._field_type is _FIELD:
|
if f._field_type is _FIELD:
|
||||||
|
@ -717,7 +702,6 @@ def _get_field(cls, a_name, a_type):
|
||||||
and _is_type(f.type, cls, dataclasses, dataclasses.InitVar, _is_initvar)
|
and _is_type(f.type, cls, dataclasses, dataclasses.InitVar, _is_initvar)
|
||||||
):
|
):
|
||||||
f._field_type = _FIELD_INITVAR
|
f._field_type = _FIELD_INITVAR
|
||||||
|
|
||||||
# Validations for individual fields. This is delayed until now,
|
# Validations for individual fields. This is delayed until now,
|
||||||
# instead of in the Field() constructor, since only here do we
|
# instead of in the Field() constructor, since only here do we
|
||||||
# know the field name, which allows for better error reporting.
|
# know the field name, which allows for better error reporting.
|
||||||
|
@ -731,14 +715,12 @@ def _get_field(cls, a_name, a_type):
|
||||||
# example, how about init=False (or really,
|
# example, how about init=False (or really,
|
||||||
# init=<not-the-default-init-value>)? It makes no sense for
|
# init=<not-the-default-init-value>)? It makes no sense for
|
||||||
# ClassVar and InitVar to specify init=<anything>.
|
# ClassVar and InitVar to specify init=<anything>.
|
||||||
|
|
||||||
# For real fields, disallow mutable defaults for known types.
|
# For real fields, disallow mutable defaults for known types.
|
||||||
if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)):
|
if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"mutable default {type(f.default)} for field "
|
f"mutable default {type(f.default)} for field "
|
||||||
f"{f.name} is not allowed: use default_factory"
|
f"{f.name} is not allowed: use default_factory"
|
||||||
)
|
)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
@ -827,7 +809,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
fields[f.name] = f
|
fields[f.name] = f
|
||||||
if getattr(b, _PARAMS).frozen:
|
if getattr(b, _PARAMS).frozen:
|
||||||
any_frozen_base = True
|
any_frozen_base = True
|
||||||
|
|
||||||
# Annotations that are defined in this class (not in base
|
# Annotations that are defined in this class (not in base
|
||||||
# classes). If __annotations__ isn't present, then this class
|
# classes). If __annotations__ isn't present, then this class
|
||||||
# adds no new annotations. We use this to compute fields that are
|
# adds no new annotations. We use this to compute fields that are
|
||||||
|
@ -866,22 +847,18 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
delattr(cls, f.name)
|
delattr(cls, f.name)
|
||||||
else:
|
else:
|
||||||
setattr(cls, f.name, f.default)
|
setattr(cls, f.name, f.default)
|
||||||
|
|
||||||
# Do we have any Field members that don't also have annotations?
|
# Do we have any Field members that don't also have annotations?
|
||||||
for name, value in cls.__dict__.items():
|
for name, value in cls.__dict__.items():
|
||||||
if isinstance(value, Field) and not name in cls_annotations:
|
if isinstance(value, Field) and not name in cls_annotations:
|
||||||
raise TypeError(f"{name!r} is a field but has no type annotation")
|
raise TypeError(f"{name!r} is a field but has no type annotation")
|
||||||
|
|
||||||
# Check rules that apply if we are derived from any dataclasses.
|
# Check rules that apply if we are derived from any dataclasses.
|
||||||
if has_dataclass_bases:
|
if has_dataclass_bases:
|
||||||
# Raise an exception if any of our bases are frozen, but we're not.
|
# Raise an exception if any of our bases are frozen, but we're not.
|
||||||
if any_frozen_base and not frozen:
|
if any_frozen_base and not frozen:
|
||||||
raise TypeError("cannot inherit non-frozen dataclass from a " "frozen one")
|
raise TypeError("cannot inherit non-frozen dataclass from a " "frozen one")
|
||||||
|
|
||||||
# Raise an exception if we're frozen, but none of our bases are.
|
# Raise an exception if we're frozen, but none of our bases are.
|
||||||
if not any_frozen_base and frozen:
|
if not any_frozen_base and frozen:
|
||||||
raise TypeError("cannot inherit frozen dataclass from a " "non-frozen one")
|
raise TypeError("cannot inherit frozen dataclass from a " "non-frozen one")
|
||||||
|
|
||||||
# Remember all of the fields on our class (including bases). This
|
# Remember all of the fields on our class (including bases). This
|
||||||
# also marks this class as being a dataclass.
|
# also marks this class as being a dataclass.
|
||||||
setattr(cls, _FIELDS, fields)
|
setattr(cls, _FIELDS, fields)
|
||||||
|
@ -900,7 +877,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
# eq methods.
|
# eq methods.
|
||||||
if order and not eq:
|
if order and not eq:
|
||||||
raise ValueError("eq must be true if order is true")
|
raise ValueError("eq must be true if order is true")
|
||||||
|
|
||||||
if init:
|
if init:
|
||||||
# Does this class have a post-init function?
|
# Does this class have a post-init function?
|
||||||
has_post_init = hasattr(cls, _POST_INIT_NAME)
|
has_post_init = hasattr(cls, _POST_INIT_NAME)
|
||||||
|
@ -920,7 +896,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
"__dataclass_self__" if "self" in fields else "self",
|
"__dataclass_self__" if "self" in fields else "self",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the fields as a list, and include only real fields. This is
|
# Get the fields as a list, and include only real fields. This is
|
||||||
# used in all of the following methods.
|
# used in all of the following methods.
|
||||||
field_list = [f for f in fields.values() if f._field_type is _FIELD]
|
field_list = [f for f in fields.values() if f._field_type is _FIELD]
|
||||||
|
@ -928,7 +903,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
if repr:
|
if repr:
|
||||||
flds = [f for f in field_list if f.repr]
|
flds = [f for f in field_list if f.repr]
|
||||||
_set_new_attribute(cls, "__repr__", _repr_fn(flds))
|
_set_new_attribute(cls, "__repr__", _repr_fn(flds))
|
||||||
|
|
||||||
if eq:
|
if eq:
|
||||||
# Create _eq__ method. There's no need for a __ne__ method,
|
# Create _eq__ method. There's no need for a __ne__ method,
|
||||||
# since python will call __eq__ and negate it.
|
# since python will call __eq__ and negate it.
|
||||||
|
@ -938,7 +912,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
_set_new_attribute(
|
_set_new_attribute(
|
||||||
cls, "__eq__", _cmp_fn("__eq__", "==", self_tuple, other_tuple)
|
cls, "__eq__", _cmp_fn("__eq__", "==", self_tuple, other_tuple)
|
||||||
)
|
)
|
||||||
|
|
||||||
if order:
|
if order:
|
||||||
# Create and set the ordering methods.
|
# Create and set the ordering methods.
|
||||||
flds = [f for f in field_list if f.compare]
|
flds = [f for f in field_list if f.compare]
|
||||||
|
@ -958,7 +931,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
f"in class {cls.__name__}. Consider using "
|
f"in class {cls.__name__}. Consider using "
|
||||||
"functools.total_ordering"
|
"functools.total_ordering"
|
||||||
)
|
)
|
||||||
|
|
||||||
if frozen:
|
if frozen:
|
||||||
for fn in _frozen_get_del_attr(cls, field_list):
|
for fn in _frozen_get_del_attr(cls, field_list):
|
||||||
if _set_new_attribute(cls, fn.__name__, fn):
|
if _set_new_attribute(cls, fn.__name__, fn):
|
||||||
|
@ -966,7 +938,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
f"Cannot overwrite attribute {fn.__name__} "
|
f"Cannot overwrite attribute {fn.__name__} "
|
||||||
f"in class {cls.__name__}"
|
f"in class {cls.__name__}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Decide if/how we're going to create a hash function.
|
# Decide if/how we're going to create a hash function.
|
||||||
hash_action = _hash_action[
|
hash_action = _hash_action[
|
||||||
bool(unsafe_hash), bool(eq), bool(frozen), has_explicit_hash
|
bool(unsafe_hash), bool(eq), bool(frozen), has_explicit_hash
|
||||||
|
@ -975,11 +946,9 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
# No need to call _set_new_attribute here, since by the time
|
# No need to call _set_new_attribute here, since by the time
|
||||||
# we're here the overwriting is unconditional.
|
# we're here the overwriting is unconditional.
|
||||||
cls.__hash__ = hash_action(cls, field_list)
|
cls.__hash__ = hash_action(cls, field_list)
|
||||||
|
|
||||||
if not getattr(cls, "__doc__"):
|
if not getattr(cls, "__doc__"):
|
||||||
# Create a class doc-string.
|
# Create a class doc-string.
|
||||||
cls.__doc__ = cls.__name__ + str(inspect.signature(cls)).replace(" -> None", "")
|
cls.__doc__ = cls.__name__ + str(inspect.signature(cls)).replace(" -> None", "")
|
||||||
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
@ -1015,7 +984,6 @@ def dataclass(
|
||||||
if _cls is None:
|
if _cls is None:
|
||||||
# We're called with parens.
|
# We're called with parens.
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
# We're called as @dataclass without parens.
|
# We're called as @dataclass without parens.
|
||||||
return wrap(_cls)
|
return wrap(_cls)
|
||||||
|
|
||||||
|
@ -1032,7 +1000,6 @@ def fields(class_or_instance):
|
||||||
fields = getattr(class_or_instance, _FIELDS)
|
fields = getattr(class_or_instance, _FIELDS)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise TypeError("must be called with a dataclass type or instance")
|
raise TypeError("must be called with a dataclass type or instance")
|
||||||
|
|
||||||
# Exclude pseudo-fields. Note that fields is sorted by insertion
|
# Exclude pseudo-fields. Note that fields is sorted by insertion
|
||||||
# order, so the order of the tuple is as the fields were defined.
|
# order, so the order of the tuple is as the fields were defined.
|
||||||
return tuple(f for f in fields.values() if f._field_type is _FIELD)
|
return tuple(f for f in fields.values() if f._field_type is _FIELD)
|
||||||
|
@ -1174,7 +1141,6 @@ def make_dataclass(
|
||||||
else:
|
else:
|
||||||
# Copy namespace since we're going to mutate it.
|
# Copy namespace since we're going to mutate it.
|
||||||
namespace = namespace.copy()
|
namespace = namespace.copy()
|
||||||
|
|
||||||
# While we're looking through the field names, validate that they
|
# While we're looking through the field names, validate that they
|
||||||
# are identifiers, are not keywords, and not duplicates.
|
# are identifiers, are not keywords, and not duplicates.
|
||||||
seen = set()
|
seen = set()
|
||||||
|
@ -1184,23 +1150,20 @@ def make_dataclass(
|
||||||
name = item
|
name = item
|
||||||
tp = "typing.Any"
|
tp = "typing.Any"
|
||||||
elif len(item) == 2:
|
elif len(item) == 2:
|
||||||
name, tp, = item
|
(name, tp) = item
|
||||||
elif len(item) == 3:
|
elif len(item) == 3:
|
||||||
name, tp, spec = item
|
name, tp, spec = item
|
||||||
namespace[name] = spec
|
namespace[name] = spec
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Invalid field: {item!r}")
|
raise TypeError(f"Invalid field: {item!r}")
|
||||||
|
|
||||||
if not isinstance(name, str) or not name.isidentifier():
|
if not isinstance(name, str) or not name.isidentifier():
|
||||||
raise TypeError(f"Field names must be valid identifers: {name!r}")
|
raise TypeError(f"Field names must be valid identifers: {name!r}")
|
||||||
if keyword.iskeyword(name):
|
if keyword.iskeyword(name):
|
||||||
raise TypeError(f"Field names must not be keywords: {name!r}")
|
raise TypeError(f"Field names must not be keywords: {name!r}")
|
||||||
if name in seen:
|
if name in seen:
|
||||||
raise TypeError(f"Field name duplicated: {name!r}")
|
raise TypeError(f"Field name duplicated: {name!r}")
|
||||||
|
|
||||||
seen.add(name)
|
seen.add(name)
|
||||||
anns[name] = tp
|
anns[name] = tp
|
||||||
|
|
||||||
namespace["__annotations__"] = anns
|
namespace["__annotations__"] = anns
|
||||||
# We use `types.new_class()` instead of simply `type()` to allow dynamic creation
|
# We use `types.new_class()` instead of simply `type()` to allow dynamic creation
|
||||||
# of generic dataclassses.
|
# of generic dataclassses.
|
||||||
|
@ -1229,14 +1192,13 @@ def replace(obj, **changes):
|
||||||
c = C(1, 2)
|
c = C(1, 2)
|
||||||
c1 = replace(c, x=3)
|
c1 = replace(c, x=3)
|
||||||
assert c1.x == 3 and c1.y == 2
|
assert c1.x == 3 and c1.y == 2
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We're going to mutate 'changes', but that's okay because it's a
|
# We're going to mutate 'changes', but that's okay because it's a
|
||||||
# new dict, even if called with 'replace(obj, **my_changes)'.
|
# new dict, even if called with 'replace(obj, **my_changes)'.
|
||||||
|
|
||||||
if not _is_dataclass_instance(obj):
|
if not _is_dataclass_instance(obj):
|
||||||
raise TypeError("replace() should be called on dataclass instances")
|
raise TypeError("replace() should be called on dataclass instances")
|
||||||
|
|
||||||
# It's an error to have init=False fields in 'changes'.
|
# It's an error to have init=False fields in 'changes'.
|
||||||
# If a field is not in 'changes', read its value from the provided obj.
|
# If a field is not in 'changes', read its value from the provided obj.
|
||||||
|
|
||||||
|
@ -1250,10 +1212,8 @@ def replace(obj, **changes):
|
||||||
"replace()"
|
"replace()"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if f.name not in changes:
|
if f.name not in changes:
|
||||||
changes[f.name] = getattr(obj, f.name)
|
changes[f.name] = getattr(obj, f.name)
|
||||||
|
|
||||||
# Create the new object, which calls __init__() and
|
# Create the new object, which calls __init__() and
|
||||||
# __post_init__() (if defined), using all of the init fields we've
|
# __post_init__() (if defined), using all of the init fields we've
|
||||||
# added and/or left in 'changes'. If there are values supplied in
|
# added and/or left in 'changes'. If there are values supplied in
|
||||||
|
|
|
@ -92,11 +92,9 @@ class Node(AbstractNode):
|
||||||
_type, _id = cls.from_global_id(global_id)
|
_type, _id = cls.from_global_id(global_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
(
|
f'Unable to parse global ID "{global_id}". '
|
||||||
f'Unable to parse global ID "{global_id}". '
|
'Make sure it is a base64 encoded string in the format: "TypeName:id". '
|
||||||
'Make sure it is a base64 encoded string in the format: "TypeName:id". '
|
f"Exception message: {str(e)}"
|
||||||
f"Exception message: {str(e)}"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
graphene_type = info.schema.get_type(_type)
|
graphene_type = info.schema.get_type(_type)
|
||||||
|
|
|
@ -51,10 +51,10 @@ letters = {letter: Letter(id=i, letter=letter) for i, letter in enumerate(letter
|
||||||
def edges(selected_letters):
|
def edges(selected_letters):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"node": {"id": base64("Letter:%s" % l.id), "letter": l.letter},
|
"node": {"id": base64("Letter:%s" % letter.id), "letter": letter.letter},
|
||||||
"cursor": base64("arrayconnection:%s" % l.id),
|
"cursor": base64("arrayconnection:%s" % letter.id),
|
||||||
}
|
}
|
||||||
for l in [letters[i] for i in selected_letters]
|
for letter in [letters[i] for i in selected_letters]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,10 +51,10 @@ letters = {letter: Letter(id=i, letter=letter) for i, letter in enumerate(letter
|
||||||
def edges(selected_letters):
|
def edges(selected_letters):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"node": {"id": base64("Letter:%s" % l.id), "letter": l.letter},
|
"node": {"id": base64("Letter:%s" % letter.id), "letter": letter.letter},
|
||||||
"cursor": base64("arrayconnection:%s" % l.id),
|
"cursor": base64("arrayconnection:%s" % letter.id),
|
||||||
}
|
}
|
||||||
for l in [letters[i] for i in selected_letters]
|
for letter in [letters[i] for i in selected_letters]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@ def cursor_for(ltr):
|
||||||
async def execute(args=""):
|
async def execute(args=""):
|
||||||
if args:
|
if args:
|
||||||
args = "(" + args + ")"
|
args = "(" + args + ")"
|
||||||
|
|
||||||
return await schema.execute_async(
|
return await schema.execute_async(
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
|
@ -164,14 +163,14 @@ async def test_respects_first_and_after_and_before_too_few():
|
||||||
@mark.asyncio
|
@mark.asyncio
|
||||||
async def test_respects_first_and_after_and_before_too_many():
|
async def test_respects_first_and_after_and_before_too_many():
|
||||||
await check(
|
await check(
|
||||||
f'first: 4, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD",
|
f'first: 4, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@mark.asyncio
|
@mark.asyncio
|
||||||
async def test_respects_first_and_after_and_before_exactly_right():
|
async def test_respects_first_and_after_and_before_exactly_right():
|
||||||
await check(
|
await check(
|
||||||
f'first: 3, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD",
|
f'first: 3, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -187,14 +186,14 @@ async def test_respects_last_and_after_and_before_too_few():
|
||||||
@mark.asyncio
|
@mark.asyncio
|
||||||
async def test_respects_last_and_after_and_before_too_many():
|
async def test_respects_last_and_after_and_before_too_many():
|
||||||
await check(
|
await check(
|
||||||
f'last: 4, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD",
|
f'last: 4, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@mark.asyncio
|
@mark.asyncio
|
||||||
async def test_respects_last_and_after_and_before_exactly_right():
|
async def test_respects_last_and_after_and_before_exactly_right():
|
||||||
await check(
|
await check(
|
||||||
f'last: 3, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD",
|
f'last: 3, after: "{cursor_for("A")}", before: "{cursor_for("E")}"', "BCD"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,6 @@ class Mutation(ObjectType):
|
||||||
):
|
):
|
||||||
if not _meta:
|
if not _meta:
|
||||||
_meta = MutationOptions(cls)
|
_meta = MutationOptions(cls)
|
||||||
|
|
||||||
output = output or getattr(cls, "Output", None)
|
output = output or getattr(cls, "Output", None)
|
||||||
fields = {}
|
fields = {}
|
||||||
|
|
||||||
|
@ -85,43 +84,35 @@ class Mutation(ObjectType):
|
||||||
interface, Interface
|
interface, Interface
|
||||||
), f'All interfaces of {cls.__name__} must be a subclass of Interface. Received "{interface}".'
|
), f'All interfaces of {cls.__name__} must be a subclass of Interface. Received "{interface}".'
|
||||||
fields.update(interface._meta.fields)
|
fields.update(interface._meta.fields)
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
# If output is defined, we don't need to get the fields
|
# If output is defined, we don't need to get the fields
|
||||||
fields = {}
|
fields = {}
|
||||||
for base in reversed(cls.__mro__):
|
for base in reversed(cls.__mro__):
|
||||||
fields.update(yank_fields_from_attrs(base.__dict__, _as=Field))
|
fields.update(yank_fields_from_attrs(base.__dict__, _as=Field))
|
||||||
output = cls
|
output = cls
|
||||||
|
|
||||||
if not arguments:
|
if not arguments:
|
||||||
input_class = getattr(cls, "Arguments", None)
|
input_class = getattr(cls, "Arguments", None)
|
||||||
if not input_class:
|
if not input_class:
|
||||||
input_class = getattr(cls, "Input", None)
|
input_class = getattr(cls, "Input", None)
|
||||||
if input_class:
|
if input_class:
|
||||||
warn_deprecation(
|
warn_deprecation(
|
||||||
(
|
f"Please use {cls.__name__}.Arguments instead of {cls.__name__}.Input."
|
||||||
f"Please use {cls.__name__}.Arguments instead of {cls.__name__}.Input."
|
" Input is now only used in ClientMutationID.\n"
|
||||||
" Input is now only used in ClientMutationID.\n"
|
"Read more:"
|
||||||
"Read more:"
|
" https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#mutation-input"
|
||||||
" https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#mutation-input"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if input_class:
|
if input_class:
|
||||||
arguments = props(input_class)
|
arguments = props(input_class)
|
||||||
else:
|
else:
|
||||||
arguments = {}
|
arguments = {}
|
||||||
|
|
||||||
if not resolver:
|
if not resolver:
|
||||||
mutate = getattr(cls, "mutate", None)
|
mutate = getattr(cls, "mutate", None)
|
||||||
assert mutate, "All mutations must define a mutate method in it"
|
assert mutate, "All mutations must define a mutate method in it"
|
||||||
resolver = get_unbound_function(mutate)
|
resolver = get_unbound_function(mutate)
|
||||||
|
|
||||||
if _meta.fields:
|
if _meta.fields:
|
||||||
_meta.fields.update(fields)
|
_meta.fields.update(fields)
|
||||||
else:
|
else:
|
||||||
_meta.fields = fields
|
_meta.fields = fields
|
||||||
|
|
||||||
_meta.interfaces = interfaces
|
_meta.interfaces = interfaces
|
||||||
_meta.output = output
|
_meta.output = output
|
||||||
_meta.resolver = resolver
|
_meta.resolver = resolver
|
||||||
|
@ -133,7 +124,7 @@ class Mutation(ObjectType):
|
||||||
def Field(
|
def Field(
|
||||||
cls, name=None, description=None, deprecation_reason=None, required=False
|
cls, name=None, description=None, deprecation_reason=None, required=False
|
||||||
):
|
):
|
||||||
""" Mount instance of mutation Field. """
|
"""Mount instance of mutation Field."""
|
||||||
return Field(
|
return Field(
|
||||||
cls._meta.output,
|
cls._meta.output,
|
||||||
args=cls._meta.arguments,
|
args=cls._meta.arguments,
|
||||||
|
|
|
@ -7,7 +7,6 @@ try:
|
||||||
from dataclasses import make_dataclass, field
|
from dataclasses import make_dataclass, field
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from ..pyutils.dataclasses import make_dataclass, field # type: ignore
|
from ..pyutils.dataclasses import make_dataclass, field # type: ignore
|
||||||
|
|
||||||
# For static type checking with Mypy
|
# For static type checking with Mypy
|
||||||
MYPY = False
|
MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
|
@ -28,7 +27,7 @@ class ObjectTypeMeta(BaseTypeMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
base_cls = super().__new__(
|
base_cls = super().__new__(
|
||||||
cls, name_, (InterObjectType,) + bases, namespace, **options,
|
cls, name_, (InterObjectType,) + bases, namespace, **options
|
||||||
)
|
)
|
||||||
if base_cls._meta:
|
if base_cls._meta:
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -133,7 +132,6 @@ class ObjectType(BaseType, metaclass=ObjectTypeMeta):
|
||||||
):
|
):
|
||||||
if not _meta:
|
if not _meta:
|
||||||
_meta = ObjectTypeOptions(cls)
|
_meta = ObjectTypeOptions(cls)
|
||||||
|
|
||||||
fields = {}
|
fields = {}
|
||||||
|
|
||||||
for interface in interfaces:
|
for interface in interfaces:
|
||||||
|
@ -141,10 +139,8 @@ class ObjectType(BaseType, metaclass=ObjectTypeMeta):
|
||||||
interface, Interface
|
interface, Interface
|
||||||
), f'All interfaces of {cls.__name__} must be a subclass of Interface. Received "{interface}".'
|
), f'All interfaces of {cls.__name__} must be a subclass of Interface. Received "{interface}".'
|
||||||
fields.update(interface._meta.fields)
|
fields.update(interface._meta.fields)
|
||||||
|
|
||||||
for base in reversed(cls.__mro__):
|
for base in reversed(cls.__mro__):
|
||||||
fields.update(yank_fields_from_attrs(base.__dict__, _as=Field))
|
fields.update(yank_fields_from_attrs(base.__dict__, _as=Field))
|
||||||
|
|
||||||
assert not (possible_types and cls.is_type_of), (
|
assert not (possible_types and cls.is_type_of), (
|
||||||
f"{cls.__name__}.Meta.possible_types will cause type collision with {cls.__name__}.is_type_of. "
|
f"{cls.__name__}.Meta.possible_types will cause type collision with {cls.__name__}.is_type_of. "
|
||||||
"Please use one or other."
|
"Please use one or other."
|
||||||
|
@ -154,7 +150,6 @@ class ObjectType(BaseType, metaclass=ObjectTypeMeta):
|
||||||
_meta.fields.update(fields)
|
_meta.fields.update(fields)
|
||||||
else:
|
else:
|
||||||
_meta.fields = fields
|
_meta.fields = fields
|
||||||
|
|
||||||
if not _meta.interfaces:
|
if not _meta.interfaces:
|
||||||
_meta.interfaces = interfaces
|
_meta.interfaces = interfaces
|
||||||
_meta.possible_types = possible_types
|
_meta.possible_types = possible_types
|
||||||
|
|
|
@ -27,8 +27,6 @@ from graphql import (
|
||||||
GraphQLSchema,
|
GraphQLSchema,
|
||||||
GraphQLString,
|
GraphQLString,
|
||||||
)
|
)
|
||||||
from graphql.execution import ExecutionContext
|
|
||||||
from graphql.execution.values import get_argument_values
|
|
||||||
|
|
||||||
from ..utils.str_converters import to_camel_case
|
from ..utils.str_converters import to_camel_case
|
||||||
from ..utils.get_unbound_function import get_unbound_function
|
from ..utils.get_unbound_function import get_unbound_function
|
||||||
|
|
|
@ -64,15 +64,11 @@ def test_base64_query_none():
|
||||||
|
|
||||||
|
|
||||||
def test_base64_query_invalid():
|
def test_base64_query_invalid():
|
||||||
bad_inputs = [
|
bad_inputs = [dict(), 123, "This is not valid base64"]
|
||||||
dict(),
|
|
||||||
123,
|
|
||||||
"This is not valid base64",
|
|
||||||
]
|
|
||||||
|
|
||||||
for input_ in bad_inputs:
|
for input_ in bad_inputs:
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""{ base64(input: $input) }""", variables={"input": input_},
|
"""{ base64(input: $input) }""", variables={"input": input_}
|
||||||
)
|
)
|
||||||
assert isinstance(result.errors, list)
|
assert isinstance(result.errors, list)
|
||||||
assert len(result.errors) == 1
|
assert len(result.errors) == 1
|
||||||
|
|
|
@ -26,8 +26,8 @@ def test_enum_construction():
|
||||||
assert RGB._meta.description == "Description"
|
assert RGB._meta.description == "Description"
|
||||||
|
|
||||||
values = RGB._meta.enum.__members__.values()
|
values = RGB._meta.enum.__members__.values()
|
||||||
assert sorted([v.name for v in values]) == ["BLUE", "GREEN", "RED"]
|
assert sorted(v.name for v in values) == ["BLUE", "GREEN", "RED"]
|
||||||
assert sorted([v.description for v in values]) == [
|
assert sorted(v.description for v in values) == [
|
||||||
"Description BLUE",
|
"Description BLUE",
|
||||||
"Description GREEN",
|
"Description GREEN",
|
||||||
"Description RED",
|
"Description RED",
|
||||||
|
@ -52,7 +52,7 @@ def test_enum_instance_construction():
|
||||||
RGB = Enum("RGB", "RED,GREEN,BLUE")
|
RGB = Enum("RGB", "RED,GREEN,BLUE")
|
||||||
|
|
||||||
values = RGB._meta.enum.__members__.values()
|
values = RGB._meta.enum.__members__.values()
|
||||||
assert sorted([v.name for v in values]) == ["BLUE", "GREEN", "RED"]
|
assert sorted(v.name for v in values) == ["BLUE", "GREEN", "RED"]
|
||||||
|
|
||||||
|
|
||||||
def test_enum_from_builtin_enum():
|
def test_enum_from_builtin_enum():
|
||||||
|
@ -465,7 +465,7 @@ def test_mutation_enum_input_type():
|
||||||
color
|
color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"createPaint": {"color": "RED"}}
|
assert result.data == {"createPaint": {"color": "RED"}}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from graphql.type import GraphQLObjectType, GraphQLSchema
|
from graphql.type import GraphQLObjectType, GraphQLSchema
|
||||||
from graphql import GraphQLError
|
from pytest import raises
|
||||||
from pytest import mark, raises, fixture
|
|
||||||
|
|
||||||
from graphene.tests.utils import dedent
|
from graphene.tests.utils import dedent
|
||||||
|
|
||||||
|
|
|
@ -36,4 +36,4 @@ class OrderedType:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((self.creation_counter))
|
return hash(self.creation_counter)
|
||||||
|
|
|
@ -2,7 +2,4 @@ from .depth_limit import depth_limit_validator
|
||||||
from .disable_introspection import DisableIntrospection
|
from .disable_introspection import DisableIntrospection
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["DisableIntrospection", "depth_limit_validator"]
|
||||||
"DisableIntrospection",
|
|
||||||
"depth_limit_validator"
|
|
||||||
]
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# backwards compatibility for v3.6
|
# backwards compatibility for v3.6
|
||||||
from typing import Pattern
|
from typing import Pattern
|
||||||
|
|
||||||
from typing import Callable, Dict, List, Optional, Union
|
from typing import Callable, Dict, List, Optional, Union
|
||||||
|
|
||||||
from graphql import GraphQLError
|
from graphql import GraphQLError
|
||||||
|
@ -75,7 +74,6 @@ def depth_limit_validator(
|
||||||
operation_name=name,
|
operation_name=name,
|
||||||
ignore=ignore,
|
ignore=ignore,
|
||||||
)
|
)
|
||||||
|
|
||||||
if callable(callback):
|
if callable(callback):
|
||||||
callback(query_depths)
|
callback(query_depths)
|
||||||
super().__init__(validation_context)
|
super().__init__(validation_context)
|
||||||
|
@ -90,7 +88,6 @@ def get_fragments(
|
||||||
for definition in definitions:
|
for definition in definitions:
|
||||||
if isinstance(definition, FragmentDefinitionNode):
|
if isinstance(definition, FragmentDefinitionNode):
|
||||||
fragments[definition.name.value] = definition
|
fragments[definition.name.value] = definition
|
||||||
|
|
||||||
return fragments
|
return fragments
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,7 +102,6 @@ def get_queries_and_mutations(
|
||||||
if isinstance(definition, OperationDefinitionNode):
|
if isinstance(definition, OperationDefinitionNode):
|
||||||
operation = definition.name.value if definition.name else "anonymous"
|
operation = definition.name.value if definition.name else "anonymous"
|
||||||
operations[operation] = definition
|
operations[operation] = definition
|
||||||
|
|
||||||
return operations
|
return operations
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,7 +122,6 @@ def determine_depth(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return depth_so_far
|
return depth_so_far
|
||||||
|
|
||||||
if isinstance(node, FieldNode):
|
if isinstance(node, FieldNode):
|
||||||
should_ignore = is_introspection_key(node.name.value) or is_ignored(
|
should_ignore = is_introspection_key(node.name.value) or is_ignored(
|
||||||
node, ignore
|
node, ignore
|
||||||
|
@ -134,7 +129,6 @@ def determine_depth(
|
||||||
|
|
||||||
if should_ignore or not node.selection_set:
|
if should_ignore or not node.selection_set:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return 1 + max(
|
return 1 + max(
|
||||||
map(
|
map(
|
||||||
lambda selection: determine_depth(
|
lambda selection: determine_depth(
|
||||||
|
@ -177,13 +171,14 @@ def determine_depth(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Depth crawler cannot handle: {node.kind}.") # pragma: no cover
|
raise Exception(
|
||||||
|
f"Depth crawler cannot handle: {node.kind}."
|
||||||
|
) # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
def is_ignored(node: FieldNode, ignore: Optional[List[IgnoreType]] = None) -> bool:
|
def is_ignored(node: FieldNode, ignore: Optional[List[IgnoreType]] = None) -> bool:
|
||||||
if ignore is None:
|
if ignore is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for rule in ignore:
|
for rule in ignore:
|
||||||
field_name = node.name.value
|
field_name = node.name.value
|
||||||
if isinstance(rule, str):
|
if isinstance(rule, str):
|
||||||
|
@ -197,5 +192,4 @@ def is_ignored(node: FieldNode, ignore: Optional[List[IgnoreType]] = None) -> bo
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid ignore option: {rule}.")
|
raise ValueError(f"Invalid ignore option: {rule}.")
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -11,7 +11,6 @@ class DisableIntrospection(ValidationRule):
|
||||||
if is_introspection_key(field_name):
|
if is_introspection_key(field_name):
|
||||||
self.report_error(
|
self.report_error(
|
||||||
GraphQLError(
|
GraphQLError(
|
||||||
f"Cannot query '{field_name}': introspection is disabled.",
|
f"Cannot query '{field_name}': introspection is disabled.", node
|
||||||
node,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -48,26 +48,11 @@ class HumanType(ObjectType):
|
||||||
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
user = Field(
|
user = Field(HumanType, required=True, name=String())
|
||||||
HumanType,
|
version = String(required=True)
|
||||||
required=True,
|
user1 = Field(HumanType, required=True)
|
||||||
name=String()
|
user2 = Field(HumanType, required=True)
|
||||||
)
|
user3 = Field(HumanType, required=True)
|
||||||
version = String(
|
|
||||||
required=True
|
|
||||||
)
|
|
||||||
user1 = Field(
|
|
||||||
HumanType,
|
|
||||||
required=True
|
|
||||||
)
|
|
||||||
user2 = Field(
|
|
||||||
HumanType,
|
|
||||||
required=True
|
|
||||||
)
|
|
||||||
user3 = Field(
|
|
||||||
HumanType,
|
|
||||||
required=True
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_user(root, info, name=None):
|
def resolve_user(root, info, name=None):
|
||||||
|
@ -91,9 +76,7 @@ def run_query(query: str, max_depth: int, ignore=None):
|
||||||
document_ast=document,
|
document_ast=document,
|
||||||
rules=(
|
rules=(
|
||||||
depth_limit_validator(
|
depth_limit_validator(
|
||||||
max_depth=max_depth,
|
max_depth=max_depth, ignore=ignore, callback=callback
|
||||||
ignore=ignore,
|
|
||||||
callback=callback
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -253,11 +236,7 @@ def test_should_ignore_field():
|
||||||
errors, result = run_query(
|
errors, result = run_query(
|
||||||
query,
|
query,
|
||||||
10,
|
10,
|
||||||
ignore=[
|
ignore=["user1", re.compile("user2"), lambda field_name: field_name == "user3"],
|
||||||
"user1",
|
|
||||||
re.compile("user2"),
|
|
||||||
lambda field_name: field_name == "user3",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = {"read1": 2, "read2": 0}
|
expected = {"read1": 2, "read2": 0}
|
||||||
|
@ -272,8 +251,4 @@ def test_should_raise_invalid_ignore():
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
with raises(ValueError, match="Invalid ignore option:"):
|
with raises(ValueError, match="Invalid ignore option:"):
|
||||||
run_query(
|
run_query(query, 10, ignore=[True])
|
||||||
query,
|
|
||||||
10,
|
|
||||||
ignore=[True],
|
|
||||||
)
|
|
||||||
|
|
|
@ -5,9 +5,7 @@ from ..disable_introspection import DisableIntrospection
|
||||||
|
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
name = String(
|
name = String(required=True)
|
||||||
required=True
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_name(root, info):
|
def resolve_name(root, info):
|
||||||
|
@ -23,9 +21,7 @@ def run_query(query: str):
|
||||||
errors = validate(
|
errors = validate(
|
||||||
schema=schema.graphql_schema,
|
schema=schema.graphql_schema,
|
||||||
document_ast=document,
|
document_ast=document,
|
||||||
rules=(
|
rules=(DisableIntrospection,),
|
||||||
DisableIntrospection,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
10
tox.ini
10
tox.ini
|
@ -11,25 +11,25 @@ commands =
|
||||||
py{36,37,38}: pytest --cov=graphene graphene examples {posargs}
|
py{36,37,38}: pytest --cov=graphene graphene examples {posargs}
|
||||||
|
|
||||||
[testenv:pre-commit]
|
[testenv:pre-commit]
|
||||||
basepython=python3.7
|
basepython=python3.8
|
||||||
deps =
|
deps =
|
||||||
pre-commit>=2,<3
|
pre-commit>=2,<3
|
||||||
setenv =
|
setenv =
|
||||||
LC_CTYPE=en_US.UTF-8
|
LC_CTYPE=en_US.UTF-8
|
||||||
commands =
|
commands =
|
||||||
pre-commit {posargs:run --all-files}
|
pre-commit run --all-files --show-diff-on-failure
|
||||||
|
|
||||||
[testenv:mypy]
|
[testenv:mypy]
|
||||||
basepython=python3.7
|
basepython=python3.8
|
||||||
deps =
|
deps =
|
||||||
mypy>=0.761,<1
|
mypy>=0.761,<1
|
||||||
commands =
|
commands =
|
||||||
mypy graphene
|
mypy graphene
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
basepython=python3.7
|
basepython=python3.8
|
||||||
deps =
|
deps =
|
||||||
flake8>=3.7,<4
|
flake8>=3.8,<4
|
||||||
commands =
|
commands =
|
||||||
pip install --pre -e .
|
pip install --pre -e .
|
||||||
flake8 graphene
|
flake8 graphene
|
||||||
|
|
Loading…
Reference in New Issue
Block a user