diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 750621c7..fa348cdf 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -10,6 +10,7 @@ on: jobs: test: + name: "Tests" runs-on: ubuntu-latest strategy: matrix: @@ -31,3 +32,16 @@ jobs: run: python setup.py test - name: Upload coverage uses: codecov/codecov-action@v1 + + static-checks: + name: "Static checks" + runs-on: ubuntu-latest + steps: + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v2 + - name: "Setup Python" + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: "Run static checks" + uses: pre-commit/action@v2.0.3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..85ea3f48 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +--- +default_stages: [commit, push] +default_language_version: + # force all unspecified python hooks to run python3 + python: python3 +minimum_pre_commit_version: "1.20.0" +repos: + - repo: meta + hooks: + - id: check-hooks-apply + - repo: https://github.com/asottile/pyupgrade + rev: v2.19.0 + hooks: + - id: pyupgrade + args: ["--py36-plus"] + - repo: local + hooks: + - id: flynt + name: Convert to f-strings with flynt + entry: flynt + language: python + additional_dependencies: ['flynt==0.64'] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b0939050 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ + +Contributor Guide +================= + +# Static checks + +The project uses static checks using fantastic [pre-commit](https://pre-commit.com/). Every change is checked on CI and if it does not pass the tests it cannot be accepted. If you want to check locally then run following command to install pre-commit: + +```bash +pip install -r requiremenets_dev.txt +``` + +To turn on pre-commit checks for commit operations in git, enter: +```bash +pre-commit install +``` + +To run all checks on your staged files, enter: +```bash +pre-commit run +``` + +To run all checks on all files, enter: +```bash +pre-commit run --all-files +``` + +Pre-commit check results are also attached to your PR through integration with Github Action. diff --git a/openapi_core/__init__.py b/openapi_core/__init__.py index 980bcd15..0803b960 100644 --- a/openapi_core/__init__.py +++ b/openapi_core/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """OpenAPI core module""" from openapi_core.shortcuts import ( create_spec, validate_request, validate_response, diff --git a/openapi_core/casting/schemas/exceptions.py b/openapi_core/casting/schemas/exceptions.py index 416f2573..d271d130 100644 --- a/openapi_core/casting/schemas/exceptions.py +++ b/openapi_core/casting/schemas/exceptions.py @@ -10,5 +10,4 @@ class CastError(OpenAPIError): type: str def __str__(self): - return "Failed to cast value to {type} type: {value}".format( - type=self.type, value=self.value) + return f"Failed to cast value to {self.type} type: {self.value}" diff --git a/openapi_core/contrib/django/backports.py b/openapi_core/contrib/django/backports.py index 9643980c..050512a9 100644 --- a/openapi_core/contrib/django/backports.py +++ b/openapi_core/contrib/django/backports.py @@ -24,4 +24,4 @@ def parse_header_name(cls, header): def request_current_scheme_host(req): - return '{}://{}'.format(req.scheme, req.get_host()) + return f'{req.scheme}://{req.get_host()}' diff --git a/openapi_core/deserializing/media_types/util.py b/openapi_core/deserializing/media_types/util.py index 5b326b21..eccc3ccf 100644 --- a/openapi_core/deserializing/media_types/util.py +++ b/openapi_core/deserializing/media_types/util.py @@ -11,10 +11,8 @@ def data_form_loads(value): value = value.decode('ASCII', errors='surrogateescape') parser = Parser() parts = parser.parsestr(value, headersonly=False) - return dict( - ( - part.get_param('name', header='content-disposition'), - part.get_payload(decode=True), - ) + return { + part.get_param('name', header='content-disposition'): + part.get_payload(decode=True) for part in parts.get_payload() - ) + } diff --git a/openapi_core/deserializing/parameters/exceptions.py b/openapi_core/deserializing/parameters/exceptions.py index f3c04f8c..0009af29 100644 --- a/openapi_core/deserializing/parameters/exceptions.py +++ b/openapi_core/deserializing/parameters/exceptions.py @@ -17,9 +17,9 @@ class ParameterDeserializeError(BaseParameterDeserializeError): def __str__(self): return ( - "Failed to deserialize value " - "of {location} parameter with style {style}: {value}" - ).format(location=self.location, style=self.style, value=self.value) + "Failed to deserialize value of " + f"{self.location} parameter with style {self.style}: {self.value}" + ) @dataclass(init=False) @@ -31,5 +31,6 @@ def __init__(self, name): self.name = name def __str__(self): - return "Value of {name} {location} parameter cannot be empty".format( - name=self.name, location=self.location) + return ( + f"Value of {self.name} {self.location} parameter cannot be empty" + ) diff --git a/openapi_core/exceptions.py b/openapi_core/exceptions.py index 4fcf4d02..8fba2d04 100644 --- a/openapi_core/exceptions.py +++ b/openapi_core/exceptions.py @@ -23,8 +23,7 @@ class MissingHeader(MissingHeaderError): name: str def __str__(self): - return "Missing header (without default value): {0}".format( - self.name) + return f"Missing header (without default value): {self.name}" @dataclass @@ -32,7 +31,7 @@ class MissingRequiredHeader(MissingHeaderError): name: str def __str__(self): - return "Missing required header: {0}".format(self.name) + return f"Missing required header: {self.name}" class OpenAPIParameterError(OpenAPIError): @@ -49,8 +48,7 @@ class MissingParameter(MissingParameterError): name: str def __str__(self): - return "Missing parameter (without default value): {0}".format( - self.name) + return f"Missing parameter (without default value): {self.name}" @dataclass @@ -58,7 +56,7 @@ class MissingRequiredParameter(MissingParameterError): name: str def __str__(self): - return "Missing required parameter: {0}".format(self.name) + return f"Missing required parameter: {self.name}" class OpenAPIRequestBodyError(OpenAPIError): diff --git a/openapi_core/security/providers.py b/openapi_core/security/providers.py index 6d5dff4d..e0613650 100644 --- a/openapi_core/security/providers.py +++ b/openapi_core/security/providers.py @@ -40,6 +40,6 @@ def __call__(self, request): scheme = self.scheme['scheme'] if auth_type.lower() != scheme: raise SecurityError( - 'Unknown authorization method %s' % auth_type) + f'Unknown authorization method {auth_type}') return encoded_credentials diff --git a/openapi_core/templating/media_types/exceptions.py b/openapi_core/templating/media_types/exceptions.py index 03c429e1..0cc7a22e 100644 --- a/openapi_core/templating/media_types/exceptions.py +++ b/openapi_core/templating/media_types/exceptions.py @@ -16,6 +16,6 @@ class MediaTypeNotFound(MediaTypeFinderError): def __str__(self): return ( - "Content for the following mimetype not found: {0}. " - "Valid mimetypes: {1}" - ).format(self.mimetype, self.availableMimetypes) + f"Content for the following mimetype not found: {self.mimetype}. " + f"Valid mimetypes: {self.availableMimetypes}" + ) diff --git a/openapi_core/templating/paths/exceptions.py b/openapi_core/templating/paths/exceptions.py index 615b9f5e..ad720cc9 100644 --- a/openapi_core/templating/paths/exceptions.py +++ b/openapi_core/templating/paths/exceptions.py @@ -13,7 +13,7 @@ class PathNotFound(PathError): url: str def __str__(self): - return "Path not found for {0}".format(self.url) + return f"Path not found for {self.url}" @dataclass @@ -23,8 +23,7 @@ class OperationNotFound(PathError): method: str def __str__(self): - return "Operation {0} not found for {1}".format( - self.method, self.url) + return f"Operation {self.method} not found for {self.url}" @dataclass @@ -33,4 +32,4 @@ class ServerNotFound(PathError): url: str def __str__(self): - return "Server not found for {0}".format(self.url) + return f"Server not found for {self.url}" diff --git a/openapi_core/templating/responses/exceptions.py b/openapi_core/templating/responses/exceptions.py index 1427efc9..71cade50 100644 --- a/openapi_core/templating/responses/exceptions.py +++ b/openapi_core/templating/responses/exceptions.py @@ -16,5 +16,5 @@ class ResponseNotFound(ResponseFinderError): availableresponses: List[str] def __str__(self): - return "Unknown response http status: {0}".format( + return "Unknown response http status: {}".format( str(self.http_status)) diff --git a/openapi_core/templating/responses/finders.py b/openapi_core/templating/responses/finders.py index 9518fd90..3d3dfbd5 100644 --- a/openapi_core/templating/responses/finders.py +++ b/openapi_core/templating/responses/finders.py @@ -11,7 +11,7 @@ def find(self, http_status='default'): return self.responses / http_status # try range - http_status_range = '{0}XX'.format(http_status[0]) + http_status_range = f'{http_status[0]}XX' if http_status_range in self.responses: return self.responses / http_status_range diff --git a/openapi_core/unmarshalling/schemas/exceptions.py b/openapi_core/unmarshalling/schemas/exceptions.py index 20bb63fa..e07b1dab 100644 --- a/openapi_core/unmarshalling/schemas/exceptions.py +++ b/openapi_core/unmarshalling/schemas/exceptions.py @@ -54,5 +54,4 @@ class FormatterNotFoundError(UnmarshallerError): type_format: str def __str__(self): - return "Formatter not found for {format} format".format( - format=self.type_format) + return f"Formatter not found for {self.type_format} format" diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 9f325a41..f5c470ea 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -78,7 +78,7 @@ def _get_parameter(self, param, request): deprecated = param.getkey('deprecated', False) if deprecated: warnings.warn( - "{0} parameter is deprecated".format(name), + f"{name} parameter is deprecated", DeprecationWarning, ) diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index 19e98ad5..a27adea2 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -114,7 +114,7 @@ def _get_header(self, name, header, response): deprecated = header.getkey('deprecated', False) if deprecated: warnings.warn( - "{0} header is deprecated".format(name), + f"{name} header is deprecated", DeprecationWarning, ) diff --git a/requirements_dev.txt b/requirements_dev.txt index fb38e977..d9911d0a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -11,3 +11,4 @@ webob strict-rfc3339==0.7 sphinx==4.0.2 sphinx-rtd-theme==0.5.2 +pre-commit diff --git a/tests/integration/schema/test_link_spec.py b/tests/integration/schema/test_link_spec.py index 22467170..a1d0b9be 100644 --- a/tests/integration/schema/test_link_spec.py +++ b/tests/integration/schema/test_link_spec.py @@ -1,5 +1,3 @@ -from __future__ import division - from openapi_core.shortcuts import create_spec diff --git a/tests/integration/schema/test_path_params.py b/tests/integration/schema/test_path_params.py index 59b4b0fe..93659f90 100644 --- a/tests/integration/schema/test_path_params.py +++ b/tests/integration/schema/test_path_params.py @@ -1,5 +1,3 @@ -from __future__ import division - import pytest from openapi_core.shortcuts import create_spec diff --git a/tests/integration/schema/test_spec.py b/tests/integration/schema/test_spec.py index eed34db4..db7cf40e 100644 --- a/tests/integration/schema/test_spec.py +++ b/tests/integration/schema/test_spec.py @@ -1,4 +1,3 @@ -from __future__ import division import pytest from base64 import b64encode diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 6f338aa7..e46ffe16 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -924,7 +924,7 @@ def test_get_pet(self, spec, response_validator): } auth = 'authuser' headers = { - 'Authorization': 'Basic {auth}'.format(auth=auth), + 'Authorization': f'Basic {auth}', } request = MockRequest( host_url, 'GET', '/pets/1', diff --git a/tests/unit/templating/test_paths_finders.py b/tests/unit/templating/test_paths_finders.py index 6c92e196..a30109d8 100644 --- a/tests/unit/templating/test_paths_finders.py +++ b/tests/unit/templating/test_paths_finders.py @@ -1,4 +1,3 @@ -from __future__ import division import pytest from openapi_core.spec.paths import SpecPath @@ -226,7 +225,7 @@ class BaseTestVariableValid: @pytest.mark.parametrize('version', ['v1', 'v2']) def test_variable(self, finder, spec, version): - request_uri = '/{0}/resource'.format(version) + request_uri = f'/{version}/resource' method = 'get' request = MockRequest( 'http://petstore.swagger.io', method, request_uri) @@ -247,7 +246,7 @@ class BaseTestPathVariableValid: @pytest.mark.parametrize('res_id', ['111', '222']) def test_path_variable(self, finder, spec, res_id): - request_uri = '/resource/{0}'.format(res_id) + request_uri = f'/resource/{res_id}' method = 'get' request = MockRequest( 'http://petstore.swagger.io', method, request_uri) @@ -476,7 +475,7 @@ def paths(self, path, path_2): def test_valid(self, finder, spec): token_id = '123' - request_uri = '/keys/{0}/tokens'.format(token_id) + request_uri = f'/keys/{token_id}/tokens' method = 'get' request = MockRequest( 'http://petstore.swagger.io', method, request_uri) @@ -578,7 +577,7 @@ def paths(self, path, path_2): def test_valid(self, finder, spec): token_id = '123' - request_uri = '/keys/{0}/tokens/master'.format(token_id) + request_uri = f'/keys/{token_id}/tokens/master' method = 'get' request = MockRequest( 'http://petstore.swagger.io', method, request_uri) diff --git a/tests/unit/templating/test_responses_finders.py b/tests/unit/templating/test_responses_finders.py index d8873133..0bf8b648 100644 --- a/tests/unit/templating/test_responses_finders.py +++ b/tests/unit/templating/test_responses_finders.py @@ -1,4 +1,3 @@ -from __future__ import division from unittest import mock import pytest