diff --git a/.github/workflows/on_update.yml b/.github/workflows/on_update.yml index 79e378be..953b9a1b 100644 --- a/.github/workflows/on_update.yml +++ b/.github/workflows/on_update.yml @@ -12,7 +12,7 @@ permissions: jobs: lint: - name: Lint + name: Lint and Format runs-on: ${{ matrix.platform }} strategy: fail-fast: false @@ -32,12 +32,9 @@ jobs: - name: Install dependencies run: | python -m pip install -r requirements/base.txt -r requirements/style.txt - - name: Run flake8 + - name: Run ruff run: | - flake8 src/ tests/ - - name: Run pylint - run: | - pylint src/ tests/ + ruff check . test: name: Test diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6346de9c..d7a54362 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR: ${{ github.event.issue.pull_request.html_url }} run: | - gh pr comment $PR --body 'The "Tests [Docker]" workflow will be started.' + gh pr comment $PR --body 'Running "Tests [Docker]" workflow.' unit-tests: name: Unit tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7caa9adc..e9ebff96 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,8 @@ default_stages: [commit] fail_fast: true default_language_version: python: python3.12 +ci: + autoupdate_schedule: monthly repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 @@ -16,39 +18,9 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + - id: debug-statements +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.14 hooks: - - id: pyupgrade - args: ['--py38-plus'] -- repo: https://github.com/psf/black - rev: 23.12.1 - hooks: - - id: black - args: ['--safe'] -- repo: https://github.com/asottile/blacken-docs - rev: 1.16.0 - hooks: - - id: blacken-docs - additional_dependencies: - - black==23.12.1 -- repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort -- repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-isort==6.1.1 - - flake8-bugbear==24.1.17 - - flake8-pyi==24.1.0 - - flake8-quotes==3.3.2 - - flake8-implicit-str-concat==0.4.0 - - flake8-pyproject==1.2.3 - args: ['--toml-config=pyproject.toml'] -- repo: https://github.com/PyCQA/pylint - rev: v3.0.3 - hooks: - - id: pylint + - id: ruff + - id: ruff-format diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 280c02d4..00000000 --- a/.pylintrc +++ /dev/null @@ -1,355 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=third_party - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns=object_detection_grpc_client.py,prediction_pb2.py,prediction_pb2_grpc.py,mnist_DDP.py,mnistddpserving.py - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=useless-suppression,suppressed-message,missing-docstring,invalid-name,no-member,locally-disabled,fixme,import-error,too-many-locals,abstract-method,duplicate-code - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=120 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -# Use 2 spaces consistent with TensorFlow style. -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=100 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=7 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=25 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=0 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.Exception diff --git a/docker-compose.test.yml b/docker-compose.test.yml index c6a85d08..367fdf23 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -8,8 +8,7 @@ services: environment: - PRAGMA_VERSION=py3.12 command: > - sh -c "flake8 src/ tests/ && - pylint src/ tests/ && + sh -c "ruff check . && pytest" python3.11: @@ -19,8 +18,7 @@ services: environment: - PRAGMA_VERSION=py3.11 command: > - sh -c "flake8 src/ tests/ && - pylint src/ tests/ && + sh -c "ruff check . && pytest" python3.10: @@ -30,8 +28,7 @@ services: environment: - PRAGMA_VERSION=py3.10 command: > - sh -c "flake8 src/ tests/ && - pylint src/ tests/ && + sh -c "ruff check . && pytest" python3.9: @@ -41,8 +38,7 @@ services: environment: - PRAGMA_VERSION=py3.9 command: > - sh -c "flake8 src/ tests/ && - pylint src/ tests/ && + sh -c "ruff check . && pytest" python3.8: @@ -52,6 +48,5 @@ services: environment: - PRAGMA_VERSION=py3.8 command: > - sh -c "flake8 src/ tests/ && - pylint src/ tests/ && + sh -c "ruff check . && pytest" diff --git a/examples/auth/auth.py b/examples/auth/auth.py index a4e386e2..0343cedd 100755 --- a/examples/auth/auth.py +++ b/examples/auth/auth.py @@ -28,9 +28,13 @@ # isort:skip_file import os import sys +import typing as t from flask import Flask, request +if t.TYPE_CHECKING: + from flask.typing import ResponseReturnValue + try: from flask_jsonrpc import JSONRPC, JSONRPCView except ModuleNotFoundError: @@ -47,12 +51,12 @@ class UnauthorizedError(Exception): class AuthorizationView(JSONRPCView): - def check_auth(self) -> bool: + def check_auth(self: t.Self) -> bool: username = request.headers.get('X-Username') password = request.headers.get('X-Password') return username == 'username' and password == 'secret' - def dispatch_request(self): + def dispatch_request(self: t.Self) -> 'ResponseReturnValue': if not self.check_auth(): raise UnauthorizedError() return super().dispatch_request() diff --git a/examples/cerberus_params_validator/example1.py b/examples/cerberus_params_validator/example1.py index 34475a91..141c9ce6 100755 --- a/examples/cerberus_params_validator/example1.py +++ b/examples/cerberus_params_validator/example1.py @@ -51,18 +51,11 @@ v.schema = { 'user': { 'type': 'dict', - 'schema': { - 'name': {'type': 'string', 'maxlength': 10}, - 'age': {'type': 'integer', 'min': 10}, - }, + 'schema': {'name': {'type': 'string', 'maxlength': 10}, 'age': {'type': 'integer', 'min': 10}}, }, 'vehicle': { 'type': 'dict', - 'schema': { - 'make': {'type': 'string'}, - 'model': {'type': 'string'}, - 'year': {'type': 'integer', 'min': 1900}, - }, + 'schema': {'make': {'type': 'string'}, 'model': {'type': 'string'}, 'year': {'type': 'integer', 'min': 1900}}, }, } diff --git a/examples/cerberus_params_validator/example2.py b/examples/cerberus_params_validator/example2.py index 25653b94..06e38869 100755 --- a/examples/cerberus_params_validator/example2.py +++ b/examples/cerberus_params_validator/example2.py @@ -28,7 +28,7 @@ # isort:skip_file import os import sys -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Self from dataclasses import dataclass from flask import Flask @@ -59,36 +59,26 @@ class DataclassValidatorMixin: default_schema = None def validate( - self, - obj: Any, + self: Self, + obj: Any, # noqa: ANN401 schema: Optional[Dict[str, Any]] = None, update: bool = False, normalize: bool = True, ) -> bool: return super().validate( - obj.__dict__, - schema=self.default_schema if schema is None else schema, - update=update, - normalize=normalize, + obj.__dict__, schema=self.default_schema if schema is None else schema, update=update, normalize=normalize ) class UserValidator(DataclassValidatorMixin, Validator): - default_schema = { - 'name': {'type': 'string', 'maxlength': 10}, - 'age': {'type': 'integer', 'min': 10}, - } + default_schema = {'name': {'type': 'string', 'maxlength': 10}, 'age': {'type': 'integer', 'min': 10}} v = Validator() v.schema = { 'vehicle': { 'type': 'dict', - 'schema': { - 'make': {'type': 'string'}, - 'model': {'type': 'string'}, - 'year': {'type': 'integer', 'min': 1900}, - }, + 'schema': {'make': {'type': 'string'}, 'model': {'type': 'string'}, 'year': {'type': 'integer', 'min': 1900}}, } } diff --git a/examples/cerberus_params_validator/example3.py b/examples/cerberus_params_validator/example3.py index 5e2749fc..81049a0c 100755 --- a/examples/cerberus_params_validator/example3.py +++ b/examples/cerberus_params_validator/example3.py @@ -28,7 +28,7 @@ # isort:skip_file import os import sys -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Dict, Optional, Self from flask import Flask from cerberus import Validator @@ -52,23 +52,20 @@ class SchemaValidatorMixin: default_schema = None def validate( - self, - obj: Any, + self: Self, + obj: Any, # noqa: ANN401 schema: Optional[Dict[str, Any]] = None, update: bool = False, normalize: bool = True, ) -> bool: return super().validate( - obj, - schema=self.default_schema if schema is None else schema, - update=update, - normalize=normalize, + obj, schema=self.default_schema if schema is None else schema, update=update, normalize=normalize ) def cerberus_validator(validator: Validator) -> Callable[..., Any]: def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: - def wrapped(*args: Any, **kwargs: Any) -> Any: + def wrapped(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 if not validator.validate(kwargs): raise InvalidParamsError(data={'message': validator.errors}) return fn(*args, **kwargs) @@ -82,10 +79,7 @@ class UserValidator(SchemaValidatorMixin, Validator): default_schema = { 'user': { 'type': 'dict', - 'schema': { - 'name': {'type': 'string', 'maxlength': 10}, - 'age': {'type': 'integer', 'min': 10}, - }, + 'schema': {'name': {'type': 'string', 'maxlength': 10}, 'age': {'type': 'integer', 'min': 10}}, } } @@ -94,11 +88,7 @@ class UserValidator(SchemaValidatorMixin, Validator): v.schema = { 'vehicle': { 'type': 'dict', - 'schema': { - 'make': {'type': 'string'}, - 'model': {'type': 'string'}, - 'year': {'type': 'integer', 'min': 1900}, - }, + 'schema': {'make': {'type': 'string'}, 'model': {'type': 'string'}, 'year': {'type': 'integer', 'min': 1900}}, } } diff --git a/examples/decorator/decorator.py b/examples/decorator/decorator.py index 0b3f55dd..a6ef54f2 100755 --- a/examples/decorator/decorator.py +++ b/examples/decorator/decorator.py @@ -46,8 +46,8 @@ jsonrpc = JSONRPC(app, '/api') -def check_terminal_id(fn: Callable[..., Any]): - def wrapped(*args: Any, **kwargs: Any) -> Any: +def check_terminal_id(fn: Callable[..., Any]) -> Any: # noqa: ANN401 + def wrapped(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 terminal_id = int(request.get_json(silent=True).get('terminal_id', 0)) if terminal_id <= 0: raise ValueError('Invalid terminal ID') @@ -57,8 +57,8 @@ def wrapped(*args: Any, **kwargs: Any) -> Any: return wrapped -def jsonrpc_headers(fn: Callable[..., Any]): - def wrapped(*args: Any, **kwargs: Any) -> Any: +def jsonrpc_headers(fn: Callable[..., Any]) -> Any: # noqa: ANN401 + def wrapped(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 headers = {'X-JSONRPC-Tag': 'JSONRPC 2.0'} rv = fn(*args, **kwargs) return rv, 200, headers @@ -77,10 +77,7 @@ def index() -> str: @check_terminal_id @jsonrpc_headers def decorators() -> dict: - return { - 'terminal_id': request.get_json(silent=True).get('terminal_id', 0), - 'headers': str(request.headers), - } + return {'terminal_id': request.get_json(silent=True).get('terminal_id', 0), 'headers': str(request.headers)} if __name__ == '__main__': diff --git a/examples/hrx/hrx.py b/examples/hrx/hrx.py index 6a621e50..5bbc891b 100755 --- a/examples/hrx/hrx.py +++ b/examples/hrx/hrx.py @@ -50,7 +50,7 @@ @app.route('/') -def index(): +def index() -> str: return render_template('index.html') diff --git a/examples/register_view_func/run.py b/examples/register_view_func/run.py index e15f97ca..57b6a253 100644 --- a/examples/register_view_func/run.py +++ b/examples/register_view_func/run.py @@ -30,6 +30,7 @@ import sys from typing import Any, Dict, List, Union, NoReturn, Optional from numbers import Real +import typing as t from flask import Flask @@ -45,26 +46,26 @@ class MyApp: - def index(self) -> str: + def index(self: t.Self) -> str: return 'Welcome to Flask JSON-RPC' - def greeting(self, name: str) -> str: + def greeting(self: t.Self, name: str) -> str: return f'Hello {name}' - def args_validate(self, a1: int, a2: str, a3: bool, a4: List[Any], a5: Dict[Any, Any]) -> str: + def args_validate(self: t.Self, a1: int, a2: str, a3: bool, a4: List[Any], a5: Dict[Any, Any]) -> str: return f'Number: {a1}, String: {a2}, Boolean: {a3}, Array: {a4}, Object: {a5}' - def notify(self, _string: Optional[str] = None) -> None: + def notify(self: t.Self, _string: Optional[str] = None) -> None: pass - def fails(self, _string: Optional[str] = None) -> NoReturn: + def fails(self: t.Self, _string: Optional[str] = None) -> NoReturn: raise ValueError('example of fail') - def sum_(self, a: Real, b: Real) -> Real: + def sum_(self: t.Self, a: Real, b: Real) -> Real: return a + b @classmethod - def multiply(cls, a: float, b: float) -> float: + def multiply(cls: t.Type[t.Self], a: float, b: float) -> float: return a * b @staticmethod diff --git a/pyproject.toml b/pyproject.toml index 180c5133..fa5fe372 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,10 +30,10 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "Flask>=3.0.0,<4.0", - "typeguard==2.13.3", - "typing_extensions>=4.3.0", - "typing_inspect==0.9.0", + "Flask>=3.0.0,<4.0", + "typeguard==2.13.3", + "typing_extensions>=4.3.0", + "typing_inspect==0.9.0", ] [project.optional-dependencies] @@ -47,55 +47,66 @@ Documentation = "https://flask-jsonrpc.readthedocs.io/" "Issue Tracker" = "https://github.com/cenobites/flask-jsonrpc/issues/" Website = "https://flask-jsonrpc.readthedocs.io/" -[tool.black] +[tool.ruff] +src = ["src"] +fix = true +show-fixes = true +show-source = true line-length = 120 -skip-string-normalization = true -target-version = ["py312"] -include = ".pyi?$" -exclude = """ -( - /( - .eggs - | .git - | .hg - | .mypy_cache - | .pytype - | .pytest_cache - | .tox - | .venv - | _build - | buck-out - | build - | dist - | htmlcov - | junit - | htmldoc - )/ - | settings.py -) -""" - -[tool.isort] -profile = "black" -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -length_sort = true -line_length = 120 -known_flask = "flask" -sections = "FUTURE,STDLIB,FLASK,THIRDPARTY,FIRSTPARTY,LOCALFOLDER" - -[tool.flake8] -select = "B,E,F,W,B9,Y0,ISC,I0" -ignore = "D203,E302,F401,F403,F405,W503" -exclude = ".git,.tox,.docker,.github,.venv,__pycache__,docs,old,build,dist,htmlcov,junit,.pytest_cache,htmldoc" +target-version = "py38" + +[tool.ruff.lint] +select = [ + "E", # pycodestyle error + "W", # pycodestyle warning + "F", # pyflakes + "UP", # pyupgrade + "ANN", # flake8-annotations + "B", # flake8-bugbear + "Q", # flake8-quotes + "SIM", # flake8-simplify + "T", # flake8-type-checking + "B", # flake8-bandit + "C", # flake8-copyright + "I", # isort +] + +[tool.ruff.lint.mccabe] max-complexity = 10 -max-line-length = 120 -[tool.pycodestyle] -max-line-length = 120 -exclude = ".git,.tox,.docker,.github,.venv,__pycache__,docs,old,build,dist,htmlcov,junit,.pytest_cache,htmldoc" +[tool.ruff.lint.flake8-quotes] +inline-quotes = "single" +docstring-quotes = "double" + +[tool.ruff.lint.flake8-type-checking] +exempt-modules = ["typing", "typing_extensions"] + +[tool.ruff.lint.flake8-bandit] +check-typed-exception = true + +[tool.ruff.lint.flake8-copyright] +author = "Cenobit Technologies, Inc. http://cenobit.es/" + +[tool.ruff.lint.isort] +length-sort = true +combine-as-imports = true +order-by-type = true +force-sort-within-sections = true +split-on-trailing-comma = false +section-order = ["future", "standard-library", "flask", "third-party", "first-party", "local-folder"] + +[tool.ruff.lint.isort.sections] +"flask" = ["flask"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.format] +quote-style = "single" +indent-style = "space" +skip-magic-trailing-comma = true +docstring-code-format = true +docstring-code-line-length = 79 [tool.pytest.ini_options] addopts = "--pyargs --doctest-modules --junitxml=junit/test-results.xml --cov-report=html --cov-report=term --cov-report=lcov --cov=flask_jsonrpc --cov-fail-under=100" @@ -165,13 +176,7 @@ ignore_errors = false ignore_missing_imports = false strict = true -[[tool.mypy.overrides]] -module = [ - "dotenv.*", - "importlib_metadata", -] -ignore_missing_imports = true - [tool.pytype] inputs = ["src/flask_jsonrpc"] python_version = "3.11" +disable = ["invalid-annotation"] diff --git a/requirements/style.txt b/requirements/style.txt index ee126366..8080abd5 100644 --- a/requirements/style.txt +++ b/requirements/style.txt @@ -1,19 +1,3 @@ -# Code formatter +# Code formatter + Code quality + Code linter # ------------------------------------------------------------------------------ -black==23.12.1 # https://github.com/psf/black -pyupgrade==3.15.0 # https://github.com/asottile/pyupgrade - -# Code quality -# ------------------------------------------------------------------------------ -flake8==7.0.0 # https://github.com/PyCQA/flake8 -flake8-isort==6.1.1 # https://github.com/gforcada/flake8-isort -isort>=4.2.5,<6 # https://github.com/PyCQA/isort -flake8-bugbear==24.1.17 # https://github.com/PyCQA/flake8-bugbear -flake8-pyi==24.1.0 # https://github.com/ambv/flake8-pyi -flake8-quotes==3.3.2 # https://github.com/zheller/flake8-quotes -flake8-implicit-str-concat==0.4.0 # https://github.com/keisheiled/flake8-implicit-str-concat -flake8-pyproject==1.2.3 # https://github.com/john-hen/Flake8-pyproject - -# Code linter -# ------------------------------------------------------------------------------ -pylint==3.0.3 # https://github.com/PyCQA/pylint +ruff==0.1.14 # https://github.com/astral-sh/ruff diff --git a/src/flask_jsonrpc/__init__.py b/src/flask_jsonrpc/__init__.py index 15c1922a..1caecd8d 100644 --- a/src/flask_jsonrpc/__init__.py +++ b/src/flask_jsonrpc/__init__.py @@ -26,6 +26,6 @@ # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations -from .app import JSONRPC -from .views import JSONRPCView -from .blueprints import JSONRPCBlueprint +from .app import JSONRPC # noqa: F401 +from .views import JSONRPCView # noqa: F401 +from .blueprints import JSONRPCBlueprint # noqa: F401 diff --git a/src/flask_jsonrpc/app.py b/src/flask_jsonrpc/app.py index ce1cee65..173abe5b 100644 --- a/src/flask_jsonrpc/app.py +++ b/src/flask_jsonrpc/app.py @@ -34,6 +34,12 @@ from .wrappers import JSONRPCDecoratorMixin from .contrib.browse import JSONRPCBrowse +# Python 3.10+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + if t.TYPE_CHECKING: from .site import JSONRPCSite from .views import JSONRPCView @@ -42,7 +48,7 @@ class JSONRPC(JSONRPCDecoratorMixin): def __init__( - self, + self: Self, app: t.Optional[Flask] = None, service_url: str = '/api', jsonrpc_site: t.Type['JSONRPCSite'] = default_jsonrpc_site, @@ -59,16 +65,16 @@ def __init__( if app: self.init_app(app) - def get_jsonrpc_site(self) -> 'JSONRPCSite': + def get_jsonrpc_site(self: Self) -> 'JSONRPCSite': return self.jsonrpc_site - def get_jsonrpc_site_api(self) -> t.Type['JSONRPCView']: + def get_jsonrpc_site_api(self: Self) -> t.Type['JSONRPCView']: return self.jsonrpc_site_api - def _make_jsonrpc_browse_url(self, path: str) -> str: + def _make_jsonrpc_browse_url(self: Self, path: str) -> str: return ''.join([path.rstrip('/'), '/browse']) - def init_app(self, app: Flask) -> None: + def init_app(self: Self, app: Flask) -> None: http_host = app.config.get('SERVER_NAME') app_root = app.config['APPLICATION_ROOT'] url_scheme = app.config['PREFERRED_URL_SCHEME'] @@ -93,15 +99,15 @@ def init_app(self, app: Flask) -> None: self.init_browse_app(app) def register( - self, + self: Self, view_func: t.Callable[..., t.Any], name: t.Optional[str] = None, - **options: t.Any, + **options: t.Any, # noqa: ANN401 ) -> None: self.register_view_function(view_func, name, **options) def register_blueprint( - self, + self: Self, app: Flask, jsonrpc_app: 'JSONRPCBlueprint', url_prefix: t.Optional[str] = None, @@ -119,20 +125,19 @@ def register_blueprint( app.add_url_rule( path, view_func=jsonrpc_app.get_jsonrpc_site_api().as_view( - urn('blueprint', app.name, jsonrpc_app.name, path), - jsonrpc_site=jsonrpc_app.get_jsonrpc_site(), + urn('blueprint', app.name, jsonrpc_app.name, path), jsonrpc_site=jsonrpc_app.get_jsonrpc_site() ), ) if app.config['DEBUG'] or enable_web_browsable_api: self.register_browse(jsonrpc_app) - def init_browse_app(self, app: Flask, path: t.Optional[str] = None, base_url: t.Optional[str] = None) -> None: + def init_browse_app(self: Self, app: Flask, path: t.Optional[str] = None, base_url: t.Optional[str] = None) -> None: browse_url = self._make_jsonrpc_browse_url(path or self.path) self.jsonrpc_browse = JSONRPCBrowse(app, url_prefix=browse_url, base_url=base_url or self.base_url) self.jsonrpc_browse.register_jsonrpc_site(self.get_jsonrpc_site()) - def register_browse(self, jsonrpc_app: t.Union['JSONRPC', 'JSONRPCBlueprint']) -> None: + def register_browse(self: Self, jsonrpc_app: t.Union['JSONRPC', 'JSONRPCBlueprint']) -> None: if not self.jsonrpc_browse: raise RuntimeError( 'You need to init the Browse app before register the Site, see JSONRPC.init_browse_app(...)' diff --git a/src/flask_jsonrpc/blueprints.py b/src/flask_jsonrpc/blueprints.py index 18ac6e56..17140dad 100644 --- a/src/flask_jsonrpc/blueprints.py +++ b/src/flask_jsonrpc/blueprints.py @@ -29,6 +29,12 @@ from .globals import default_jsonrpc_site, default_jsonrpc_site_api from .wrappers import JSONRPCDecoratorMixin +# Python 3.10+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + if t.TYPE_CHECKING: from .site import JSONRPCSite from .views import JSONRPCView @@ -36,7 +42,7 @@ class JSONRPCBlueprint(JSONRPCDecoratorMixin): def __init__( - self, + self: Self, name: str, import_name: str, jsonrpc_site: t.Type['JSONRPCSite'] = default_jsonrpc_site, @@ -47,8 +53,8 @@ def __init__( self.jsonrpc_site = jsonrpc_site() self.jsonrpc_site_api = jsonrpc_site_api - def get_jsonrpc_site(self) -> 'JSONRPCSite': + def get_jsonrpc_site(self: Self) -> 'JSONRPCSite': return self.jsonrpc_site - def get_jsonrpc_site_api(self) -> t.Type['JSONRPCView']: + def get_jsonrpc_site_api(self: Self) -> t.Type['JSONRPCView']: return self.jsonrpc_site_api diff --git a/src/flask_jsonrpc/contrib/browse/__init__.py b/src/flask_jsonrpc/contrib/browse/__init__.py index 5770ab43..1af302bf 100644 --- a/src/flask_jsonrpc/contrib/browse/__init__.py +++ b/src/flask_jsonrpc/contrib/browse/__init__.py @@ -31,9 +31,14 @@ from flask_jsonrpc.helpers import urn +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + if t.TYPE_CHECKING: - from flask import Flask - from flask import typing as ft + from flask import Flask, typing as ft from flask_jsonrpc.site import JSONRPCSite from flask_jsonrpc.typing import ServiceMethodDescribe @@ -41,10 +46,7 @@ class JSONRPCBrowse: def __init__( - self, - app: t.Optional['Flask'] = None, - url_prefix: str = '/api/browse', - base_url: t.Optional[str] = None, + self: Self, app: t.Optional['Flask'] = None, url_prefix: str = '/api/browse', base_url: t.Optional[str] = None ) -> None: self.app = app self.url_prefix = url_prefix @@ -53,10 +55,10 @@ def __init__( if app: self.init_app(app) - def _service_methods_desc(self) -> t.Dict[str, 'ServiceMethodDescribe']: + def _service_methods_desc(self: Self) -> t.Dict[str, 'ServiceMethodDescribe']: return dict(ChainMap(*[site.describe()['methods'] for site in self.jsonrpc_sites])) - def init_app(self, app: 'Flask') -> None: + def init_app(self: Self, app: 'Flask') -> None: name = urn('browse', app.name, self.url_prefix) browse = Blueprint(name, __name__, template_folder='templates', static_folder='static') browse.add_url_rule('/', view_func=self.vf_index) @@ -67,25 +69,21 @@ def init_app(self, app: 'Flask') -> None: app.register_blueprint(browse, url_prefix=self.url_prefix) app.add_url_rule( - f'{self.url_prefix}/static/', - 'urn:browse.static', - view_func=app.send_static_file, + f'{self.url_prefix}/static/', 'urn:browse.static', view_func=app.send_static_file ) - def register_jsonrpc_site(self, jsonrpc_site: 'JSONRPCSite') -> None: + def register_jsonrpc_site(self: Self, jsonrpc_site: 'JSONRPCSite') -> None: self.jsonrpc_sites.add(jsonrpc_site) - def vf_index(self) -> str: + def vf_index(self: Self) -> str: server_urls: t.Dict[str, str] = {} service_describes = [site.describe() for site in self.jsonrpc_sites] for service_describe in service_describes: - server_urls.update( - {name: service_describe['servers'][0]['url'] for name in service_describe['methods'].keys()} - ) + server_urls.update({name: service_describe['servers'][0]['url'] for name in service_describe['methods']}) url_prefix = f"{request.script_root}{request.path.rstrip('/')}" return render_template('browse/index.html', url_prefix=url_prefix, server_urls=server_urls) - def vf_json_packages(self) -> 'ft.ResponseReturnValue': + def vf_json_packages(self: Self) -> 'ft.ResponseReturnValue': service_methods = self._service_methods_desc() packages = sorted(service_methods.keys()) packages_tree: t.Dict[str, t.List[t.Dict[str, t.Any]]] = {} @@ -94,14 +92,14 @@ def vf_json_packages(self) -> 'ft.ResponseReturnValue': packages_tree.setdefault(package_name, []).append({'name': package, **service_methods[package]}) return jsonify(packages_tree) - def vf_json_method(self, method_name: str) -> 'ft.ResponseReturnValue': + def vf_json_method(self: Self, method_name: str) -> 'ft.ResponseReturnValue': service_procedures = self._service_methods_desc() if method_name not in service_procedures: return jsonify({'message': 'Not found'}), 404 return jsonify({'name': method_name, **service_procedures[method_name]}) - def vf_partials_dashboard(self) -> str: + def vf_partials_dashboard(self: Self) -> str: return render_template('browse/partials/dashboard.html') - def vf_partials_response_object(self) -> str: + def vf_partials_response_object(self: Self) -> str: return render_template('browse/partials/response_object.html') diff --git a/src/flask_jsonrpc/exceptions.py b/src/flask_jsonrpc/exceptions.py index 1bfe4ea4..b9e03fa0 100644 --- a/src/flask_jsonrpc/exceptions.py +++ b/src/flask_jsonrpc/exceptions.py @@ -30,6 +30,12 @@ from flask import current_app +# Python 3.10+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + try: from flaskext.babel import gettext as _ # type: ignore @@ -56,10 +62,10 @@ class JSONRPCError(Exception): status_code: int = 400 def __init__( - self, + self: Self, message: t.Optional[str] = None, code: t.Optional[int] = None, - data: t.Optional[t.Any] = None, + data: t.Optional[t.Any] = None, # noqa: ANN401 status_code: t.Optional[int] = None, ) -> None: """Setup the Exception and overwrite the default message""" @@ -74,15 +80,10 @@ def __init__( self.status_code = status_code @property - def jsonrpc_format(self) -> t.Dict[str, t.Any]: + def jsonrpc_format(self: Self) -> t.Dict[str, t.Any]: """Return the Exception data in a format for JSON-RPC""" - error = { - 'name': self.__class__.__name__, - 'code': self.code, - 'message': self.message, - 'data': self.data, - } + error = {'name': self.__class__.__name__, 'code': self.code, 'message': self.message, 'data': self.data} # RuntimeError: Working outside of application context. # This typically means that you attempted to use functionality that needed diff --git a/src/flask_jsonrpc/helpers.py b/src/flask_jsonrpc/helpers.py index 4448e38d..0d3ee6be 100644 --- a/src/flask_jsonrpc/helpers.py +++ b/src/flask_jsonrpc/helpers.py @@ -25,8 +25,8 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import typing as t -import itertools from operator import getitem +import itertools from .types import Types, Object @@ -34,7 +34,7 @@ from .types import JSONRPCNewType -def urn(name: str, *args: t.Any) -> str: +def urn(name: str, *args: t.Any) -> str: # noqa: ANN401 """Return the URN name. >>> urn('python') @@ -61,7 +61,7 @@ def urn(name: str, *args: t.Any) -> str: return f"urn:{name}{sep}{st.replace('::', ':')}".lower() -def from_python_type(tp: t.Any) -> 'JSONRPCNewType': +def from_python_type(tp: t.Any) -> 'JSONRPCNewType': # noqa: ANN401 """Convert Python type to JSONRPCNewType. >>> str(from_python_type(str)) @@ -85,7 +85,7 @@ def from_python_type(tp: t.Any) -> 'JSONRPCNewType': return Object -def get(obj: t.Dict[str, t.Any], path: str, default: t.Any = None) -> t.Any: +def get(obj: t.Dict[str, t.Any], path: str, default: t.Any = None) -> t.Any: # noqa: ANN401 """Get the value at any depth of a nested object based on the path described by `path`. If path doesn't exist, `default` is returned. Args: diff --git a/src/flask_jsonrpc/settings.py b/src/flask_jsonrpc/settings.py index 078c9c00..361f48b2 100644 --- a/src/flask_jsonrpc/settings.py +++ b/src/flask_jsonrpc/settings.py @@ -26,21 +26,22 @@ # POSSIBILITY OF SUCH DAMAGE. import typing as t -DEFAULTS = { - 'DEFAULT_JSONRPC_METHOD': { - 'VALIDATE': True, - 'NOTIFICATION': True, - }, -} +# Python 3.10+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + +DEFAULTS = {'DEFAULT_JSONRPC_METHOD': {'VALIDATE': True, 'NOTIFICATION': True}} class JSONRPCSettings: - def __init__(self, defaults: t.Optional[t.Dict[str, t.Any]] = None): + def __init__(self: Self, defaults: t.Optional[t.Dict[str, t.Any]] = None) -> None: self.defaults = defaults or DEFAULTS - def __getattr__(self, attr: str) -> t.Any: + def __getattr__(self: Self, attr: str) -> t.Any: # noqa: ANN401 if attr not in self.defaults: - raise AttributeError(f"Invalid setting: {attr!r}") + raise AttributeError(f'Invalid setting: {attr!r}') val = self.defaults[attr] diff --git a/src/flask_jsonrpc/site.py b/src/flask_jsonrpc/site.py index 37d7a76e..1de3e54e 100644 --- a/src/flask_jsonrpc/site.py +++ b/src/flask_jsonrpc/site.py @@ -25,8 +25,8 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # pylint: disable=R0904 -import typing as t from uuid import UUID, uuid4 +import typing as t from urllib.parse import urlsplit from flask import json, request, current_app @@ -46,6 +46,12 @@ MethodNotFoundError, ) +# Python 3.10+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + JSONRPC_VERSION_DEFAULT: str = '2.0' JSONRPC_DESCRIBE_METHOD_NAME: str = 'rpc.describe' JSONRPC_DESCRIBE_SERVICE_METHOD_TYPE: str = 'method' @@ -54,7 +60,7 @@ class JSONRPCSite: - def __init__(self, path: t.Optional[str] = None, base_url: t.Optional[str] = None) -> None: + def __init__(self: Self, path: t.Optional[str] = None, base_url: t.Optional[str] = None) -> None: self.path = path self.base_url = base_url self.view_funcs: t.Dict[str, t.Callable[..., t.Any]] = {} @@ -64,31 +70,29 @@ def __init__(self, path: t.Optional[str] = None, base_url: t.Optional[str] = Non self.register(JSONRPC_DESCRIBE_METHOD_NAME, self.describe) @property - def is_json(self) -> bool: + def is_json(self: Self) -> bool: """Check if the mimetype indicates JSON data, either :mimetype:`application/json` or :mimetype:`application/*+json`. https://github.com/pallets/werkzeug/blob/master/src/werkzeug/wrappers/json.py#L54 """ mt = request.mimetype - return mt in ( - 'application/json', - 'application/json-rpc', - 'application/jsonrequest', - ) or (mt.startswith('application/') and mt.endswith('+json')) + return mt in ('application/json', 'application/json-rpc', 'application/jsonrequest') or ( + mt.startswith('application/') and mt.endswith('+json') + ) - def set_path(self, path: str) -> None: + def set_path(self: Self, path: str) -> None: self.path = path - def set_base_url(self, base_url: t.Optional[str]) -> None: + def set_base_url(self: Self, base_url: t.Optional[str]) -> None: self.base_url = base_url - def register(self, name: str, view_func: t.Callable[..., t.Any]) -> None: + def register(self: Self, name: str, view_func: t.Callable[..., t.Any]) -> None: self.view_funcs[name] = view_func def dispatch_request( - self, - ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]],]: + self: Self, + ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]]]: if not self.validate_request(): raise ParseError( data={ @@ -102,13 +106,13 @@ def dispatch_request( return self.batch_dispatch(json_data) return self.handle_dispatch_except(json_data) - def validate_request(self) -> bool: + def validate_request(self: Self) -> bool: if not self.is_json: current_app.logger.error('invalid mimetype') return False return True - def to_json(self, request_data: bytes) -> t.Any: + def to_json(self: Self, request_data: bytes) -> t.Any: # noqa: ANN401 try: return json.loads(request_data) except ValueError as e: @@ -116,8 +120,8 @@ def to_json(self, request_data: bytes) -> t.Any: raise ParseError(data={'message': f'Invalid JSON: {request_data!r}'}) from e def handle_dispatch_except( - self, req_json: t.Dict[str, t.Any] - ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]],]: + self: Self, req_json: t.Dict[str, t.Any] + ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]]]: try: if not self.validate(req_json): raise InvalidRequestError(data={'message': f'Invalid JSON: {req_json!r}'}) @@ -141,8 +145,8 @@ def handle_dispatch_except( return response, jsonrpc_error.status_code, JSONRPC_DEFAULT_HTTP_HEADERS def batch_dispatch( - self, reqs_json: t.List[t.Dict[str, t.Any]] - ) -> t.Tuple[t.List[t.Any], int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]],]: + self: Self, reqs_json: t.List[t.Dict[str, t.Any]] + ) -> t.Tuple[t.List[t.Any], int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]]]: if not reqs_json: raise InvalidRequestError(data={'message': 'Empty array'}) @@ -159,19 +163,15 @@ def batch_dispatch( return resp_views, status_code, headers def dispatch( - self, req_json: t.Dict[str, t.Any] - ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]],]: + self: Self, req_json: t.Dict[str, t.Any] + ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]]]: method_name = req_json['method'] params = req_json.get('params', {}) view_func = self.view_funcs.get(method_name) validate = getattr(view_func, 'jsonrpc_validate', settings.DEFAULT_JSONRPC_METHOD['VALIDATE']) - notification = getattr( - view_func, - 'jsonrpc_notification', - settings.DEFAULT_JSONRPC_METHOD['NOTIFICATION'], - ) + notification = getattr(view_func, 'jsonrpc_notification', settings.DEFAULT_JSONRPC_METHOD['NOTIFICATION']) if not view_func: - raise MethodNotFoundError(data={'message': f"Method not found: {method_name}"}) + raise MethodNotFoundError(data={'message': f'Method not found: {method_name}'}) if self.is_notification_request(req_json) and not notification: raise InvalidRequestError( @@ -207,14 +207,15 @@ def dispatch( return self.make_response(req_json, resp_view) - def validate(self, req_json: t.Dict[str, t.Any]) -> bool: + def validate(self: Self, req_json: t.Dict[str, t.Any]) -> bool: if not isinstance(req_json, dict) or 'method' not in req_json: return False return True def unpack_tuple_returns( - self, resp_view: t.Any - ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]],]: + self: Self, + resp_view: t.Any, # noqa: ANN401 + ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]]]: # https://github.com/pallets/flask/blob/d091bb00c0358e9f30006a064f3dbb671b99aeae/src/flask/app.py#L1981 if isinstance(resp_view, tuple): len_resp_view = len(resp_view) @@ -240,39 +241,37 @@ def unpack_tuple_returns( return resp_view, JSONRPC_DEFAULT_HTTP_STATUS_CODE, JSONRPC_DEFAULT_HTTP_HEADERS def make_response( - self, req_json: t.Dict[str, t.Any], resp_view: t.Any - ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]],]: + self: Self, + req_json: t.Dict[str, t.Any], + resp_view: t.Any, # noqa: ANN401 + ) -> t.Tuple[t.Any, int, t.Union[Headers, t.Dict[str, str], t.Tuple[str], t.List[t.Tuple[str]]]]: rv, status_code, headers = self.unpack_tuple_returns(resp_view) if self.is_notification_request(req_json): return None, 204, headers - resp = { - 'id': req_json.get('id'), - 'jsonrpc': req_json.get('jsonrpc', JSONRPC_VERSION_DEFAULT), - 'result': rv, - } + resp = {'id': req_json.get('id'), 'jsonrpc': req_json.get('jsonrpc', JSONRPC_VERSION_DEFAULT), 'result': rv} return resp, status_code, headers - def is_notification_request(self, req_json: t.Dict[str, t.Any]) -> bool: + def is_notification_request(self: Self, req_json: t.Dict[str, t.Any]) -> bool: return 'id' not in req_json - def is_batch_request(self, req_json: t.Any) -> bool: + def is_batch_request(self: Self, req_json: t.Any) -> bool: # noqa: ANN401 return isinstance(req_json, list) - def python_type_name(self, pytype: t.Any) -> str: + def python_type_name(self: Self, pytype: t.Any) -> str: # noqa: ANN401 return str(from_python_type(pytype)) - def service_method_params_desc(self, view_func: t.Callable[..., t.Any]) -> t.List[fjt.ServiceMethodParamsDescribe]: + def service_method_params_desc( + self: Self, view_func: t.Callable[..., t.Any] + ) -> t.List[fjt.ServiceMethodParamsDescribe]: return [ fjt.ServiceMethodParamsDescribe( # pytype: disable=missing-parameter - name=name, - type=self.python_type_name(tp), - required=False, - nullable=False, + name=name, type=self.python_type_name(tp), required=False, nullable=False ) for name, tp in getattr(view_func, 'jsonrpc_method_params', {}).items() + if name not in ['self', 'cls'] # XXX: It assumes the standard param names ] - def service_methods_desc(self) -> t.Dict[str, fjt.ServiceMethodDescribe]: + def service_methods_desc(self: Self) -> t.Dict[str, fjt.ServiceMethodDescribe]: methods: t.Dict[str, fjt.ServiceMethodDescribe] = {} for key, view_func in self.view_funcs.items(): if key == JSONRPC_DESCRIBE_METHOD_NAME: @@ -284,16 +283,16 @@ def service_methods_desc(self) -> t.Dict[str, fjt.ServiceMethodDescribe]: options=getattr(view_func, 'jsonrpc_options', {}), params=self.service_method_params_desc(view_func), returns=fjt.ServiceMethodReturnsDescribe( - type=self.python_type_name(getattr(view_func, 'jsonrpc_method_return', type(None))), + type=self.python_type_name(getattr(view_func, 'jsonrpc_method_return', type(None))) ), ) return methods - def service_server_url(self) -> str: + def service_server_url(self: Self) -> str: url = urlsplit(self.base_url or self.path) return f"{url.scheme!r}://{url.netloc!r}/{(self.path or '').lstrip('/')}" if self.base_url else str(url.path) - def service_desc(self) -> fjt.ServiceDescribe: + def service_desc(self: Self) -> fjt.ServiceDescribe: return fjt.ServiceDescribe( id=f'urn:uuid:{self.uuid}', version=self.version, @@ -303,5 +302,5 @@ def service_desc(self) -> fjt.ServiceDescribe: methods=self.service_methods_desc(), ) - def describe(self) -> fjt.ServiceDescribe: + def describe(self: Self) -> fjt.ServiceDescribe: return self.service_desc() diff --git a/src/flask_jsonrpc/types.py b/src/flask_jsonrpc/types.py index c6fa15ba..2f159343 100644 --- a/src/flask_jsonrpc/types.py +++ b/src/flask_jsonrpc/types.py @@ -31,6 +31,12 @@ from typing_inspect import is_new_type # type: ignore +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + # Python 3.10+ try: from types import NoneType, UnionType @@ -44,31 +50,19 @@ except ImportError: # pragma: no cover from typing import Literal # type: ignore # pylint: disable=C0412 -# Python 3.7+ -try: - from typing import get_args # pylint: disable=C0412 -except ImportError: # pragma: no cover - from typing_inspect import get_args # type: ignore # pylint: disable=C0412 - -# Python 3.7+ -try: - from typing import get_origin # pylint: disable=C0412 -except ImportError: # pragma: no cover - from typing_inspect import get_origin # type: ignore # pylint: disable=C0412 - class JSONRPCNewType: - def __init__(self, name: str, *types: t.Union[type, t.Tuple[t.Union[type, t.Tuple[type, ...]], ...]]) -> None: + def __init__(self: Self, name: str, *types: t.Union[type, t.Tuple[t.Union[type, t.Tuple[type, ...]], ...]]) -> None: self.name = name self.types = types - def check_expected_type(self, expected_type: t.Any) -> bool: + def check_expected_type(self: Self, expected_type: t.Any) -> bool: # noqa: ANN401 return any(expected_type is tp for tp in self.types) - def check_expected_types(self, expected_types: t.Any) -> bool: + def check_expected_types(self: Self, expected_types: t.Any) -> bool: # noqa: ANN401 return all(any(expt_tp is tp for tp in self.types) for expt_tp in expected_types) - def check_type_var(self, expected_type: t.Any) -> bool: + def check_type_var(self: Self, expected_type: t.Any) -> bool: # noqa: ANN401 bound_type = getattr(expected_type, '__bound__', None) if bound_type is None: expected_types = getattr(expected_type, '__constraints__', None) @@ -77,19 +71,19 @@ def check_type_var(self, expected_type: t.Any) -> bool: return self.check_expected_types(expected_types) return self.check_expected_type(bound_type) - def check_new_type(self, expected_type: t.Any) -> bool: + def check_new_type(self: Self, expected_type: t.Any) -> bool: # noqa: ANN401 super_type = getattr(expected_type, '__supertype__', None) return self.check_expected_type(super_type) - def check_union(self, expected_type: t.Any) -> bool: - expected_types = [expt_tp for expt_tp in get_args(expected_type) if expt_tp is not type(None)] # noqa: E721 + def check_union(self: Self, expected_type: t.Any) -> bool: # noqa: ANN401 + expected_types = [expt_tp for expt_tp in t.get_args(expected_type) if expt_tp is not type(None)] # noqa: E721 return self.check_expected_types(expected_types) - def check_args_type(self, expected_type: t.Any) -> bool: - expected_types = get_args(expected_type) + def check_args_type(self: Self, expected_type: t.Any) -> bool: # noqa: ANN401 + expected_types = t.get_args(expected_type) return self.check_expected_types(expected_types) - def check_type(self, o: t.Any) -> bool: # pylint: disable=R0911 + def check_type(self: Self, o: t.Any) -> bool: # pylint: disable=R0911 # noqa: ANN401 expected_type = o if expected_type is t.Any: return self is Object @@ -103,7 +97,7 @@ def check_type(self, o: t.Any) -> bool: # pylint: disable=R0911 if is_new_type(expected_type): return self.check_new_type(expected_type) - origin_type = get_origin(expected_type) + origin_type = t.get_origin(expected_type) if origin_type is not None: if origin_type is t.Union or origin_type is UnionType: return self.check_union(expected_type) @@ -121,7 +115,7 @@ def check_type(self, o: t.Any) -> bool: # pylint: disable=R0911 return self.check_expected_type(expected_type) - def __str__(self) -> str: + def __str__(self: Self) -> str: return self.name diff --git a/src/flask_jsonrpc/views.py b/src/flask_jsonrpc/views.py index fe1979cd..3d276954 100644 --- a/src/flask_jsonrpc/views.py +++ b/src/flask_jsonrpc/views.py @@ -26,22 +26,27 @@ # POSSIBILITY OF SUCH DAMAGE. import typing as t -from flask import typing as ft -from flask import jsonify, current_app, make_response +from flask import typing as ft, jsonify, current_app, make_response from flask.views import MethodView from .site import JSONRPC_VERSION_DEFAULT, JSONRPC_DEFAULT_HTTP_HEADERS from .exceptions import JSONRPCError +# Python 3.10+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + if t.TYPE_CHECKING: from .site import JSONRPCSite class JSONRPCView(MethodView): - def __init__(self, jsonrpc_site: 'JSONRPCSite') -> None: + def __init__(self: Self, jsonrpc_site: 'JSONRPCSite') -> None: self.jsonrpc_site = jsonrpc_site - def post(self) -> ft.ResponseReturnValue: + def post(self: Self) -> ft.ResponseReturnValue: try: response, status_code, headers = self.jsonrpc_site.dispatch_request() if status_code == 204: @@ -49,9 +54,5 @@ def post(self) -> ft.ResponseReturnValue: return make_response(jsonify(response), status_code, headers) except JSONRPCError as e: current_app.logger.exception('jsonrpc error') - response = { - 'id': None, - 'jsonrpc': JSONRPC_VERSION_DEFAULT, - 'error': e.jsonrpc_format, - } + response = {'id': None, 'jsonrpc': JSONRPC_VERSION_DEFAULT, 'error': e.jsonrpc_format} return make_response(jsonify(response), e.status_code, JSONRPC_DEFAULT_HTTP_HEADERS) diff --git a/src/flask_jsonrpc/wrappers.py b/src/flask_jsonrpc/wrappers.py index 4ab12173..0458856c 100644 --- a/src/flask_jsonrpc/wrappers.py +++ b/src/flask_jsonrpc/wrappers.py @@ -31,28 +31,34 @@ from .settings import settings +# Python 3.10+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + if t.TYPE_CHECKING: from .site import JSONRPCSite from .views import JSONRPCView class JSONRPCDecoratorMixin: - def _method_options(self, options: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + def _method_options(self: Self, options: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: default_options = { 'validate': settings.DEFAULT_JSONRPC_METHOD['VALIDATE'], 'notification': settings.DEFAULT_JSONRPC_METHOD['NOTIFICATION'], } return {**default_options, **options} - def _method_has_parameters(self, fn: t.Callable[..., t.Any]) -> bool: + def _method_has_parameters(self: Self, fn: t.Callable[..., t.Any]) -> bool: fn_signature = signature(fn) return bool(fn_signature.parameters) - def _method_has_return(self, fn: t.Callable[..., t.Any]) -> bool: + def _method_has_return(self: Self, fn: t.Callable[..., t.Any]) -> bool: fn_annotations = t.get_type_hints(fn) return 'return' in fn_annotations and fn_annotations['return'] is not type(None) # noqa: E721 - def _validate(self, fn: t.Callable[..., t.Any]) -> bool: + def _validate(self: Self, fn: t.Callable[..., t.Any]) -> bool: if not self._method_has_parameters(fn) and not self._method_has_return(fn): return True if not getattr(fn, '__annotations__', None): @@ -63,29 +69,26 @@ def _validate(self, fn: t.Callable[..., t.Any]) -> bool: return False return True - def _get_function(self, fn: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + def _get_function(self: Self, fn: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: if isfunction(fn): return fn if ismethod(fn) and getattr(fn, '__func__', None): return fn.__func__ # pytype: disable=attribute-error,bad-return-type raise ValueError('the view function must be either a function or a method') - def get_jsonrpc_site(self) -> 'JSONRPCSite': + def get_jsonrpc_site(self: Self) -> 'JSONRPCSite': raise NotImplementedError('.get_jsonrpc_site must be overridden') - def get_jsonrpc_site_api(self) -> t.Type['JSONRPCView']: + def get_jsonrpc_site_api(self: Self) -> t.Type['JSONRPCView']: raise NotImplementedError('.get_jsonrpc_site_api must be overridden') def register_view_function( - self, - view_func: t.Callable[..., t.Any], - name: t.Optional[str] = None, - **options: t.Dict[str, t.Any], + self: Self, view_func: t.Callable[..., t.Any], name: t.Optional[str] = None, **options: t.Dict[str, t.Any] ) -> t.Callable[..., t.Any]: fn = self._get_function(view_func) fn_options = self._method_options(options) fn_annotations = t.get_type_hints(fn) - method_name = getattr(fn, '__name__', '') if not name else name + method_name = name if name else getattr(fn, '__name__', '') view_func_wrapped = typechecked(view_func) if fn_options['validate'] else view_func setattr(view_func_wrapped, 'jsonrpc_method_name', method_name) # noqa: B010 setattr(view_func_wrapped, 'jsonrpc_method_sig', fn_annotations) # noqa: B010 @@ -97,11 +100,11 @@ def register_view_function( self.get_jsonrpc_site().register(method_name, view_func_wrapped) return view_func_wrapped - def method(self, name: t.Optional[str] = None, **options: t.Dict[str, t.Any]) -> t.Callable[..., t.Any]: + def method(self: Self, name: t.Optional[str] = None, **options: t.Dict[str, t.Any]) -> t.Callable[..., t.Any]: validate = options.get('validate', settings.DEFAULT_JSONRPC_METHOD['VALIDATE']) def decorator(fn: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: - method_name = getattr(fn, '__name__', '') if not name else name + method_name = name if name else getattr(fn, '__name__', '') if validate and not self._validate(fn): raise ValueError(f'no type annotations present to: {method_name}') return self.register_view_function(fn, name, **options) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3775a947..2d135627 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -26,31 +26,33 @@ # POSSIBILITY OF SUCH DAMAGE. import os import time -import unittest from pathlib import Path +import unittest import urllib3 import requests from selenium import webdriver +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + WEB_DRIVER_SCREENSHOT_DIR = Path(os.environ['PYTEST_CACHE_DIR']) / 'screenshots' class APITestCase(unittest.TestCase): - def setUp(self) -> None: + def setUp(self: Self) -> None: urllib3.disable_warnings() session = requests.Session() - session.headers.update( - { - 'Content-Type': 'application/json', - } - ) + session.headers.update({'Content-Type': 'application/json'}) session.verify = False self.requests = session class WebDriverTestCase(unittest.TestCase): - def setUp(self) -> None: + def setUp(self: Self) -> None: chrome_prefs = {} chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--no-sandbox') @@ -68,5 +70,5 @@ def setUp(self) -> None: if not WEB_DRIVER_SCREENSHOT_DIR.exists(): WEB_DRIVER_SCREENSHOT_DIR.mkdir(parents=True) - def take_screenshot(self): + def take_screenshot(self: Self) -> None: self.driver.get_screenshot_as_file(str(WEB_DRIVER_SCREENSHOT_DIR / f'{self.id()}-{time.time()}.png')) diff --git a/tests/integration/test_app.py b/tests/integration/test_app.py index 9544eb25..3a101593 100644 --- a/tests/integration/test_app.py +++ b/tests/integration/test_app.py @@ -33,38 +33,32 @@ API_URL = os.environ['API_URL'] +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + class APITest(APITestCase): - def test_greeting(self): + def test_greeting(self: Self) -> None: rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'}, rv.json()) self.assertEqual(200, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'}, rv.json()) self.assertEqual(200, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 'Flask'}, - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 'Flask'}} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'}, rv.json()) self.assertEqual(200, rv.status_code) - def test_app_greeting_with_different_content_types(self): + def test_app_greeting_with_different_content_types(self: Self) -> None: rv = self.requests.post( API_URL, data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}), @@ -75,14 +69,7 @@ def test_app_greeting_with_different_content_types(self): rv = self.requests.post( API_URL, - data=json.dumps( - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - } - ), + data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}), headers={'Content-Type': 'application/jsonrequest'}, ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'}, rv.json()) @@ -90,20 +77,13 @@ def test_app_greeting_with_different_content_types(self): rv = self.requests.post( API_URL, - data=json.dumps( - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 'Flask'}, - } - ), + data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 'Flask'}}), headers={'Content-Type': 'application/json'}, ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'}, rv.json()) self.assertEqual(200, rv.status_code) - def test_greeting_raise_parse_error(self): + def test_greeting_raise_parse_error(self: Self) -> None: rv = self.requests.post(API_URL, data={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) self.assertEqual( { @@ -132,9 +112,7 @@ def test_greeting_raise_parse_error(self): 'error': { 'code': -32700, 'data': { - 'message': 'Invalid JSON: b"{\'id\': 1, ' - '\'jsonrpc\': \'2.0\', ' - '\'method\': \'jsonrpc.greeting\'}"' + 'message': "Invalid JSON: b\"{'id': 1, " "'jsonrpc': '2.0', " "'method': 'jsonrpc.greeting'}\"" }, 'message': 'Parse error', 'name': 'ParseError', @@ -159,7 +137,7 @@ def test_greeting_raise_parse_error(self): 'error': { 'code': -32700, 'data': { - 'message': "Invalid JSON: b\"[\\n " + 'message': 'Invalid JSON: b"[\\n ' "{'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', " "'params': ['Flask'], 'id': '1'},\\n " "{'jsonrpc': '2.0', 'method'\\n ]\"" @@ -172,7 +150,7 @@ def test_greeting_raise_parse_error(self): ) self.assertEqual(400, rv.status_code) - def test_greeting_raise_invalid_request_error(self): + def test_greeting_raise_invalid_request_error(self: Self) -> None: rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0'}) self.assertEqual( { @@ -189,15 +167,9 @@ def test_greeting_raise_invalid_request_error(self): ) self.assertEqual(400, rv.status_code) - def test_greeting_raise_invalid_params_error(self): + def test_greeting_raise_invalid_params_error(self: Self) -> None: rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': 'Wrong', - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': 'Wrong'} ) self.assertEqual( { @@ -216,15 +188,7 @@ def test_greeting_raise_invalid_params_error(self): ) self.assertEqual(400, rv.status_code) - rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': [1], - }, - ) + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': [1]}) self.assertEqual( { 'id': 1, @@ -241,13 +205,7 @@ def test_greeting_raise_invalid_params_error(self): self.assertEqual(400, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 2}, - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 2}} ) self.assertEqual( { @@ -264,7 +222,7 @@ def test_greeting_raise_invalid_params_error(self): ) self.assertEqual(400, rv.status_code) - def test_greeting_raise_method_not_found_error(self): + def test_greeting_raise_method_not_found_error(self: Self) -> None: rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'method-not-found'}) self.assertEqual( { @@ -281,41 +239,21 @@ def test_greeting_raise_method_not_found_error(self): ) self.assertEqual(400, rv.status_code) - def test_echo(self): + def test_echo(self: Self) -> None: rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': ['Python'], - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': ['Python']} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Python'}, rv.json()) self.assertEqual(200, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': {'string': 'Flask'}, - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': {'string': 'Flask'}} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Flask'}, rv.json()) self.assertEqual(200, rv.status_code) - def test_echo_raise_invalid_params_error(self): - rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': 'Wrong', - }, - ) + def test_echo_raise_invalid_params_error(self: Self) -> None: + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': 'Wrong'}) self.assertEqual( { 'id': 1, @@ -333,10 +271,7 @@ def test_echo_raise_invalid_params_error(self): ) self.assertEqual(400, rv.status_code) - rv = self.requests.post( - API_URL, - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [1]}, - ) + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [1]}) self.assertEqual( { 'id': 1, @@ -353,13 +288,7 @@ def test_echo_raise_invalid_params_error(self): self.assertEqual(400, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': {'name': 2}, - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': {'name': 2}} ) self.assertEqual( { @@ -392,49 +321,28 @@ def test_echo_raise_invalid_params_error(self): ) self.assertEqual(400, rv.status_code) - def test_notify(self): + def test_notify(self: Self) -> None: rv = self.requests.post(API_URL, json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify'}) self.assertEqual('', rv.text) self.assertEqual(204, rv.status_code) - rv = self.requests.post( - API_URL, - json={ - 'jsonrpc': '2.0', - 'method': 'jsonrpc.notify', - 'params': ['Some string'], - }, - ) + rv = self.requests.post(API_URL, json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify', 'params': ['Some string']}) self.assertEqual('', rv.text) self.assertEqual(204, rv.status_code) - def test_not_allow_notify(self): - rv = self.requests.post( - API_URL, - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify'}, - ) + def test_not_allow_notify(self: Self) -> None: + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify'}) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Not allow notify'}, rv.json()) self.assertEqual(200, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.not_allow_notify', - 'params': ['Some string'], - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify', 'params': ['Some string']} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Not allow notify'}, rv.json()) self.assertEqual(200, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'jsonrpc': '2.0', - 'method': 'jsonrpc.not_allow_notify', - 'params': ['Some string'], - }, + API_URL, json={'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify', 'params': ['Some string']} ) self.assertEqual( { @@ -454,23 +362,12 @@ def test_not_allow_notify(self): ) self.assertEqual(400, rv.status_code) - def test_fails(self): - rv = self.requests.post( - API_URL, - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [2]}, - ) + def test_fails(self: Self) -> None: + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [2]}) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 2}, rv.json()) self.assertEqual(200, rv.status_code) - rv = self.requests.post( - API_URL, - json={ - 'id': '1', - 'jsonrpc': '2.0', - 'method': 'jsonrpc.fails', - 'params': [1], - }, - ) + rv = self.requests.post(API_URL, json={'id': '1', 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [1]}) self.assertEqual( { 'id': '1', @@ -486,7 +383,7 @@ def test_fails(self): ) self.assertEqual(500, rv.status_code) - def test_strange_echo(self): + def test_strange_echo(self: Self) -> None: data = { 'id': 1, 'jsonrpc': '2.0', @@ -495,12 +392,7 @@ def test_strange_echo(self): } rv = self.requests.post(API_URL, json=data) self.assertEqual( - { - 'id': 1, - 'jsonrpc': '2.0', - 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask'], - }, - rv.json(), + {'id': 1, 'jsonrpc': '2.0', 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask']}, rv.json() ) self.assertEqual(200, rv.status_code) @@ -512,112 +404,59 @@ def test_strange_echo(self): } rv = self.requests.post(API_URL, json=data) self.assertEqual( - { - 'id': 1, - 'jsonrpc': '2.0', - 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Default'], - }, - rv.json(), + {'id': 1, 'jsonrpc': '2.0', 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Default']}, rv.json() ) self.assertEqual(200, rv.status_code) - def test_sum(self): + def test_sum(self: Self) -> None: data = {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.sum', 'params': [1, 3]} rv = self.requests.post(API_URL, json=data) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 4}, rv.json()) self.assertEqual(200, rv.status_code) - data = { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.sum', - 'params': [0.5, 1.5], - } + data = {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.sum', 'params': [0.5, 1.5]} rv = self.requests.post(API_URL, json=data) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 2.0}, rv.json()) self.assertEqual(200, rv.status_code) - def test_decorators(self): - data = { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.decorators', - 'params': ['Python'], - } + def test_decorators(self: Self) -> None: + data = {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.decorators', 'params': ['Python']} rv = self.requests.post(API_URL, json=data) - self.assertEqual( - {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python from decorator, ;)'}, - rv.json(), - ) + self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python from decorator, ;)'}, rv.json()) self.assertEqual(200, rv.status_code) - def test_return_status_code(self): + def test_return_status_code(self: Self) -> None: rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnStatusCode', - 'params': ['OK'], - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnStatusCode', 'params': ['OK']} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Status Code OK'}, rv.json()) self.assertEqual(201, rv.status_code) - def test_return_headers(self): + def test_return_headers(self: Self) -> None: rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnHeaders', - 'params': ['OK'], - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnHeaders', 'params': ['OK']} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Headers OK'}, rv.json()) self.assertEqual(200, rv.status_code) self.assertEqual('1', rv.headers['X-JSONRPC']) - def test_return_status_code_and_headers(self): + def test_return_status_code_and_headers(self: Self) -> None: rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnStatusCodeAndHeaders', - 'params': ['OK'], - }, - ) - self.assertEqual( - {'id': 1, 'jsonrpc': '2.0', 'result': 'Status Code and Headers OK'}, - rv.json(), + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnStatusCodeAndHeaders', 'params': ['OK']} ) + self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Status Code and Headers OK'}, rv.json()) self.assertEqual(400, rv.status_code) self.assertEqual('1', rv.headers['X-JSONRPC']) - def test_not_validate_method(self): + def test_not_validate_method(self: Self) -> None: rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.not_validate', - 'params': ['OK'], - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.not_validate', 'params': ['OK']} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Not validate: OK'}, rv.json()) self.assertEqual(200, rv.status_code) - def test_no_return_method(self): - rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.noReturn', - 'params': [], - }, - ) + def test_no_return_method(self: Self) -> None: + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.noReturn', 'params': []}) self.assertEqual( { 'error': { @@ -633,7 +472,7 @@ def test_no_return_method(self): ) self.assertEqual(500, rv.status_code) - def test_with_rcp_batch(self): + def test_with_rcp_batch(self: Self) -> None: rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'}, rv.json()) self.assertEqual(200, rv.status_code) @@ -641,24 +480,9 @@ def test_with_rcp_batch(self): rv = self.requests.post( API_URL, json=[ - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, - { - 'id': 2, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Flask'], - }, - { - 'id': 3, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['JSON-RCP'], - }, + {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}, + {'id': 2, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Flask']}, + {'id': 3, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['JSON-RCP']}, ], ) self.assertEqual( @@ -674,20 +498,10 @@ def test_with_rcp_batch(self): rv = self.requests.post( API_URL, json=[ - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, + {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}, {'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Flask']}, {'id': 3, 'jsonrpc': '2.0', 'params': ['Flask']}, - { - 'id': 4, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['JSON-RCP'], - }, + {'id': 4, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['JSON-RCP']}, ], ) self.assertEqual( @@ -713,39 +527,22 @@ def test_with_rcp_batch(self): self.assertEqual({'id': 2, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'}, rv.json()) self.assertEqual(200, rv.status_code) - def test_class(self): + def test_class(self: Self) -> None: rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'classapp.index'}) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'}, rv.json()) self.assertEqual(200, rv.status_code) - rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'greeting', - 'params': ['Python'], - }, - ) + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'greeting', 'params': ['Python']}) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'}, rv.json()) self.assertEqual(200, rv.status_code) rv = self.requests.post( - API_URL, - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'hello', - 'params': {'name': 'Flask'}, - }, + API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'hello', 'params': {'name': 'Flask'}} ) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'}, rv.json()) self.assertEqual(200, rv.status_code) - rv = self.requests.post( - API_URL, - json={'id': 1, 'jsonrpc': '2.0', 'method': 'echo', 'params': ['Python', 1]}, - ) + rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'echo', 'params': ['Python', 1]}) self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Python'}, rv.json()) self.assertEqual(200, rv.status_code) @@ -768,7 +565,7 @@ def test_class(self): ) self.assertEqual(500, rv.status_code) - def test_system_describe(self): + def test_system_describe(self: Self) -> None: rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'rpc.describe'}) json_data = rv.json() self.assertEqual(1, json_data['id']) @@ -782,14 +579,7 @@ def test_system_describe(self): 'jsonrpc.greeting': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'name', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'name', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -797,18 +587,8 @@ def test_system_describe(self): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, - { - 'name': '_some', - 'type': 'Object', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, + {'name': '_some', 'type': 'Object', 'required': False, 'nullable': False}, ], 'returns': {'type': 'String'}, 'description': None, @@ -816,42 +596,21 @@ def test_system_describe(self): 'jsonrpc.notify': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, 'jsonrpc.not_allow_notify': { 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, 'jsonrpc.fails': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'n', - 'type': 'Number', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'n', 'type': 'Number', 'required': False, 'nullable': False}], 'returns': {'type': 'Number'}, 'description': None, }, @@ -859,36 +618,11 @@ def test_system_describe(self): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, - { - 'name': 'omg', - 'type': 'Object', - 'required': False, - 'nullable': False, - }, - { - 'name': 'wtf', - 'type': 'Array', - 'required': False, - 'nullable': False, - }, - { - 'name': 'nowai', - 'type': 'Number', - 'required': False, - 'nullable': False, - }, - { - 'name': 'yeswai', - 'type': 'String', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, + {'name': 'omg', 'type': 'Object', 'required': False, 'nullable': False}, + {'name': 'wtf', 'type': 'Array', 'required': False, 'nullable': False}, + {'name': 'nowai', 'type': 'Number', 'required': False, 'nullable': False}, + {'name': 'yeswai', 'type': 'String', 'required': False, 'nullable': False}, ], 'returns': {'type': 'Array'}, 'description': None, @@ -897,18 +631,8 @@ def test_system_describe(self): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'a', - 'type': 'Number', - 'required': False, - 'nullable': False, - }, - { - 'name': 'b', - 'type': 'Number', - 'required': False, - 'nullable': False, - }, + {'name': 'a', 'type': 'Number', 'required': False, 'nullable': False}, + {'name': 'b', 'type': 'Number', 'required': False, 'nullable': False}, ], 'returns': {'type': 'Number'}, 'description': None, @@ -916,56 +640,28 @@ def test_system_describe(self): 'jsonrpc.decorators': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, 'jsonrpc.returnStatusCode': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Array'}, 'description': None, }, 'jsonrpc.returnHeaders': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Array'}, 'description': None, }, 'jsonrpc.returnStatusCodeAndHeaders': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Array'}, 'description': None, }, @@ -979,56 +675,28 @@ def test_system_describe(self): 'jsonrpc.noReturn': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, 'classapp.index': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'name', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'name', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, 'greeting': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'name', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'name', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, 'hello': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'name', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'name', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -1036,18 +704,8 @@ def test_system_describe(self): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, - { - 'name': '_some', - 'type': 'Object', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, + {'name': '_some', 'type': 'Object', 'required': False, 'nullable': False}, ], 'returns': {'type': 'String'}, 'description': None, @@ -1055,42 +713,21 @@ def test_system_describe(self): 'notify': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, 'not_allow_notify': { 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, 'fails': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'n', - 'type': 'Number', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'n', 'type': 'Number', 'required': False, 'nullable': False}], 'returns': {'type': 'Number'}, 'description': None, }, diff --git a/tests/integration/test_browse.py b/tests/integration/test_browse.py index cee9a7c1..5f257ee6 100644 --- a/tests/integration/test_browse.py +++ b/tests/integration/test_browse.py @@ -31,11 +31,17 @@ from .conftest import WebDriverTestCase +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + BROWSABLE_API_URL = os.environ['BROWSABLE_API_URL'] class WebBrowsableAPITest(WebDriverTestCase): - def test_index(self): + def test_index(self: Self) -> None: self.driver.get(BROWSABLE_API_URL) logo_link = self.driver.find_element(By.XPATH, '//*[@id="logo-link"]') self.assertEqual('Web browsable API', logo_link.text) diff --git a/tests/test_apps/app/__init__.py b/tests/test_apps/app/__init__.py index 288d3b74..eaf44bdf 100644 --- a/tests/test_apps/app/__init__.py +++ b/tests/test_apps/app/__init__.py @@ -32,6 +32,12 @@ from flask import Flask +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + try: from flask_jsonrpc import JSONRPC except ModuleNotFoundError: @@ -44,7 +50,7 @@ class App: - def index(self, name: str = 'Flask JSON-RPC') -> str: + def index(self: Self, name: str = 'Flask JSON-RPC') -> str: return f'Hello {name}' @staticmethod @@ -52,34 +58,34 @@ def greeting(name: str = 'Flask JSON-RPC') -> str: return f'Hello {name}' @classmethod - def hello(cls, name: str = 'Flask JSON-RPC') -> str: + def hello(cls: t.Type[Self], name: str = 'Flask JSON-RPC') -> str: return f'Hello {name}' - def echo(self, string: str, _some: t.Any = None) -> str: + def echo(self: Self, string: str, _some: t.Any = None) -> str: # noqa: ANN401 return string - def notify(self, _string: str = None) -> None: + def notify(self: Self, _string: str = None) -> None: pass - def not_allow_notify(self, _string: str = None) -> str: + def not_allow_notify(self: Self, _string: str = None) -> str: return 'Now allow notify' - def fails(self, n: int) -> int: + def fails(self: Self, n: int) -> int: if n % 2 == 0: return n raise ValueError('number is odd') -def jsonrpc_decorator(fn): +def jsonrpc_decorator(fn: t.Callable[..., str]) -> t.Callable[..., str]: @functools.wraps(fn) - def wrapped(*args, **kwargs): + def wrapped(*args, **kwargs) -> str: # noqa: ANN002,ANN003 rv = fn(*args, **kwargs) return f'{rv} from decorator, ;)' return wrapped -def create_app(test_config: t.Dict[str, t.Any] = None): # noqa: C901 pylint: disable=W0612 +def create_app(test_config: t.Dict[str, t.Any] = None) -> Flask: # noqa: C901 pylint: disable=W0612 """Create and configure an instance of the Flask application.""" flask_app = Flask('apptest', instance_relative_config=True) if test_config: @@ -94,7 +100,7 @@ def greeting(name: str = 'Flask JSON-RPC') -> str: # pylint: disable=W0612 @jsonrpc.method('jsonrpc.echo') - def echo(string: str, _some: t.Any = None) -> str: + def echo(string: str, _some: t.Any = None) -> str: # noqa: ANN401 return string # pylint: disable=W0612 @@ -117,11 +123,7 @@ def fails(n: int) -> int: # pylint: disable=W0612 @jsonrpc.method('jsonrpc.strangeEcho') def strange_echo( - string: str, - omg: t.Dict[str, t.Any], - wtf: t.List[str], - nowai: int, - yeswai: str = 'Default', + string: str, omg: t.Dict[str, t.Any], wtf: t.List[str], nowai: int, yeswai: str = 'Default' ) -> t.List[t.Any]: return [string, omg, wtf, nowai, yeswai] @@ -153,7 +155,7 @@ def return_status_code_and_headers(s: str) -> t.Tuple[str, int, t.Dict[str, t.An # pylint: disable=W0612 @jsonrpc.method('jsonrpc.not_validate', validate=False) - def not_validate(s='Oops!'): + def not_validate(s='Oops!'): # noqa: ANN001,ANN202 return f'Not validate: {s}' @jsonrpc.method('jsonrpc.noReturn') diff --git a/tests/test_apps/async_app/__init__.py b/tests/test_apps/async_app/__init__.py index 3d98e458..8bfc79eb 100644 --- a/tests/test_apps/async_app/__init__.py +++ b/tests/test_apps/async_app/__init__.py @@ -33,6 +33,12 @@ from flask import Flask +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self + try: from flask_jsonrpc import JSONRPC except ModuleNotFoundError: @@ -45,7 +51,7 @@ class App: - async def index(self, name: str = 'Flask JSON-RPC') -> str: + async def index(self: Self, name: str = 'Flask JSON-RPC') -> str: await asyncio.sleep(0) return f'Hello {name}' @@ -55,38 +61,38 @@ async def greeting(name: str = 'Flask JSON-RPC') -> str: return f'Hello {name}' @classmethod - async def hello(cls, name: str = 'Flask JSON-RPC') -> str: + async def hello(cls: t.Type[Self], name: str = 'Flask JSON-RPC') -> str: await asyncio.sleep(0) return f'Hello {name}' - async def echo(self, string: str, _some: t.Any = None) -> str: + async def echo(self: Self, string: str, _some: t.Any = None) -> str: # noqa: ANN401 await asyncio.sleep(0) return string - async def notify(self, _string: str = None) -> None: + async def notify(self: Self, _string: str = None) -> None: await asyncio.sleep(0) - async def not_allow_notify(self, _string: str = None) -> str: + async def not_allow_notify(self: Self, _string: str = None) -> str: await asyncio.sleep(0) return 'Now allow notify' - async def fails(self, n: int) -> int: + async def fails(self: Self, n: int) -> int: await asyncio.sleep(0) if n % 2 == 0: return n raise ValueError('number is odd') -def async_jsonrpc_decorator(fn): +def async_jsonrpc_decorator(fn: t.Callable[..., str]) -> t.Callable[..., str]: @functools.wraps(fn) - async def wrapped(*args, **kwargs): + async def wrapped(*args, **kwargs) -> str: # noqa: ANN002,ANN003 rv = await fn(*args, **kwargs) return f'{rv} from decorator, ;)' return wrapped -def create_async_app(test_config: t.Dict[str, t.Any] = None): # noqa: C901 pylint: disable=W0612 +def create_async_app(test_config: t.Dict[str, t.Any] = None) -> Flask: # noqa: C901 pylint: disable=W0612 """Create and configure an instance of the Flask application.""" flask_app = Flask('apptest', instance_relative_config=True) if test_config: @@ -102,7 +108,7 @@ async def greeting(name: str = 'Flask JSON-RPC') -> str: # pylint: disable=W0612 @jsonrpc.method('jsonrpc.echo') - async def echo(string: str, _some: t.Any = None) -> str: + async def echo(string: str, _some: t.Any = None) -> str: # noqa: ANN401 await asyncio.sleep(0) return string @@ -128,11 +134,7 @@ async def fails(n: int) -> int: # pylint: disable=W0612 @jsonrpc.method('jsonrpc.strangeEcho') async def strange_echo( - string: str, - omg: t.Dict[str, t.Any], - wtf: t.List[str], - nowai: int, - yeswai: str = 'Default', + string: str, omg: t.Dict[str, t.Any], wtf: t.List[str], nowai: int, yeswai: str = 'Default' ) -> t.List[t.Any]: await asyncio.sleep(0) return [string, omg, wtf, nowai, yeswai] @@ -164,15 +166,13 @@ async def return_headers(s: str) -> t.Tuple[str, t.Dict[str, t.Any]]: # pylint: disable=W0612 @jsonrpc.method('jsonrpc.returnStatusCodeAndHeaders') - async def return_status_code_and_headers( - s: str, - ) -> t.Tuple[str, int, t.Dict[str, t.Any]]: + async def return_status_code_and_headers(s: str) -> t.Tuple[str, int, t.Dict[str, t.Any]]: await asyncio.sleep(0) return f'Status Code and Headers {s}', 400, {'X-JSONRPC': '1'} # pylint: disable=W0612 @jsonrpc.method('jsonrpc.not_validate', validate=False) - async def not_validate(s='Oops!'): + async def not_validate(s='Oops!'): # noqa: ANN001,ANN202 await asyncio.sleep(0) return f'Not validate: {s}' diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 5f7bcb71..5a05b4fd 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -24,14 +24,26 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +import typing as t + import pytest from ..test_apps.app import create_app from ..test_apps.async_app import create_async_app +# Python 3.11+ +try: + from typing import Self +except ImportError: # pragma: no cover + from typing_extensions import Self # noqa: F401 + +if t.TYPE_CHECKING: + from flask import Flask + from flask.testing import FlaskClient, FlaskCliRunner + @pytest.fixture(scope='module') -def app(): +def app() -> 't.Generator[Flask]': """Create and configure a new app instance for each test.""" flask_app = create_app({'TESTING': True}) @@ -39,7 +51,7 @@ def app(): @pytest.fixture(scope='module') -def async_app(): +def async_app() -> 't.Generator[Flask]': """Create and configure a new async app instance for each test.""" flask_app = create_async_app({'TESTING': True}) @@ -48,14 +60,14 @@ def async_app(): # pylint: disable=W0621 @pytest.fixture(scope='module') -def client(app): +def client(app: 'Flask') -> 'FlaskClient': """A test client for the app.""" return app.test_client() # pylint: disable=W0621 @pytest.fixture(scope='module') -def async_client(async_app): +def async_client(async_app: 'Flask') -> 't.Generator[FlaskClient]': """A test async client for the app.""" with async_app.test_client() as testing_client: yield testing_client @@ -63,6 +75,6 @@ def async_client(async_app): # pylint: disable=W0621 @pytest.fixture -def runner(app): +def runner(app: 'Flask') -> 'FlaskCliRunner': """A test runner for the app's Click commands.""" return app.test_cli_runner() diff --git a/tests/unit/contrib/test_browse.py b/tests/unit/contrib/test_browse.py index b077f621..0fa184ba 100644 --- a/tests/unit/contrib/test_browse.py +++ b/tests/unit/contrib/test_browse.py @@ -24,19 +24,20 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. + from flask import Flask from flask_jsonrpc import JSONRPC, JSONRPCBlueprint from flask_jsonrpc.contrib.browse import JSONRPCBrowse -def test_browse_create(): +def test_browse_create() -> None: app = Flask('test_browse', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) # pylint: disable=W0612 @jsonrpc.method('app.fn1', validate=False) - def fn1(s): + def fn1(s): # noqa: ANN001,ANN202 """Function app.fn1""" return f'Foo {s}' @@ -55,17 +56,11 @@ def fn3(s: str) -> str: assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo 1'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 @@ -107,14 +102,7 @@ def fn3(s: str) -> str: 'name': 'app.fn2', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -122,14 +110,7 @@ def fn3(s: str) -> str: 'name': 'app.fn3', 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -164,7 +145,7 @@ def fn3(s: str) -> str: assert rv.status_code == 200 -def test_jsonrpc_browse(): +def test_jsonrpc_browse() -> None: app = Flask('test_browse', instance_relative_config=True) jsonrpc_browse = JSONRPCBrowse() jsonrpc_browse.init_app(app) @@ -185,7 +166,7 @@ def test_jsonrpc_browse(): assert rv.status_code == 200 -def test_browse_create_without_register_app(): +def test_browse_create_without_register_app() -> None: app = Flask('test_browse', instance_relative_config=True) jsonrpc = JSONRPC(service_url='/api', enable_web_browsable_api=True) @@ -204,14 +185,7 @@ def fn1(s: str) -> str: 'name': 'app.fn2', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, } @@ -229,7 +203,7 @@ def fn1(s: str) -> str: assert rv.status_code == 200 -def test_browse_create_multiple_jsonrpc_versions(): +def test_browse_create_multiple_jsonrpc_versions() -> None: app = Flask('test_browse', instance_relative_config=True) jsonrpc_v1 = JSONRPC(app, '/api/v1', enable_web_browsable_api=True) jsonrpc_v2 = JSONRPC(app, '/api/v2', enable_web_browsable_api=True) @@ -262,14 +236,7 @@ def fn2(s: str) -> str: 'name': 'app.fn2', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -277,14 +244,7 @@ def fn2(s: str) -> str: 'name': 'app.fn3', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -323,14 +283,7 @@ def fn2(s: str) -> str: 'name': 'app.fn1', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -338,14 +291,7 @@ def fn2(s: str) -> str: 'name': 'app.fn2', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -379,7 +325,7 @@ def fn2(s: str) -> str: assert rv.status_code == 200 -def test_browse_create_modular_apps(): +def test_browse_create_modular_apps() -> None: jsonrpc_api_1 = JSONRPCBlueprint('jsonrpc_api_1', __name__) # pylint: disable=W0612 @@ -425,14 +371,7 @@ def fn1_b3(s: str) -> str: 'name': 'blue1.fn2', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, } @@ -442,14 +381,7 @@ def fn1_b3(s: str) -> str: 'name': 'blue2.fn1', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -457,14 +389,7 @@ def fn1_b3(s: str) -> str: 'name': 'blue2.fn2', 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -472,14 +397,7 @@ def fn1_b3(s: str) -> str: 'name': 'blue2.not_notify', 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': 's', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 's', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py index ba3338cc..00b363dc 100644 --- a/tests/unit/test_app.py +++ b/tests/unit/test_app.py @@ -36,7 +36,7 @@ from flask_jsonrpc import JSONRPC, JSONRPCBlueprint -def test_app_create(): +def test_app_create() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -72,15 +72,8 @@ def fn4(s: str) -> str: jsonrpc.register(fn3, name='app.fn3') with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}, - ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}) + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn0', 'params': []}) @@ -91,24 +84,15 @@ def fn4(s: str) -> str: assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn4', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn4', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Goo :)'} assert rv.status_code == 200 @@ -133,11 +117,7 @@ def fn4(s: str) -> str: data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'app.index'}), headers={'Content-Type': 'application/json-rpc'}, ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 rv = client.post( @@ -145,11 +125,7 @@ def fn4(s: str) -> str: data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}), headers={'Content-Type': 'application/jsonrequest'}, ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 rv = client.post( @@ -157,15 +133,11 @@ def fn4(s: str) -> str: data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}), headers={'Content-Type': 'application/json'}, ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 -def test_app_create_with_server_name(): +def test_app_create_with_server_name() -> None: app = Flask('test_app', instance_relative_config=True) app.config.update({'SERVER_NAME': 'domain:80'}) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -176,19 +148,12 @@ def index() -> str: return 'Welcome to Flask JSON-RPC' with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}, - ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}) + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 -def test_app_create_without_register_app(): +def test_app_create_without_register_app() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(service_url='/api', enable_web_browsable_api=True) @@ -200,32 +165,28 @@ def fn1(s: str) -> str: jsonrpc.init_app(app) with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 -def test_app_create_without_register_browse(): +def test_app_create_without_register_browse() -> None: jsonrpc = JSONRPC(service_url='/api', enable_web_browsable_api=True) with pytest.raises( - RuntimeError, - match='You need to init the Browse app before register the Site, see JSONRPC.init_browse_app(...)', + RuntimeError, match='You need to init the Browse app before register the Site, see JSONRPC.init_browse_app(...)' ): jsonrpc.register_browse(jsonrpc) -def test_app_create_with_method_without_annotation(): +def test_app_create_with_method_without_annotation() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) with pytest.raises(ValueError, match='no type annotations present to: app.fn1'): # pylint: disable=W0612 @jsonrpc.method('app.fn1') - def fn1(s): + def fn1(s): # noqa: ANN001,ANN202 return f'Foo {s}' # pylint: disable=W0612 @@ -236,11 +197,11 @@ def fn2(s: str) -> str: with pytest.raises(ValueError, match='no type annotations present to: app.fn3'): # pylint: disable=W0612 @jsonrpc.method('app.fn3') - def fn3(s): # pylint: disable=W0612 + def fn3(s): # pylint: disable=W0612 # noqa: ANN001,ANN202 return f'Poo {s}' -def test_app_create_with_method_without_annotation_on_params(): +def test_app_create_with_method_without_annotation_on_params() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -252,7 +213,7 @@ def fn4() -> None: with pytest.raises(ValueError, match='no type annotations present to: app.fn2'): # pylint: disable=W0612 @jsonrpc.method('app.fn2') - def fn2(s) -> str: + def fn2(s) -> str: # noqa: ANN001 return f'Foo {s}' # pylint: disable=W0612 @@ -263,17 +224,17 @@ def fn1(s: str) -> str: with pytest.raises(ValueError, match='no type annotations present to: app.fn3'): # pylint: disable=W0612 @jsonrpc.method('app.fn3') - def fn3(s): # pylint: disable=W0612 + def fn3(s): # pylint: disable=W0612 # noqa: ANN001,ANN202 return f'Poo {s}' -def test_app_create_with_method_without_annotation_on_return(): +def test_app_create_with_method_without_annotation_on_return() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) # pylint: disable=W0612 @jsonrpc.method('app.fn1') - def fn1(s: str): + def fn1(s: str): # noqa: ANN202 return f'Foo {s}' # pylint: disable=W0612 @@ -287,10 +248,7 @@ def fn3(s: str) -> t.NoReturn: raise ValueError(f'no return: {s}') with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}) assert rv.json == { 'error': { 'code': -32602, @@ -303,17 +261,11 @@ def fn3(s: str) -> t.NoReturn: } assert rv.status_code == 400 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar :)'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': ['OK']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': ['OK']}) assert rv.json == { 'error': { 'code': -32000, @@ -327,7 +279,7 @@ def fn3(s: str) -> t.NoReturn: assert rv.status_code == 500 -def test_app_create_with_wrong_return(): +def test_app_create_with_wrong_return() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -336,10 +288,7 @@ def fn2(s: str) -> t.Tuple[str, int, int, int]: # pylint: disable=W0612 return f'Bar {s}', 1, 2, 3 with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}) assert rv.json == { 'error': { 'code': -32000, @@ -358,7 +307,7 @@ def fn2(s: str) -> t.Tuple[str, int, int, int]: # pylint: disable=W0612 assert rv.status_code == 500 -def test_app_create_with_invalid_view_func(): +def test_app_create_with_invalid_view_func() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, service_url='/api', enable_web_browsable_api=True) @@ -371,15 +320,12 @@ def fn1(s: str) -> str: jsonrpc.register(fn1.__new__, name='invalid') with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 -def test_app_create_multiple_jsonrpc_versions(): +def test_app_create_multiple_jsonrpc_versions() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc_v1 = JSONRPC(app, '/api/v1', enable_web_browsable_api=True) jsonrpc_v2 = JSONRPC(app, '/api/v2', enable_web_browsable_api=True) @@ -417,50 +363,32 @@ def fn4_v2(s: str) -> str: jsonrpc_v2.register(fn4_v2) with app.test_client() as client: - rv = client.post( - '/api/v1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api/v1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'v1: Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api/v2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':D']}, - ) + rv = client.post('/api/v2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':D']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'v2: Foo :D'} assert rv.status_code == 200 - rv = client.post( - '/api/v1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [';)']}, - ) + rv = client.post('/api/v1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [';)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Poo ;)'} assert rv.status_code == 200 - rv = client.post( - '/api/v2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': ['\\oB']}, - ) + rv = client.post('/api/v2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': ['\\oB']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar \\oB'} assert rv.status_code == 200 - rv = client.post( - '/api/v1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v1', 'params': ['\\oB']}, - ) + rv = client.post('/api/v1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v1', 'params': ['\\oB']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Poo \\oB'} assert rv.status_code == 200 - rv = client.post( - '/api/v2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v2', 'params': ['\\oB']}, - ) + rv = client.post('/api/v2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v2', 'params': ['\\oB']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar \\oB'} assert rv.status_code == 200 -def test_app_create_modular_apps(): +def test_app_create_modular_apps() -> None: jsonrpc_api_1 = JSONRPCBlueprint('jsonrpc_api_1', __name__) # pylint: disable=W0612 @@ -494,37 +422,25 @@ def fn1_b3(s: str) -> str: jsonrpc.register_blueprint(app, jsonrpc_api_3, url_prefix='/b3') with app.test_client() as client: - rv = client.post( - '/api/b1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue1.fn2', 'params': [':)']}, - ) + rv = client.post('/api/b1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue1.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b1: Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api/b2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn2', 'params': [':)']}, - ) + rv = client.post('/api/b2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b2: Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api/b2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn1', 'params': [':)']}, - ) + rv = client.post('/api/b2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn1', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b2: Bar :)'} assert rv.status_code == 200 - rv = client.post( - '/api/b3', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue3.fn2', 'params': [':)']}, - ) + rv = client.post('/api/b3', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue3.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b3: Foo :)'} assert rv.status_code == 200 # pylint: disable=R0915 -def test_app_create_with_rcp_batch(): +def test_app_create_with_rcp_batch() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -589,10 +505,7 @@ def headers_duplicate() -> t.Tuple[float, int, t.Dict[str, t.Any]]: with app.test_client() as client: idx = uuid.uuid4() - rv = client.post( - '/api', - json={'id': idx.hex, 'jsonrpc': '2.0', 'method': 'sum', 'params': [1, 1]}, - ) + rv = client.post('/api', json={'id': idx.hex, 'jsonrpc': '2.0', 'method': 'sum', 'params': [1, 1]}) assert rv.json == {'id': idx.hex, 'jsonrpc': '2.0', 'result': 2} assert rv.status_code == 200 @@ -686,12 +599,7 @@ def headers_duplicate() -> t.Tuple[float, int, t.Dict[str, t.Any]]: json=[ {'id': '1', 'jsonrpc': '2.0', 'method': 'sum', 'params': [1, 1]}, {'id': '2', 'jsonrpc': '2.0', 'method': 'subtract', 'params': [2, 2]}, - { - 'id': '3', - 'jsonrpc': '2.0', - 'method': 'get_user', - 'params': {'uid': '345'}, - }, + {'id': '3', 'jsonrpc': '2.0', 'method': 'get_user', 'params': {'uid': '345'}}, {'jsonrpc': '2.0', 'method': 'notify_sum', 'params': [[1, 2, 3, 4, 5]]}, {'id': 'h1', 'jsonrpc': '2.0', 'method': 'headers1'}, {'id': 'h2', 'jsonrpc': '2.0', 'method': 'headers2'}, diff --git a/tests/unit/test_async_app.py b/tests/unit/test_async_app.py index 80f36f3f..e9edc862 100644 --- a/tests/unit/test_async_app.py +++ b/tests/unit/test_async_app.py @@ -39,7 +39,7 @@ pytest.importorskip('asgiref') -def test_app_create(): +def test_app_create() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -51,7 +51,7 @@ async def index() -> str: # pylint: disable=W0612 @jsonrpc.method('app.fn0') - async def fn0(): + async def fn0() -> None: await asyncio.sleep(0) # pylint: disable=W0612 @@ -79,15 +79,8 @@ def fn4(s: str) -> str: jsonrpc.register(fn3, name='app.fn3') with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}, - ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}) + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn0', 'params': []}) @@ -98,24 +91,15 @@ def fn4(s: str) -> str: assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn4', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn4', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Goo :)'} assert rv.status_code == 200 @@ -140,11 +124,7 @@ def fn4(s: str) -> str: data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'app.index'}), headers={'Content-Type': 'application/json-rpc'}, ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 rv = client.post( @@ -152,11 +132,7 @@ def fn4(s: str) -> str: data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}), headers={'Content-Type': 'application/jsonrequest'}, ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 rv = client.post( @@ -164,15 +140,11 @@ def fn4(s: str) -> str: data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}), headers={'Content-Type': 'application/json'}, ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 -def test_app_create_with_server_name(): +def test_app_create_with_server_name() -> None: app = Flask('test_app', instance_relative_config=True) app.config.update({'SERVER_NAME': 'domain:80'}) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -183,19 +155,12 @@ def index() -> str: return 'Welcome to Flask JSON-RPC' with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}, - ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Welcome to Flask JSON-RPC', - } + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.index', 'params': []}) + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Welcome to Flask JSON-RPC'} assert rv.status_code == 200 -def test_app_create_without_register_app(): +def test_app_create_without_register_app() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(service_url='/api', enable_web_browsable_api=True) @@ -208,22 +173,19 @@ async def fn1(s: str) -> str: jsonrpc.init_app(app) with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 -def test_app_create_with_method_without_annotation(): +def test_app_create_with_method_without_annotation() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) with pytest.raises(ValueError, match='no type annotations present to: app.fn1'): # pylint: disable=W0612 @jsonrpc.method('app.fn1') - async def fn1(s): + async def fn1(s): # noqa: ANN001,ANN202 await asyncio.sleep(0) return f'Foo {s}' @@ -236,24 +198,24 @@ async def fn2(s: str) -> str: with pytest.raises(ValueError, match='no type annotations present to: app.fn3'): # pylint: disable=W0612 @jsonrpc.method('app.fn3') - async def fn3(s): # pylint: disable=W0612 + async def fn3(s): # pylint: disable=W0612 # noqa: ANN001,ANN202 await asyncio.sleep(0) return f'Poo {s}' -def test_app_create_with_method_without_annotation_on_params(): +def test_app_create_with_method_without_annotation_on_params() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) # pylint: disable=W0612 @jsonrpc.method('app.fn4') - async def fn4(): + async def fn4() -> None: await asyncio.sleep(0) with pytest.raises(ValueError, match='no type annotations present to: app.fn2'): # pylint: disable=W0612 @jsonrpc.method('app.fn2') - async def fn2(s) -> str: + async def fn2(s) -> str: # noqa: ANN001 await asyncio.sleep(0) return f'Foo {s}' @@ -266,18 +228,18 @@ async def fn1(s: str) -> str: with pytest.raises(ValueError, match='no type annotations present to: app.fn3'): # pylint: disable=W0612 @jsonrpc.method('app.fn3') - async def fn3(s): # pylint: disable=W0612 + async def fn3(s): # pylint: disable=W0612 # noqa: ANN001,ANN202 await asyncio.sleep(0) return f'Poo {s}' -def test_app_create_with_method_without_annotation_on_return(): +def test_app_create_with_method_without_annotation_on_return() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) # pylint: disable=W0612 @jsonrpc.method('app.fn1') - async def fn1(s: str): + async def fn1(s: str): # noqa: ANN202 await asyncio.sleep(0) return f'Foo {s}' @@ -294,10 +256,7 @@ async def fn3(s: str) -> t.NoReturn: raise ValueError(f'no return: {s}') with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}) assert rv.json == { 'error': { 'code': -32602, @@ -310,17 +269,11 @@ async def fn3(s: str) -> t.NoReturn: } assert rv.status_code == 400 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar :)'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': ['OK']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': ['OK']}) assert rv.json == { 'error': { 'code': -32000, @@ -334,7 +287,7 @@ async def fn3(s: str) -> t.NoReturn: assert rv.status_code == 500 -def test_app_create_with_wrong_return(): +def test_app_create_with_wrong_return() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -344,10 +297,7 @@ async def fn2(s: str) -> t.Tuple[str, int, int, int]: # pylint: disable=W0612 return f'Bar {s}', 1, 2, 3 with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [':)']}) assert rv.json == { 'error': { 'code': -32000, @@ -366,7 +316,7 @@ async def fn2(s: str) -> t.Tuple[str, int, int, int]: # pylint: disable=W0612 assert rv.status_code == 500 -def test_app_create_with_invalid_view_func(): +def test_app_create_with_invalid_view_func() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, service_url='/api', enable_web_browsable_api=True) @@ -380,15 +330,12 @@ async def fn1(s: str) -> str: jsonrpc.register(fn1.__new__, name='invalid') with app.test_client() as client: - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo :)'} assert rv.status_code == 200 -def test_app_create_multiple_jsonrpc_versions(): +def test_app_create_multiple_jsonrpc_versions() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc_v1 = JSONRPC(app, '/api/v1', enable_web_browsable_api=True) jsonrpc_v2 = JSONRPC(app, '/api/v2', enable_web_browsable_api=True) @@ -432,50 +379,32 @@ async def fn4_v2(s: str) -> str: jsonrpc_v2.register(fn4_v2) with app.test_client() as client: - rv = client.post( - '/api/v1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}, - ) + rv = client.post('/api/v1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'v1: Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api/v2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':D']}, - ) + rv = client.post('/api/v2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn2', 'params': [':D']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'v2: Foo :D'} assert rv.status_code == 200 - rv = client.post( - '/api/v1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [';)']}, - ) + rv = client.post('/api/v1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn3', 'params': [';)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Poo ;)'} assert rv.status_code == 200 - rv = client.post( - '/api/v2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': ['\\oB']}, - ) + rv = client.post('/api/v2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': ['\\oB']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar \\oB'} assert rv.status_code == 200 - rv = client.post( - '/api/v1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v1', 'params': ['\\oB']}, - ) + rv = client.post('/api/v1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v1', 'params': ['\\oB']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Poo \\oB'} assert rv.status_code == 200 - rv = client.post( - '/api/v2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v2', 'params': ['\\oB']}, - ) + rv = client.post('/api/v2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'fn4_v2', 'params': ['\\oB']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Bar \\oB'} assert rv.status_code == 200 -def test_app_create_modular_apps(): +def test_app_create_modular_apps() -> None: jsonrpc_api_1 = JSONRPCBlueprint('jsonrpc_api_1', __name__) # pylint: disable=W0612 @@ -513,31 +442,19 @@ async def fn1_b3(s: str) -> str: jsonrpc.register_blueprint(app, jsonrpc_api_3, url_prefix='/b3') with app.test_client() as client: - rv = client.post( - '/api/b1', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue1.fn2', 'params': [':)']}, - ) + rv = client.post('/api/b1', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue1.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b1: Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api/b2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn2', 'params': [':)']}, - ) + rv = client.post('/api/b2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b2: Foo :)'} assert rv.status_code == 200 - rv = client.post( - '/api/b2', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn1', 'params': [':)']}, - ) + rv = client.post('/api/b2', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue2.fn1', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b2: Bar :)'} assert rv.status_code == 200 - rv = client.post( - '/api/b3', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue3.fn2', 'params': [':)']}, - ) + rv = client.post('/api/b3', json={'id': 1, 'jsonrpc': '2.0', 'method': 'blue3.fn2', 'params': [':)']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'b3: Foo :)'} assert rv.status_code == 200 @@ -545,7 +462,7 @@ async def fn1_b3(s: str) -> str: # pylint: disable=R0915 -def test_app_create_with_rcp_batch(): +def test_app_create_with_rcp_batch() -> None: app = Flask('test_app', instance_relative_config=True) jsonrpc = JSONRPC(app, '/api', enable_web_browsable_api=True) @@ -619,10 +536,7 @@ async def headers_duplicate() -> t.Tuple[float, int, t.Dict[str, t.Any]]: with app.test_client() as client: idx = uuid.uuid4() - rv = client.post( - '/api', - json={'id': idx.hex, 'jsonrpc': '2.0', 'method': 'sum', 'params': [1, 1]}, - ) + rv = client.post('/api', json={'id': idx.hex, 'jsonrpc': '2.0', 'method': 'sum', 'params': [1, 1]}) assert rv.json == {'id': idx.hex, 'jsonrpc': '2.0', 'result': 2} assert rv.status_code == 200 @@ -716,12 +630,7 @@ async def headers_duplicate() -> t.Tuple[float, int, t.Dict[str, t.Any]]: json=[ {'id': '1', 'jsonrpc': '2.0', 'method': 'sum', 'params': [1, 1]}, {'id': '2', 'jsonrpc': '2.0', 'method': 'subtract', 'params': [2, 2]}, - { - 'id': '3', - 'jsonrpc': '2.0', - 'method': 'get_user', - 'params': {'uid': '345'}, - }, + {'id': '3', 'jsonrpc': '2.0', 'method': 'get_user', 'params': {'uid': '345'}}, {'jsonrpc': '2.0', 'method': 'notify_sum', 'params': [[1, 2, 3, 4, 5]]}, {'id': 'h1', 'jsonrpc': '2.0', 'method': 'headers1'}, {'id': 'h2', 'jsonrpc': '2.0', 'method': 'headers2'}, diff --git a/tests/unit/test_async_client.py b/tests/unit/test_async_client.py index 3abbd96e..9964536f 100644 --- a/tests/unit/test_async_client.py +++ b/tests/unit/test_async_client.py @@ -25,43 +25,33 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import json +import typing as t import pytest +if t.TYPE_CHECKING: + from flask.templating import FlaskClient + pytest.importorskip('asgiref') -def test_app_greeting(async_client): +def test_app_greeting(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'} assert rv.status_code == 200 - rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, - ) + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'} assert rv.status_code == 200 rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 'Flask'}, - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 'Flask'}} ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'} assert rv.status_code == 200 -def test_app_greeting_with_different_content_types(async_client): +def test_app_greeting_with_different_content_types(async_client: 'FlaskClient') -> None: rv = async_client.post( '/api', data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}), @@ -72,14 +62,7 @@ def test_app_greeting_with_different_content_types(async_client): rv = async_client.post( '/api', - data=json.dumps( - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - } - ), + data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}), headers={'Content-Type': 'application/jsonrequest'}, ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'} @@ -87,21 +70,14 @@ def test_app_greeting_with_different_content_types(async_client): rv = async_client.post( '/api', - data=json.dumps( - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 'Flask'}, - } - ), + data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 'Flask'}}), headers={'Content-Type': 'application/json'}, ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'} assert rv.status_code == 200 -def test_app_greeting_raise_parse_error(async_client): +def test_app_greeting_raise_parse_error(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', data={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) assert rv.json == { 'id': None, @@ -128,7 +104,7 @@ def test_app_greeting_raise_parse_error(async_client): 'jsonrpc': '2.0', 'error': { 'code': -32700, - 'data': {'message': 'Invalid JSON: b"{\'id\': 1, \'jsonrpc\': \'2.0\', \'method\': \'jsonrpc.greeting\'}"'}, + 'data': {'message': "Invalid JSON: b\"{'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}\""}, 'message': 'Parse error', 'name': 'ParseError', }, @@ -149,7 +125,7 @@ def test_app_greeting_raise_parse_error(async_client): 'error': { 'code': -32700, 'data': { - 'message': 'Invalid JSON: b"[\\n {\'jsonrpc\': ' + 'message': "Invalid JSON: b\"[\\n {'jsonrpc': " "'2.0', 'method': 'jsonrpc.greeting', 'params': " "['Flask'], 'id': '1'},\\n " "{'jsonrpc': '2.0', 'method'\\n " @@ -162,7 +138,7 @@ def test_app_greeting_raise_parse_error(async_client): assert rv.status_code == 400 -def test_app_greeting_raise_invalid_request_error(async_client): +def test_app_greeting_raise_invalid_request_error(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0'}) assert rv.json == { 'id': 1, @@ -177,16 +153,8 @@ def test_app_greeting_raise_invalid_request_error(async_client): assert rv.status_code == 400 -def test_app_greeting_raise_invalid_params_error(async_client): - rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': 'Wrong', - }, - ) +def test_app_greeting_raise_invalid_params_error(async_client: 'FlaskClient') -> None: + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': 'Wrong'}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -199,10 +167,7 @@ def test_app_greeting_raise_invalid_params_error(async_client): } assert rv.status_code == 400 - rv = async_client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': [1]}, - ) + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': [1]}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -216,13 +181,7 @@ def test_app_greeting_raise_invalid_params_error(async_client): assert rv.status_code == 400 rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 2}, - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 2}} ) assert rv.json == { 'id': 1, @@ -237,7 +196,7 @@ def test_app_greeting_raise_invalid_params_error(async_client): assert rv.status_code == 400 -def test_app_greeting_raise_method_not_found_error(async_client): +def test_app_greeting_raise_method_not_found_error(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'method-not-found'}) assert rv.json == { 'id': 1, @@ -252,37 +211,20 @@ def test_app_greeting_raise_method_not_found_error(async_client): assert rv.status_code == 400 -def test_app_echo(async_client): - rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': ['Python'], - }, - ) +def test_app_echo(async_client: 'FlaskClient') -> None: + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': ['Python']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Python'} assert rv.status_code == 200 rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': {'string': 'Flask'}, - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': {'string': 'Flask'}} ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Flask'} assert rv.status_code == 200 -def test_app_echo_raise_invalid_params_error(async_client): - rv = async_client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': 'Wrong'}, - ) +def test_app_echo_raise_invalid_params_error(async_client: 'FlaskClient') -> None: + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': 'Wrong'}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -295,10 +237,7 @@ def test_app_echo_raise_invalid_params_error(async_client): } assert rv.status_code == 400 - rv = async_client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [1]}, - ) + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [1]}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -311,15 +250,7 @@ def test_app_echo_raise_invalid_params_error(async_client): } assert rv.status_code == 400 - rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': {'name': 2}, - }, - ) + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': {'name': 2}}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -346,20 +277,17 @@ def test_app_echo_raise_invalid_params_error(async_client): assert rv.status_code == 400 -def test_app_notify(async_client): +def test_app_notify(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify'}) assert rv.json is None assert rv.status_code == 204 - rv = async_client.post( - '/api', - json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify', 'params': ['Some string']}, - ) + rv = async_client.post('/api', json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify', 'params': ['Some string']}) assert rv.json is None assert rv.status_code == 204 -def test_app_not_allow_notify(async_client): +def test_app_not_allow_notify(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify'}) assert rv.json == { 'error': { @@ -377,45 +305,28 @@ def test_app_not_allow_notify(async_client): assert rv.status_code == 400 rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.not_allow_notify', - 'params': ['Some string'], - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify', 'params': ['Some string']} ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Not allow notify'} assert rv.status_code == 200 -def test_app_no_return(async_client): +def test_app_no_return(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.noReturn'}) assert rv.json == { - 'error': { - 'code': -32000, - 'data': {'message': 'no return'}, - 'message': 'Server error', - 'name': 'ServerError', - }, + 'error': {'code': -32000, 'data': {'message': 'no return'}, 'message': 'Server error', 'name': 'ServerError'}, 'id': 1, 'jsonrpc': '2.0', } assert rv.status_code == 500 -def test_app_fails(async_client): - rv = async_client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [2]}, - ) +def test_app_fails(async_client: 'FlaskClient') -> None: + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [2]}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 2} assert rv.status_code == 200 - rv = async_client.post( - '/api', - json={'id': '1', 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [1]}, - ) + rv = async_client.post('/api', json={'id': '1', 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [1]}) assert rv.json == { 'id': '1', 'jsonrpc': '2.0', @@ -429,7 +340,7 @@ def test_app_fails(async_client): assert rv.status_code == 500 -def test_app_strange_echo(async_client): +def test_app_strange_echo(async_client: 'FlaskClient') -> None: data = { 'id': 1, 'jsonrpc': '2.0', @@ -437,11 +348,7 @@ def test_app_strange_echo(async_client): 'params': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask'], } rv = async_client.post('/api', json=data) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask'], - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask']} assert rv.status_code == 200 data = { @@ -451,15 +358,11 @@ def test_app_strange_echo(async_client): 'params': ['string', {'a': 1}, ['a', 'b', 'c'], 23], } rv = async_client.post('/api', json=data) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Default'], - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Default']} assert rv.status_code == 200 -def test_app_sum(async_client): +def test_app_sum(async_client: 'FlaskClient') -> None: data = {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.sum', 'params': [1, 3]} rv = async_client.post('/api', json=data) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 4} @@ -471,71 +374,40 @@ def test_app_sum(async_client): assert rv.status_code == 200 -def test_app_decorators(async_client): - data = { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.decorators', - 'params': ['Python'], - } +def test_app_decorators(async_client: 'FlaskClient') -> None: + data = {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.decorators', 'params': ['Python']} rv = async_client.post('/api', json=data) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Hello Python from decorator, ;)', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python from decorator, ;)'} assert rv.status_code == 200 -def test_app_return_status_code(async_client): +def test_app_return_status_code(async_client: 'FlaskClient') -> None: rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnStatusCode', - 'params': ['OK'], - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnStatusCode', 'params': ['OK']} ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Status Code OK'} assert rv.status_code == 201 -def test_app_return_headers(async_client): +def test_app_return_headers(async_client: 'FlaskClient') -> None: rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnHeaders', - 'params': ['OK'], - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnHeaders', 'params': ['OK']} ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Headers OK'} assert rv.status_code == 200 assert ('X-JSONRPC', '1') in list(rv.headers) -def test_app_return_status_code_and_headers(async_client): +def test_app_return_status_code_and_headers(async_client: 'FlaskClient') -> None: rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnStatusCodeAndHeaders', - 'params': ['OK'], - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnStatusCodeAndHeaders', 'params': ['OK']} ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Status Code and Headers OK', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Status Code and Headers OK'} assert rv.status_code == 400 assert ('X-JSONRPC', '1') in list(rv.headers) -def test_app_with_rcp_batch(async_client): +def test_app_with_rcp_batch(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'} assert rv.status_code == 200 @@ -543,24 +415,9 @@ def test_app_with_rcp_batch(async_client): rv = async_client.post( '/api', json=[ - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, - { - 'id': 2, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Flask'], - }, - { - 'id': 3, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['JSON-RCP'], - }, + {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}, + {'id': 2, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Flask']}, + {'id': 3, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['JSON-RCP']}, ], ) assert rv.json == [ @@ -573,20 +430,10 @@ def test_app_with_rcp_batch(async_client): rv = async_client.post( '/api', json=[ - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, + {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}, {'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Flask']}, {'id': 3, 'jsonrpc': '2.0', 'params': ['Flask']}, - { - 'id': 4, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['JSON-RCP'], - }, + {'id': 4, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['JSON-RCP']}, ], ) assert rv.json == [ @@ -610,34 +457,20 @@ def test_app_with_rcp_batch(async_client): assert rv.status_code == 200 -def test_app_class(async_client): +def test_app_class(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'classapp.index'}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'} assert rv.status_code == 200 - rv = async_client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'greeting', 'params': ['Python']}, - ) + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'greeting', 'params': ['Python']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'} assert rv.status_code == 200 - rv = async_client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'hello', - 'params': {'name': 'Flask'}, - }, - ) + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'hello', 'params': {'name': 'Flask'}}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'} assert rv.status_code == 200 - rv = async_client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'echo', 'params': ['Python', 1]}, - ) + rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'echo', 'params': ['Python', 1]}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Python'} assert rv.status_code == 200 @@ -658,7 +491,7 @@ def test_app_class(async_client): assert rv.status_code == 500 -def test_app_system_describe(async_client): +def test_app_system_describe(async_client: 'FlaskClient') -> None: rv = async_client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'rpc.describe'}) assert rv.json['id'] == 1 assert rv.json['jsonrpc'] == '2.0' @@ -679,18 +512,8 @@ def test_app_system_describe(async_client): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, - { - 'name': '_some', - 'type': 'Object', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, + {'name': '_some', 'type': 'Object', 'required': False, 'nullable': False}, ], 'returns': {'type': 'String'}, 'description': None, @@ -698,28 +521,14 @@ def test_app_system_describe(async_client): 'jsonrpc.notify': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, 'jsonrpc.not_allow_notify': { 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -734,26 +543,11 @@ def test_app_system_describe(async_client): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, {'name': 'omg', 'type': 'Object', 'required': False, 'nullable': False}, {'name': 'wtf', 'type': 'Array', 'required': False, 'nullable': False}, - { - 'name': 'nowai', - 'type': 'Number', - 'required': False, - 'nullable': False, - }, - { - 'name': 'yeswai', - 'type': 'String', - 'required': False, - 'nullable': False, - }, + {'name': 'nowai', 'type': 'Number', 'required': False, 'nullable': False}, + {'name': 'yeswai', 'type': 'String', 'required': False, 'nullable': False}, ], 'returns': {'type': 'Array'}, 'description': None, @@ -771,14 +565,7 @@ def test_app_system_describe(async_client): 'jsonrpc.decorators': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -813,14 +600,7 @@ def test_app_system_describe(async_client): 'jsonrpc.noReturn': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, @@ -849,18 +629,8 @@ def test_app_system_describe(async_client): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, - { - 'name': '_some', - 'type': 'Object', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, + {'name': '_some', 'type': 'Object', 'required': False, 'nullable': False}, ], 'returns': {'type': 'String'}, 'description': None, @@ -868,28 +638,14 @@ def test_app_system_describe(async_client): 'notify': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, 'not_allow_notify': { 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 8b2e0ef2..ca76e64d 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -25,39 +25,29 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import json +import typing as t +if t.TYPE_CHECKING: + from flask.testing import FlaskClient -def test_app_greeting(client): + +def test_app_greeting(client: 'FlaskClient') -> None: rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'} assert rv.status_code == 200 rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 'Flask'}, - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 'Flask'}} ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'} assert rv.status_code == 200 -def test_app_greeting_with_different_content_types(client): +def test_app_greeting_with_different_content_types(client: 'FlaskClient') -> None: rv = client.post( '/api', data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}), @@ -68,14 +58,7 @@ def test_app_greeting_with_different_content_types(client): rv = client.post( '/api', - data=json.dumps( - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - } - ), + data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}), headers={'Content-Type': 'application/jsonrequest'}, ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'} @@ -83,21 +66,14 @@ def test_app_greeting_with_different_content_types(client): rv = client.post( '/api', - data=json.dumps( - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 'Flask'}, - } - ), + data=json.dumps({'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 'Flask'}}), headers={'Content-Type': 'application/json'}, ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'} assert rv.status_code == 200 -def test_app_greeting_raise_parse_error(client): +def test_app_greeting_raise_parse_error(client: 'FlaskClient') -> None: rv = client.post('/api', data={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) assert rv.json == { 'id': None, @@ -124,7 +100,7 @@ def test_app_greeting_raise_parse_error(client): 'jsonrpc': '2.0', 'error': { 'code': -32700, - 'data': {'message': 'Invalid JSON: b"{\'id\': 1, \'jsonrpc\': \'2.0\', \'method\': \'jsonrpc.greeting\'}"'}, + 'data': {'message': "Invalid JSON: b\"{'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}\""}, 'message': 'Parse error', 'name': 'ParseError', }, @@ -145,7 +121,7 @@ def test_app_greeting_raise_parse_error(client): 'error': { 'code': -32700, 'data': { - 'message': 'Invalid JSON: b"[\\n {\'jsonrpc\': ' + 'message': "Invalid JSON: b\"[\\n {'jsonrpc': " "'2.0', 'method': 'jsonrpc.greeting', 'params': " "['Flask'], 'id': '1'},\\n " "{'jsonrpc': '2.0', 'method'\\n " @@ -158,7 +134,7 @@ def test_app_greeting_raise_parse_error(client): assert rv.status_code == 400 -def test_app_greeting_raise_invalid_request_error(client): +def test_app_greeting_raise_invalid_request_error(client: 'FlaskClient') -> None: rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0'}) assert rv.json == { 'id': 1, @@ -173,16 +149,8 @@ def test_app_greeting_raise_invalid_request_error(client): assert rv.status_code == 400 -def test_app_greeting_raise_invalid_params_error(client): - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': 'Wrong', - }, - ) +def test_app_greeting_raise_invalid_params_error(client: 'FlaskClient') -> None: + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': 'Wrong'}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -195,10 +163,7 @@ def test_app_greeting_raise_invalid_params_error(client): } assert rv.status_code == 400 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': [1]}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': [1]}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -211,15 +176,7 @@ def test_app_greeting_raise_invalid_params_error(client): } assert rv.status_code == 400 - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': {'name': 2}, - }, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': {'name': 2}}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -233,7 +190,7 @@ def test_app_greeting_raise_invalid_params_error(client): assert rv.status_code == 400 -def test_app_greeting_raise_method_not_found_error(client): +def test_app_greeting_raise_method_not_found_error(client: 'FlaskClient') -> None: rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'method-not-found'}) assert rv.json == { 'id': 1, @@ -248,37 +205,18 @@ def test_app_greeting_raise_method_not_found_error(client): assert rv.status_code == 400 -def test_app_echo(client): - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': ['Python'], - }, - ) +def test_app_echo(client: 'FlaskClient') -> None: + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': ['Python']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Python'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': {'string': 'Flask'}, - }, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': {'string': 'Flask'}}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Flask'} assert rv.status_code == 200 -def test_app_echo_raise_invalid_params_error(client): - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': 'Wrong'}, - ) +def test_app_echo_raise_invalid_params_error(client: 'FlaskClient') -> None: + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': 'Wrong'}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -291,10 +229,7 @@ def test_app_echo_raise_invalid_params_error(client): } assert rv.status_code == 400 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [1]}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': [1]}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -307,15 +242,7 @@ def test_app_echo_raise_invalid_params_error(client): } assert rv.status_code == 400 - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.echo', - 'params': {'name': 2}, - }, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.echo', 'params': {'name': 2}}) assert rv.json == { 'id': 1, 'jsonrpc': '2.0', @@ -342,20 +269,17 @@ def test_app_echo_raise_invalid_params_error(client): assert rv.status_code == 400 -def test_app_notify(client): +def test_app_notify(client: 'FlaskClient') -> None: rv = client.post('/api', json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify'}) assert rv.json is None assert rv.status_code == 204 - rv = client.post( - '/api', - json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify', 'params': ['Some string']}, - ) + rv = client.post('/api', json={'jsonrpc': '2.0', 'method': 'jsonrpc.notify', 'params': ['Some string']}) assert rv.json is None assert rv.status_code == 204 -def test_app_not_allow_notify(client): +def test_app_not_allow_notify(client: 'FlaskClient') -> None: rv = client.post('/api', json={'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify'}) assert rv.json == { 'error': { @@ -373,45 +297,28 @@ def test_app_not_allow_notify(client): assert rv.status_code == 400 rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.not_allow_notify', - 'params': ['Some string'], - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.not_allow_notify', 'params': ['Some string']} ) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Not allow notify'} assert rv.status_code == 200 -def test_app_no_return(client): +def test_app_no_return(client: 'FlaskClient') -> None: rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.noReturn'}) assert rv.json == { - 'error': { - 'code': -32000, - 'data': {'message': 'no return'}, - 'message': 'Server error', - 'name': 'ServerError', - }, + 'error': {'code': -32000, 'data': {'message': 'no return'}, 'message': 'Server error', 'name': 'ServerError'}, 'id': 1, 'jsonrpc': '2.0', } assert rv.status_code == 500 -def test_app_fails(client): - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [2]}, - ) +def test_app_fails(client: 'FlaskClient') -> None: + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [2]}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 2} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': '1', 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [1]}, - ) + rv = client.post('/api', json={'id': '1', 'jsonrpc': '2.0', 'method': 'jsonrpc.fails', 'params': [1]}) assert rv.json == { 'id': '1', 'jsonrpc': '2.0', @@ -425,7 +332,7 @@ def test_app_fails(client): assert rv.status_code == 500 -def test_app_strange_echo(client): +def test_app_strange_echo(client: 'FlaskClient') -> None: data = { 'id': 1, 'jsonrpc': '2.0', @@ -433,11 +340,7 @@ def test_app_strange_echo(client): 'params': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask'], } rv = client.post('/api', json=data) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask'], - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Flask']} assert rv.status_code == 200 data = { @@ -447,15 +350,11 @@ def test_app_strange_echo(client): 'params': ['string', {'a': 1}, ['a', 'b', 'c'], 23], } rv = client.post('/api', json=data) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Default'], - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': ['string', {'a': 1}, ['a', 'b', 'c'], 23, 'Default']} assert rv.status_code == 200 -def test_app_sum(client): +def test_app_sum(client: 'FlaskClient') -> None: data = {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.sum', 'params': [1, 3]} rv = client.post('/api', json=data) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 4} @@ -467,71 +366,36 @@ def test_app_sum(client): assert rv.status_code == 200 -def test_app_decorators(client): - data = { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.decorators', - 'params': ['Python'], - } +def test_app_decorators(client: 'FlaskClient') -> None: + data = {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.decorators', 'params': ['Python']} rv = client.post('/api', json=data) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Hello Python from decorator, ;)', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python from decorator, ;)'} assert rv.status_code == 200 -def test_app_return_status_code(client): - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnStatusCode', - 'params': ['OK'], - }, - ) +def test_app_return_status_code(client: 'FlaskClient') -> None: + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnStatusCode', 'params': ['OK']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Status Code OK'} assert rv.status_code == 201 -def test_app_return_headers(client): - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnHeaders', - 'params': ['OK'], - }, - ) +def test_app_return_headers(client: 'FlaskClient') -> None: + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnHeaders', 'params': ['OK']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Headers OK'} assert rv.status_code == 200 assert ('X-JSONRPC', '1') in list(rv.headers) -def test_app_return_status_code_and_headers(client): +def test_app_return_status_code_and_headers(client: 'FlaskClient') -> None: rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.returnStatusCodeAndHeaders', - 'params': ['OK'], - }, + '/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.returnStatusCodeAndHeaders', 'params': ['OK']} ) - assert rv.json == { - 'id': 1, - 'jsonrpc': '2.0', - 'result': 'Status Code and Headers OK', - } + assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Status Code and Headers OK'} assert rv.status_code == 400 assert ('X-JSONRPC', '1') in list(rv.headers) -def test_app_with_rcp_batch(client): +def test_app_with_rcp_batch(client: 'FlaskClient') -> None: rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting'}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'} assert rv.status_code == 200 @@ -539,24 +403,9 @@ def test_app_with_rcp_batch(client): rv = client.post( '/api', json=[ - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, - { - 'id': 2, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Flask'], - }, - { - 'id': 3, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['JSON-RCP'], - }, + {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}, + {'id': 2, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Flask']}, + {'id': 3, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['JSON-RCP']}, ], ) assert rv.json == [ @@ -569,20 +418,10 @@ def test_app_with_rcp_batch(client): rv = client.post( '/api', json=[ - { - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['Python'], - }, + {'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Python']}, {'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['Flask']}, {'id': 3, 'jsonrpc': '2.0', 'params': ['Flask']}, - { - 'id': 4, - 'jsonrpc': '2.0', - 'method': 'jsonrpc.greeting', - 'params': ['JSON-RCP'], - }, + {'id': 4, 'jsonrpc': '2.0', 'method': 'jsonrpc.greeting', 'params': ['JSON-RCP']}, ], ) assert rv.json == [ @@ -606,34 +445,20 @@ def test_app_with_rcp_batch(client): assert rv.status_code == 200 -def test_app_class(client): +def test_app_class(client: 'FlaskClient') -> None: rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'classapp.index'}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask JSON-RPC'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'greeting', 'params': ['Python']}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'greeting', 'params': ['Python']}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Python'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={ - 'id': 1, - 'jsonrpc': '2.0', - 'method': 'hello', - 'params': {'name': 'Flask'}, - }, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'hello', 'params': {'name': 'Flask'}}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Hello Flask'} assert rv.status_code == 200 - rv = client.post( - '/api', - json={'id': 1, 'jsonrpc': '2.0', 'method': 'echo', 'params': ['Python', 1]}, - ) + rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'echo', 'params': ['Python', 1]}) assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Python'} assert rv.status_code == 200 @@ -654,7 +479,7 @@ def test_app_class(client): assert rv.status_code == 500 -def test_app_system_describe(client): +def test_app_system_describe(client: 'FlaskClient') -> None: rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'rpc.describe'}) assert rv.json['id'] == 1 assert rv.json['jsonrpc'] == '2.0' @@ -675,18 +500,8 @@ def test_app_system_describe(client): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, - { - 'name': '_some', - 'type': 'Object', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, + {'name': '_some', 'type': 'Object', 'required': False, 'nullable': False}, ], 'returns': {'type': 'String'}, 'description': None, @@ -694,28 +509,14 @@ def test_app_system_describe(client): 'jsonrpc.notify': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, 'jsonrpc.not_allow_notify': { 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -730,26 +531,11 @@ def test_app_system_describe(client): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, {'name': 'omg', 'type': 'Object', 'required': False, 'nullable': False}, {'name': 'wtf', 'type': 'Array', 'required': False, 'nullable': False}, - { - 'name': 'nowai', - 'type': 'Number', - 'required': False, - 'nullable': False, - }, - { - 'name': 'yeswai', - 'type': 'String', - 'required': False, - 'nullable': False, - }, + {'name': 'nowai', 'type': 'Number', 'required': False, 'nullable': False}, + {'name': 'yeswai', 'type': 'String', 'required': False, 'nullable': False}, ], 'returns': {'type': 'Array'}, 'description': None, @@ -767,14 +553,7 @@ def test_app_system_describe(client): 'jsonrpc.decorators': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': 'string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, @@ -809,14 +588,7 @@ def test_app_system_describe(client): 'jsonrpc.noReturn': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, @@ -845,18 +617,8 @@ def test_app_system_describe(client): 'type': 'method', 'options': {'notification': True, 'validate': True}, 'params': [ - { - 'name': 'string', - 'type': 'String', - 'required': False, - 'nullable': False, - }, - { - 'name': '_some', - 'type': 'Object', - 'required': False, - 'nullable': False, - }, + {'name': 'string', 'type': 'String', 'required': False, 'nullable': False}, + {'name': '_some', 'type': 'Object', 'required': False, 'nullable': False}, ], 'returns': {'type': 'String'}, 'description': None, @@ -864,28 +626,14 @@ def test_app_system_describe(client): 'notify': { 'type': 'method', 'options': {'notification': True, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'Null'}, 'description': None, }, 'not_allow_notify': { 'type': 'method', 'options': {'notification': False, 'validate': True}, - 'params': [ - { - 'name': '_string', - 'type': 'String', - 'required': False, - 'nullable': False, - } - ], + 'params': [{'name': '_string', 'type': 'String', 'required': False, 'nullable': False}], 'returns': {'type': 'String'}, 'description': None, }, diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index 5bb5ee99..2d4bdbc6 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -24,6 +24,7 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. + from flask_jsonrpc.exceptions import ( ParseError, ServerError, @@ -35,7 +36,7 @@ ) -def test_jsonrpc_error(): +def test_jsonrpc_error() -> None: error = JSONRPCError(message="I'm a teapot", code=-32768, data={'data': [1, 2, 3]}, status_code=418) assert error.code == -32768 assert error.message == "I'm a teapot" @@ -49,35 +50,25 @@ def test_jsonrpc_error(): } -def test_jsonrpc_error_with_default_params(): +def test_jsonrpc_error_with_default_params() -> None: error = JSONRPCError() assert error.code == 0 assert error.message is None assert error.data is None assert error.status_code == 400 - assert error.jsonrpc_format == { - 'code': 0, - 'data': None, - 'message': None, - 'name': 'JSONRPCError', - } + assert error.jsonrpc_format == {'code': 0, 'data': None, 'message': None, 'name': 'JSONRPCError'} -def test_parser_error(): +def test_parser_error() -> None: error = ParseError() assert error.code == -32700 assert error.message == 'Parse error' assert error.data is None assert error.status_code == 400 - assert error.jsonrpc_format == { - 'code': -32700, - 'data': None, - 'message': 'Parse error', - 'name': 'ParseError', - } + assert error.jsonrpc_format == {'code': -32700, 'data': None, 'message': 'Parse error', 'name': 'ParseError'} -def test_invalid_request_error(): +def test_invalid_request_error() -> None: error = InvalidRequestError() assert error.code == -32600 assert error.message == 'Invalid Request' @@ -91,7 +82,7 @@ def test_invalid_request_error(): } -def test_method_not_found_error(): +def test_method_not_found_error() -> None: error = MethodNotFoundError() assert error.code == -32601 assert error.message == 'Method not found' @@ -105,7 +96,7 @@ def test_method_not_found_error(): } -def test_invalid_params_error(): +def test_invalid_params_error() -> None: error = InvalidParamsError() assert error.code == -32602 assert error.message == 'Invalid params' @@ -119,29 +110,19 @@ def test_invalid_params_error(): } -def test_internal_error(): +def test_internal_error() -> None: error = InternalError() assert error.code == -32603 assert error.message == 'Internal error' assert error.data is None assert error.status_code == 400 - assert error.jsonrpc_format == { - 'code': -32603, - 'data': None, - 'message': 'Internal error', - 'name': 'InternalError', - } + assert error.jsonrpc_format == {'code': -32603, 'data': None, 'message': 'Internal error', 'name': 'InternalError'} -def test_server_error(): +def test_server_error() -> None: error = ServerError() assert error.code == -32000 assert error.message == 'Server error' assert error.data is None assert error.status_code == 500 - assert error.jsonrpc_format == { - 'code': -32000, - 'data': None, - 'message': 'Server error', - 'name': 'ServerError', - } + assert error.jsonrpc_format == {'code': -32000, 'data': None, 'message': 'Server error', 'name': 'ServerError'} diff --git a/tests/unit/test_helpers.py b/tests/unit/test_helpers.py index 971760a8..364e12b2 100644 --- a/tests/unit/test_helpers.py +++ b/tests/unit/test_helpers.py @@ -31,7 +31,7 @@ from flask_jsonrpc.helpers import urn, from_python_type -def test_urn(): +def test_urn() -> None: with pytest.raises(ValueError, match='name is required'): urn(None) with pytest.raises(ValueError, match='name is required'): @@ -39,7 +39,7 @@ def test_urn(): assert urn('a', 'b', 'c', 'd') == 'urn:a:b:c:d' -def test_from_python_type(): +def test_from_python_type() -> None: assert str(from_python_type(str)) == 'String' assert str(from_python_type(t.AnyStr)) == 'String' assert str(from_python_type(int)) == 'Number' diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index fc4a7427..2bd61edf 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -24,12 +24,13 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. + import pytest from flask_jsonrpc.settings import JSONRPCSettings -def test_settings(): +def test_settings() -> None: settings = JSONRPCSettings({'setting': True}) assert settings.setting is True with pytest.raises(AttributeError, match="Invalid setting: 'xxx'"): diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 0a40fc30..94e73ab9 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -34,7 +34,7 @@ from flask_jsonrpc import types -def test_types(): +def test_types() -> None: assert types.String.check_type(str) assert types.String.check_type(t.AnyStr) assert str(types.String) == 'String' @@ -74,14 +74,14 @@ def test_types(): assert str(types.Null) == 'Null' -def test_types_others(): +def test_types_others() -> None: assert types.Object.check_type(OrderedDict) assert types.Object.check_type(defaultdict) assert types.Object.check_type(t.DefaultDict) assert types.Object.check_type(t.Mapping) -def test_types_complex(): +def test_types_complex() -> None: T = t.TypeVar('T') S = t.TypeVar('S', int, float) X = t.TypeVar('X', bound=int) @@ -97,8 +97,8 @@ def test_types_complex(): assert types.String.check_type(t.Optional[str]) -def test_types_from_fn(): - def fn(_a: str, _b: int, _c: t.Dict[str, t.Any], _d: t.List[int], _e: t.Any) -> bool: +def test_types_from_fn() -> None: + def fn(_a: str, _b: int, _c: t.Dict[str, t.Any], _d: t.List[int], _e: t.Any) -> bool: # noqa: ANN401 return True fn_annotations = t.get_type_hints(fn) @@ -112,7 +112,7 @@ def fn(_a: str, _b: int, _c: t.Dict[str, t.Any], _d: t.List[int], _e: t.Any) -> # pylint: disable=E1136 @pytest.mark.skipif(sys.version_info < (3, 9), reason='requires python3.9 or higher') -def test_generic_type_alias(): +def test_generic_type_alias() -> None: T = t.TypeVar('T') assert types.Array.check_type(list[int]) @@ -127,7 +127,7 @@ def test_generic_type_alias(): # pylint: disable=E1131 @pytest.mark.skipif(sys.version_info < (3, 10), reason='requires python3.10 or higher') -def test_union_type_expression(): +def test_union_type_expression() -> None: assert types.Array.check_type(list[int | str]) assert types.String.check_type(str | bytearray) assert types.String.check_type(bytearray | str) @@ -136,12 +136,12 @@ def test_union_type_expression(): @pytest.mark.skipif(sys.version_info < (3, 10), reason='requires python3.10 or higher') -def test_none_type(): +def test_none_type() -> None: assert types.Null.check_type(type(None)) assert types.Null.check_type(types.NoneType) -def test_new_type(): +def test_new_type() -> None: UserId = t.NewType('UserId', int) UserUid = t.NewType('UserUid', str)