diff --git a/pyproject.toml b/pyproject.toml index b0471b7..ebdc892 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,70 @@ [build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta:__legacy__" \ No newline at end of file +build-backend = "setuptools.build_meta" +requires = [ + "setuptools>=42", # At least v42 of setuptools required. +] + +[tool.ruff] +line-length = 90 + +extend-exclude = [ + "docs/source/conf.py", + "setup.py", +] + +lint.select = [ + # Builtins + "A", + # Bugbear + "B", + # comprehensions + "C4", + # Pycodestyle + "E", + # eradicate + "ERA", + # Pyflakes + "F", + # isort + "I", + # pandas-vet + "PD", + # return + "RET", + # Bandit + "S", + # print + "T20", + "W", + # flake8-2020 + "YTT", +] + +lint.extend-ignore = [ + "S101", # Allow use of `assert`. +] + +lint.per-file-ignores."tests/*" = [ + "ERA001", # Found commented-out code. + "S101", # Allow use of `assert`, and `print`. +] + +[tool.pytest.ini_options] +addopts = """ + -rfEXs -p pytester --strict-markers --verbosity=3 + --cov --cov-report=term-missing --cov-report=xml + """ +filterwarnings = [ + "error::UserWarning", +] +log_level = "DEBUG" +log_cli_level = "DEBUG" +log_format = "%(asctime)-15s [%(name)-36s] %(levelname)-8s: %(message)s" +minversion = "2.0" +testpaths = [ + "responder", + "tests", +] +markers = [ +] +xfail_strict = true diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 965cd90..0000000 --- a/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -addopts= -rsxX -s -vvv --strict -filterwarnings = - error::UserWarning diff --git a/responder/__init__.py b/responder/__init__.py index 2daa8b0..c573a7a 100644 --- a/responder/__init__.py +++ b/responder/__init__.py @@ -1,2 +1,9 @@ -from .core import * from . import ext +from .core import API, Request, Response + +__all__ = [ + "API", + "Request", + "Response", + "ext", +] diff --git a/responder/api.py b/responder/api.py index 4e089a4..98bad27 100644 --- a/responder/api.py +++ b/responder/api.py @@ -1,29 +1,23 @@ -import json import os - from pathlib import Path -import jinja2 import uvicorn from starlette.exceptions import ExceptionMiddleware -from starlette.middleware.wsgi import WSGIMiddleware -from starlette.middleware.errors import ServerErrorMiddleware from starlette.middleware.cors import CORSMiddleware +from starlette.middleware.errors import ServerErrorMiddleware from starlette.middleware.gzip import GZipMiddleware from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware -from starlette.middleware.trustedhost import TrustedHostMiddleware from starlette.middleware.sessions import SessionMiddleware -from starlette.staticfiles import StaticFiles +from starlette.middleware.trustedhost import TrustedHostMiddleware from starlette.testclient import TestClient -from starlette.websockets import WebSocket -from . import models, status_codes +from . import status_codes from .background import BackgroundQueue +from .ext.schema import OpenAPISchema as OpenAPISchema from .formats import get_formats from .routes import Router -from .statics import DEFAULT_API_THEME, DEFAULT_CORS_PARAMS, DEFAULT_SECRET_KEY -from .ext.schema import OpenAPISchema as OpenAPISchema from .staticfiles import StaticFiles +from .statics import DEFAULT_CORS_PARAMS, DEFAULT_SECRET_KEY from .templates import Templates @@ -34,7 +28,7 @@ class API: :param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist. :param auto_escape: If ``True``, HTML and XML templates will automatically be escaped. :param enable_hsts: If ``True``, send all responses to HTTPS URLs. - """ + """ # noqa: E501 status_codes = status_codes @@ -47,7 +41,7 @@ class API: description=None, terms_of_service=None, contact=None, - license=None, + license=None, # noqa: A002 openapi=None, openapi_route="/schema.yml", static_dir="static", @@ -84,7 +78,7 @@ class API: # if not debug: # raise RuntimeError( # "You need to specify `allowed_hosts` when debug is set to False" - # ) + # ) # noqa: ERA001 allowed_hosts = ["*"] self.allowed_hosts = allowed_hosts @@ -170,11 +164,12 @@ class API: """Given a path portion of a URL, tests that it matches against any registered route. :param path: The path portion of a URL, to test all known routes against. - """ + """ # noqa: E501 (Line too long) for route in self.router.routes: match, _ = route.matches(path) if match: return route + return None def add_route( self, @@ -192,8 +187,9 @@ class API: :param route: A string representation of the route. :param endpoint: The endpoint for the route -- can be a callable, or a class. :param default: If ``True``, all unknown requests will route to this view. - :param static: If ``True``, and no endpoint was passed, render "static/index.html", and it will become a default route. - """ + :param static: If ``True``, and no endpoint was passed, render "static/index.html". + Also, it will become a default route. + """ # noqa: E501 # Path if static: @@ -229,7 +225,8 @@ class API: :param resp: The Response to mutate. :param location: The location of the redirect. :param set_text: If ``True``, sets the Redirect body content automatically. - :param status_code: an `API.status_codes` attribute, or an integer, representing the HTTP status code of the redirect. + :param status_code: an `API.status_codes` attribute, or an integer, + representing the HTTP status code of the redirect. """ resp.redirect(location, set_text=set_text, status_code=status_code) @@ -284,13 +281,15 @@ class API: def mount(self, route, app): """Mounts an WSGI / ASGI application at a given route. - :param route: String representation of the route to be used (shouldn't be parameterized). + :param route: String representation of the route to be used + (shouldn't be parameterized). :param app: The other WSGI / ASGI app. """ self.router.apps.update({route: app}) def session(self, base_url="http://;"): - """Testing HTTP client. Returns a Requests session object, able to send HTTP requests to the Responder application. + """Testing HTTP client. Returns a Requests session object, + able to send HTTP requests to the Responder application. :param base_url: The URL to mount the connection adaptor to. """ @@ -310,11 +309,13 @@ class API: def template(self, filename, *args, **kwargs): """Renders the given `jinja2 `_ template, with provided values supplied. + Note: The current ``api`` instance is by default passed into the view. This is set in the dict ``api.jinja_values_base``. + :param filename: The filename of the jinja2 template, in ``templates_dir``. :param *args: Data to pass into the template. :param *kwargs: Date to pass into the template. - """ + """ # noqa: E501 return self.templates.render(filename, *args, **kwargs) def template_string(self, source, *args, **kwargs): @@ -323,7 +324,7 @@ class API: :param source: The template to use. :param *args: Data to pass into the template. :param **kwargs: Data to pass into the template. - """ + """ # noqa: E501 return self.templates.render_string(source, *args, **kwargs) def serve(self, *, address=None, port=None, **options): @@ -334,11 +335,11 @@ class API: :param address: The address to bind to. :param port: The port to bind to. If none is provided, one will be selected at random. :param options: Additional keyword arguments to send to ``uvicorn.run()``. - """ + """ # noqa: E501 if "PORT" in os.environ: if address is None: - address = "0.0.0.0" + address = "0.0.0.0" # noqa: S104 port = int(os.environ["PORT"]) if address is None: diff --git a/responder/background.py b/responder/background.py index ca069b6..7f35da3 100644 --- a/responder/background.py +++ b/responder/background.py @@ -1,8 +1,8 @@ import asyncio -import functools import concurrent.futures import multiprocessing import traceback + from starlette.concurrency import run_in_threadpool @@ -27,7 +27,7 @@ class BackgroundQueue: def on_future_done(fs): try: fs.result() - except: + except Exception: traceback.print_exc() def do_task(*args, **kwargs): @@ -40,5 +40,4 @@ class BackgroundQueue: async def __call__(self, func, *args, **kwargs) -> None: if asyncio.iscoroutinefunction(func): return await asyncio.ensure_future(func(*args, **kwargs)) - else: - return await run_in_threadpool(func, *args, **kwargs) + return await run_in_threadpool(func, *args, **kwargs) diff --git a/responder/core.py b/responder/core.py index ac22195..8cbed85 100644 --- a/responder/core.py +++ b/responder/core.py @@ -1,2 +1,8 @@ from .api import API from .models import Request, Response + +__all__ = [ + "API", + "Request", + "Response", +] diff --git a/responder/ext/schema/__init__.py b/responder/ext/schema/__init__.py index e641ad6..0fa192a 100644 --- a/responder/ext/schema/__init__.py +++ b/responder/ext/schema/__init__.py @@ -7,8 +7,8 @@ import yaml from apispec import APISpec, yaml_utils from apispec.ext.marshmallow import MarshmallowPlugin -from responder.statics import DEFAULT_API_THEME from responder import status_codes +from responder.statics import DEFAULT_API_THEME class OpenAPISchema: @@ -21,7 +21,7 @@ class OpenAPISchema: description=None, terms_of_service=None, contact=None, - license=None, + license=None, # noqa: A002 openapi=None, openapi_route="/schema.yml", docs_route="/docs/", @@ -60,7 +60,6 @@ class OpenAPISchema: @property def _apispec(self): - info = {} if self.description is not None: info["description"] = self.description @@ -81,9 +80,7 @@ class OpenAPISchema: for route in self.app.router.routes: if route.description: - operations = yaml_utils.load_operations_from_docstring( - route.description - ) + operations = yaml_utils.load_operations_from_docstring(route.description) spec.path(path=route.route, operations=operations) for name, schema in self.schemas.items(): @@ -123,7 +120,6 @@ class OpenAPISchema: @property def docs(self): - loader = jinja2.PrefixLoader( { self.docs_theme: jinja2.PackageLoader( diff --git a/responder/formats.py b/responder/formats.py index 99ad77f..db6c8e9 100644 --- a/responder/formats.py +++ b/responder/formats.py @@ -9,10 +9,10 @@ from .models import QueryDict async def format_form(r, encode=False): if encode: - pass - elif "multipart/form-data" in r.headers.get("Content-Type"): + return None + if "multipart/form-data" in r.headers.get("Content-Type"): decode = decoder.MultipartDecoder(await r.content, r.mimetype) - querys = list() + queries = [] for part in decode.parts: header = part.headers.get(b"Content-Disposition").decode("utf-8") text = part.text @@ -22,63 +22,59 @@ async def format_form(r, encode=False): if len(split) > 1: key = split[1] key = key[1:-1] - querys.append((key, text)) + queries.append((key, text)) - content = urlencode(querys) + content = urlencode(queries) return QueryDict(content) - else: - return QueryDict(await r.text) + return QueryDict(await r.text) async def format_yaml(r, encode=False): if encode: r.headers.update({"Content-Type": "application/x-yaml"}) return yaml.safe_dump(r.media) - else: - return yaml.safe_load(await r.content) + return yaml.safe_load(await r.content) async def format_json(r, encode=False): if encode: r.headers.update({"Content-Type": "application/json"}) return json.dumps(r.media) - else: - return json.loads(await r.content) + return json.loads(await r.content) async def format_files(r, encode=False): if encode: - pass - else: - decoded = decoder.MultipartDecoder(await r.content, r.mimetype) - dump = {} - for part in decoded.parts: - header = part.headers[b"Content-Disposition"].decode("utf-8") - mimetype = part.headers.get(b"Content-Type", None) - filename = None + return None + decoded = decoder.MultipartDecoder(await r.content, r.mimetype) + dump = {} + for part in decoded.parts: + header = part.headers[b"Content-Disposition"].decode("utf-8") + mimetype = part.headers.get(b"Content-Type", None) + filename = None - for section in [h.strip() for h in header.split(";")]: - split = section.split("=") - if len(split) > 1: - key = split[0] - value = split[1] + for section in [h.strip() for h in header.split(";")]: + split = section.split("=") + if len(split) > 1: + key = split[0] + value = split[1] - value = value[1:-1] + value = value[1:-1] - if key == "filename": - filename = value - elif key == "name": - formname = value + if key == "filename": + filename = value + elif key == "name": + formname = value - if mimetype is None: - dump[formname] = part.content - else: - dump[formname] = { - "filename": filename, - "content": part.content, - "content-type": mimetype.decode("utf-8"), - } - return dump + if mimetype is None: + dump[formname] = part.content + else: + dump[formname] = { + "filename": filename, + "content": part.content, + "content-type": mimetype.decode("utf-8"), + } + return dump def get_formats(): diff --git a/responder/models.py b/responder/models.py index d78edb6..da72a70 100644 --- a/responder/models.py +++ b/responder/models.py @@ -1,22 +1,24 @@ import functools import inspect -from urllib.parse import parse_qs +import typing as t from http.cookies import SimpleCookie +from urllib.parse import parse_qs import chardet import rfc3986 - -from requests.structures import CaseInsensitiveDict from requests.cookies import RequestsCookieJar - -from starlette.requests import Request as StarletteRequest, State +from requests.structures import CaseInsensitiveDict +from starlette.requests import Request as StarletteRequest +from starlette.requests import State from starlette.responses import ( Response as StarletteResponse, +) +from starlette.responses import ( StreamingResponse as StarletteStreamingResponse, ) -from .status_codes import HTTP_301 from .statics import DEFAULT_ENCODING +from .status_codes import HTTP_301 class QueryDict(dict): @@ -206,6 +208,7 @@ class Request: async def declared_encoding(self): if "Encoding" in self.headers: return self.headers["Encoding"] + return None @property async def apparent_encoding(self): @@ -225,20 +228,20 @@ class Request: """Returns ``True`` if the incoming Request accepts the given ``content_type``.""" return content_type in self.headers.get("Accept", []) - async def media(self, format=None): + async def media(self, format: t.Union[str, t.Callable] = None): # noqa: A001, A002 """Renders incoming json/yaml/form data as Python objects. Must be awaited. - :param format: The name of the format being used. Alternatively accepts a custom callable for the format type. + :param format: The name of the format being used. + Alternatively, accepts a custom callable for the format type. """ if format is None: - format = "yaml" if "yaml" in self.mimetype or "" else "json" - format = "form" if "form" in self.mimetype or "" else format + format = "yaml" if "yaml" in self.mimetype or "" else "json" # noqa: A001 + format = "form" if "form" in self.mimetype or "" else format # noqa: A001 if format in self.formats: return await self.formats[format](self) - else: - return await format(self) + return await format(self) def content_setter(mimetype): @@ -276,11 +279,11 @@ class Response: self.content = None #: A bytes representation of the response body. self.mimetype = None self.encoding = DEFAULT_ENCODING - self.media = None #: A Python object that will be content-negotiated and sent back to the client. Typically, in JSON formatting. + self.media = None #: A Python object that will be content-negotiated and + #: sent back to the client. Typically, in JSON formatting. self._stream = None - self.headers = ( - {} - ) #: A Python dictionary of ``{key: value}``, representing the headers of the response. + self.headers = {} #: A Python dictionary of ``{key: value}``, + #: representing the headers of the response. self.formats = formats self.cookies = SimpleCookie() #: The cookies set in the Response self.session = ( @@ -316,9 +319,9 @@ class Response: content = content.encode(self.encoding) return (content, headers) - for format in self.formats: - if self.req.accepts(format): - return (await self.formats[format](self, encode=True)), {} + for format_ in self.formats: + if self.req.accepts(format_): + return (await self.formats[format_](self, encode=True)), {} # Default to JSON anyway. return ( diff --git a/responder/routes.py b/responder/routes.py index f6bbea8..7062808 100644 --- a/responder/routes.py +++ b/responder/routes.py @@ -1,18 +1,17 @@ import asyncio -import re import inspect +import re import traceback from collections import defaultdict -from starlette.middleware.wsgi import WSGIMiddleware -from starlette.websockets import WebSocket, WebSocketClose from starlette.concurrency import run_in_threadpool from starlette.exceptions import HTTPException +from starlette.middleware.wsgi import WSGIMiddleware +from starlette.websockets import WebSocket, WebSocketClose -from .models import Request, Response from . import status_codes from .formats import get_formats - +from .models import Request, Response _CONVERTORS = { "int": (int, r"\d+"), @@ -120,9 +119,9 @@ class Route(BaseRoute): try: view = getattr(endpoint, method_name) views.append(view) - except AttributeError: + except AttributeError as ex: if on_request is None: - raise HTTPException(status_code=status_codes.HTTP_405) + raise HTTPException(status_code=status_codes.HTTP_405) from ex else: views.append(self.endpoint) @@ -291,8 +290,9 @@ class Router: await websocket_close(receive, send) return + # FIXME: Please review! request = Request(scope, receive) - response = Response(request, formats=get_formats()) + response = Response(request, formats=get_formats()) # noqa: F841 raise HTTPException(status_code=status_codes.HTTP_404) diff --git a/responder/staticfiles.py b/responder/staticfiles.py index 28da854..c13f002 100644 --- a/responder/staticfiles.py +++ b/responder/staticfiles.py @@ -1,14 +1,16 @@ -import typing - -from starlette.staticfiles import StaticFiles +from starlette.staticfiles import StaticFiles as StarletteStaticFiles -class StaticFiles(StaticFiles): - """I've created an issue to disccuss allowing multiple directories in starletter's `StaticFiles`. +class StaticFiles(StarletteStaticFiles): + """ + Extension to Starlette's `StaticFiles`. + + I've created an issue to discuss allowing multiple directories in + Starlette's `StaticFiles`. https://github.com/encode/starlette/issues/625 - I've also made a PR to add this method to starlette StaticFiles + I've also made a PR to add this method to Starlette StaticFiles Once accepted we will remove this. https://github.com/encode/starlette/pull/626 diff --git a/responder/statics.py b/responder/statics.py index c10acfa..f73c80c 100644 --- a/responder/statics.py +++ b/responder/statics.py @@ -1,7 +1,7 @@ DEFAULT_ENCODING = "utf-8" DEFAULT_API_THEME = "swaggerui" DEFAULT_SESSION_COOKIE = "Responder-Session" -DEFAULT_SECRET_KEY = "NOTASECRET" +DEFAULT_SECRET_KEY = "NOTASECRET" # noqa: S105 DEFAULT_CORS_PARAMS = { "allow_origins": (), diff --git a/responder/templates.py b/responder/templates.py index 883f426..7c8f1da 100644 --- a/responder/templates.py +++ b/responder/templates.py @@ -10,7 +10,7 @@ class Templates: self.directory = directory self._env = jinja2.Environment( loader=jinja2.FileSystemLoader([str(self.directory)]), - autoescape=autoescape, + autoescape=autoescape, # noqa: S701 enable_async=enable_async, ) self.default_context = {} if context is None else {**context} @@ -33,7 +33,7 @@ class Templates: :param template: The filename of the jinja2 template. :param **kwargs: Data to pass into the template. :param **kwargs: Data to pass into the template. - """ + """ # noqa: E501 return self.get_template(template).render(*args, **kwargs) @contextmanager @@ -54,6 +54,6 @@ class Templates: :param source: The template to use. :param *args, **kwargs: Data to pass into the template. :param **kwargs: Data to pass into the template. - """ + """ # noqa: E501 template = self._env.from_string(source) return template.render(*args, **kwargs) diff --git a/setup.py b/setup.py index f0e9b9a..4be3dc9 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import os import sys from shutil import rmtree -from setuptools import find_packages, setup, Command +from setuptools import Command, find_packages, setup here = os.path.abspath(os.path.dirname(__file__)) @@ -120,7 +120,7 @@ setup( install_requires=required, extras_require={ "graphql": ["graphene"], - "test": ["pytest", "pytest-cov", "pytest-mock", "flask"] + "test": ["pytest", "pytest-cov", "pytest-mock", "flask"], }, include_package_data=True, license="Apache 2.0", diff --git a/tests/conftest.py b/tests/conftest.py index b38fb30..74addee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ -import responder from pathlib import Path + import pytest -import multiprocessing -import concurrent.futures + +import responder @pytest.fixture diff --git a/tests/test_encodings.py b/tests/test_encodings.py index 244cb83..976de02 100644 --- a/tests/test_encodings.py +++ b/tests/test_encodings.py @@ -1,6 +1,3 @@ -import pytest - - def test_custom_encoding(api, session): data = "hi alex!" diff --git a/tests/test_models.py b/tests/test_models.py index 76e8464..35a6e20 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,9 +1,9 @@ import inspect + import pytest from responder import models - _default_query = "q=%7b%20hello%20%7d&name=myname&user_name=test_user" diff --git a/tests/test_responder.py b/tests/test_responder.py index 1f090e7..b61c266 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -1,16 +1,15 @@ -import pytest -import yaml import random -import responder import string - -from responder.routes import Route, WebSocketRoute -from responder.templates import Templates - +import pytest +import yaml from starlette.middleware.base import BaseHTTPMiddleware from starlette.testclient import TestClient as StarletteTestClient +import responder +from responder.routes import Route, WebSocketRoute +from responder.templates import Templates + def test_api_basic_route(api): @api.route("/") @@ -133,7 +132,7 @@ def test_yaml_media(api): r = api.requests.get("http://;/", headers={"Accept": "yaml"}) assert "yaml" in r.headers["Content-Type"] - assert yaml.load(r.content, Loader=yaml.FullLoader) == dump + assert yaml.load(r.content, Loader=yaml.FullLoader) == dump # noqa: S506 def test_argumented_routing(api): @@ -305,9 +304,7 @@ def test_json_downloads(api): def route(req, resp): resp.media = dump - r = api.requests.get( - api.url_for(route), headers={"Content-Type": "application/json"} - ) + r = api.requests.get(api.url_for(route), headers={"Content-Type": "application/json"}) assert r.json() == dump @@ -325,9 +322,10 @@ def test_yaml_downloads(api): def test_schema_generation_explicit(): + import marshmallow + import responder from responder.ext.schema import OpenAPISchema as OpenAPISchema - import marshmallow api = responder.API() @@ -359,9 +357,10 @@ def test_schema_generation_explicit(): def test_schema_generation(): - import responder from marshmallow import Schema, fields + import responder + api = responder.API(title="Web Service", openapi="3.0.2") @api.schema("Pet") @@ -390,11 +389,11 @@ def test_schema_generation(): def test_documentation_explicit(): + import marshmallow + import responder from responder.ext.schema import OpenAPISchema as OpenAPISchema - import marshmallow - description = "This is a sample server for a pet store." terms_of_service = "http://example.com/terms/" contact = { @@ -402,7 +401,7 @@ def test_documentation_explicit(): "url": "http://www.example.com/support", "email": "support@example.com", } - license = { + license_ = { "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html", } @@ -418,7 +417,7 @@ def test_documentation_explicit(): description=description, terms_of_service=terms_of_service, contact=contact, - license=license, + license=license_, ) @schema.schema("Pet") @@ -444,9 +443,10 @@ def test_documentation_explicit(): def test_documentation(): - import responder from marshmallow import Schema, fields + import responder + description = "This is a sample server for a pet store." terms_of_service = "http://example.com/terms/" contact = { @@ -454,7 +454,7 @@ def test_documentation(): "url": "http://www.example.com/support", "email": "support@example.com", } - license = { + license_ = { "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html", } @@ -467,7 +467,7 @@ def test_documentation(): description=description, terms_of_service=terms_of_service, contact=contact, - license=license, + license=license_, allowed_hosts=["testserver", ";"], ) @@ -551,8 +551,7 @@ def test_sessions(api): r = api.requests.get(api.url_for(view)) assert ( - r.cookies[api.session_cookie] - == '{"hello": "world"}.r3EB04hEEyLYIJaAXCEq3d4YEbs' + r.cookies[api.session_cookie] == '{"hello": "world"}.r3EB04hEEyLYIJaAXCEq3d4YEbs' ) assert r.json() == {"hello": "world"} @@ -602,7 +601,6 @@ def test_template_async(api, template_path): def test_file_uploads(api): @api.route("/") async def upload(req, resp): - files = await req.media("files") result = {} result["hello"] = files["hello"]["content"].decode("utf-8") @@ -645,8 +643,8 @@ def test_websockets_text(api): await ws.close() client = StarletteTestClient(api) - with client.websocket_connect("ws://;/ws") as websocket: - data = websocket.receive_text() + with client.websocket_connect("ws://;/ws") as ws: + data = ws.receive_text() assert data == payload @@ -660,8 +658,8 @@ def test_websockets_bytes(api): await ws.close() client = StarletteTestClient(api) - with client.websocket_connect("ws://;/ws") as websocket: - data = websocket.receive_bytes() + with client.websocket_connect("ws://;/ws") as ws: + data = ws.receive_bytes() assert data == payload @@ -675,8 +673,8 @@ def test_websockets_json(api): await ws.close() client = StarletteTestClient(api) - with client.websocket_connect("ws://;/ws") as websocket: - data = websocket.receive_json() + with client.websocket_connect("ws://;/ws") as ws: + data = ws.receive_json() assert data == payload @@ -694,10 +692,10 @@ def test_before_websockets(api): await ws.send_json({"before": "request"}) client = StarletteTestClient(api) - with client.websocket_connect("ws://;/ws") as websocket: - data = websocket.receive_json() + with client.websocket_connect("ws://;/ws") as ws: + data = ws.receive_json() assert data == {"before": "request"} - data = websocket.receive_json() + data = ws.receive_json() assert data == payload @@ -713,7 +711,7 @@ def test_startup(api): who[0] = "world" with api.requests as session: - r = session.get(f"http://;/hello") + r = session.get("http://;/hello") assert r.text == "hello, world!" @@ -731,16 +729,16 @@ def test_redirects(api, session): def test_session_thoroughly(api, session): @api.route("/set") - def set(req, resp): + def setter(req, resp): resp.session["hello"] = "world" api.redirect(resp, location="/get") @api.route("/get") - def get(req, resp): + def getter(req, resp): resp.media = {"session": req.session} - r = session.get(api.url_for(set)) - r = session.get(api.url_for(get)) + r = session.get(api.url_for(setter)) + r = session.get(api.url_for(getter)) assert r.json() == {"session": {"hello": "world"}} @@ -815,9 +813,9 @@ def test_allowed_hosts(enable_hsts, cors): def create_asset(static_dir, name=None, parent_dir=None): if name is None: - name = random.choices(string.ascii_letters, k=6) + name = random.choices(string.ascii_letters, k=6) # noqa: S311 # :3 - ext = random.choices(string.ascii_letters, k=2) + ext = random.choices(string.ascii_letters, k=2) # noqa: S311 name = f"{name}.{ext}" if parent_dir is None: @@ -881,7 +879,7 @@ def test_staticfiles_none_dir(tmpdir): assert r.status_code == api.status_codes.HTTP_404 # SPA - with pytest.raises(Exception) as excinfo: + with pytest.raises(Exception): # noqa: B017 api.add_route("/spa", static=True) @@ -918,7 +916,6 @@ def test_stream(api, session): @api.route("/{who}") async def greeting(req, resp, *, who): - resp.stream(shout_stream, who) r = session.get("/morocco") @@ -966,8 +963,7 @@ def test_empty_req_text(api): request.state.test1 = 42 request.state.test2 = "Foo" - response = await call_next(request) - return response + return await call_next(request) api.add_middleware(StateMiddleware) diff --git a/tests/test_status_codes.py b/tests/test_status_codes.py index 7608851..658ca09 100644 --- a/tests/test_status_codes.py +++ b/tests/test_status_codes.py @@ -1,4 +1,5 @@ import pytest + from responder import status_codes