sanic-org / sanic-ext Goto Github PK
View Code? Open in Web Editor NEWExtended Sanic functionality
Home Page: https://sanic.dev/en/plugins/sanic-ext/getting-started.html
License: MIT License
Extended Sanic functionality
Home Page: https://sanic.dev/en/plugins/sanic-ext/getting-started.html
License: MIT License
This is really a good idea
It feels more like something we should add to sanic-ext
.
Originally posted by @ahopkins in sanic-org/sanic#2233 (comment)
Is your feature request related to a problem? Please describe your use case.
I want to give my openapi.yaml
to swagger ui
and find that i can't do this by modify config options(UI | Sanic Framework).
I find in
it only support modify prefix and can't modify suffix openapi.json
.
Describe the solution you'd like
Maybe in
can beurl: "__URL_PREFIX__/__OPENAPI_FILE__",
And i can set OAS_OPENAPI_FILE=src/resource/openapi.yaml
to make swagger ui default load my openapi.yaml
Additional context
Describe the bug
In my Sanic application, I declare all my parameters once as they are used accross many endpoints. With last sanic-ext, I have this error:
AttributeError: module 'sanic_ext.extensions.openapi.openapi' has no attribute 'Parameter'. Did you mean: 'parameter'?
To Reproduce
Simple code to reproduce
from sanic import Sanic
from sanic.response import text
from sanic_ext import openapi
app = Sanic("MyHelloWorldApp")
my_parameter = openapi.Parameter()
@app.get("/test1")
@openapi.parameter(my_parameter)
async def test1(request):
return text("test1")
@app.get("/test2")
@openapi.parameter(my_parameter)
async def test2(request):
return text("test2")
Additional context
If this is expected behaviour, please fix the doc also as this page states
@openapi.parameter(parameter=Parameter("foobar", deprecated=True))
Edit: broken change was introduced w/ 22.1.0
Describe the bug
Setting OAS_URL_PREFIX to anything other than /docs causes redoc and swagger to fail, because the html looks for /docs/openapi.json (and swagger-config for swagger)
To Reproduce
Initialize Sanic-ext like this:
cfg = Config(oas_ui_default="swagger", oas_url_prefix='/swagger')
Extend(app, config=cfg)
Expected behavior
Swagger UI functions at hostname/swagger
Environment (please complete the following information):
Additional context
This is fixed in a branch in my fork. Let me know if you want a PR. I also fixed / enhanced with the ability to use the root as the openapi endpoint. Tested with redoc and swagger with the default of no prefix set (so default of /docs) and url prefixes ['/docs', '/swagger', '']. I only tested with the base of my installed sanic, which is 21.9.1
rocknet@ec1f277
Describe the bug
I tried to use the sanic-ext validation to validate a complex json body without pydantic, and occour error. Using the pydantic model works.
To Reproduce
Validate like this example of models, using built-in list keyword:
@dataclass
class User:
id: int
name: str
friends: list[Friend]
@dataclass
class Friend:
name: str
Expected behavior
Validate normally.
Environment (please complete the following information):
Additional context
Like @ahopkins said, maybe this is a bug related to 'list' keyword that is builtin in more recent Python versions. Using the List type imported of typing module appears to work as expected.
https://discord.com/channels/812221182594121728/872787982741032971/898279553288523807
Describe the bug
I'm following this tutorial https://sanic.dev/en/plugins/sanic-ext/validation.html#implementation .
with define dataclass
attribute with optional paramater,
it will return 400 , if the query params q
have value ex q=123123
without value it will work without problem
I also found the same issue when using pydantic
To Reproduce
from dataclasses import dataclass, asdict
from typing import Optional
from sanic import Sanic
from sanic.response import json
from sanic_ext import validate
app = Sanic("Sanic-APP")
@dataclass
class SearchParams:
q: Optional[str] = None
@app.route("/")
@validate(query=SearchParams)
async def handler(request, query: SearchParams):
return json(asdict(query))
Expected behavior
when the dataclass attribute is Optional
, it should not error when we send the query params with value ex ?q=123456
Environment (please complete the following information):
Describe the bug
When accessing the /docs/swagger
route, a blank page is loaded. It seems to be due to the defined stylesheets no longer existing.
To Reproduce
Load sanic-ext
into your Sanic app and access the Swagger UI route.
Expected behavior
The Swagger UI is displayed.
Environment (please complete the following information):
The current circular detection algorithm is flawed in that it does not account for the following, which should be allowable.
class A:
...
class B:
def __init__(self, a: A):
self.a = a
class C:
def __init__(self, a: A):
self.a = a
class D:
def __init__(self, b: B, c: C):
self.b = b
self.c = c
def make_a(_):
return A()
def make_b(_, a: A):
return B(a)
def make_c(_, a: A):
return C(a)
def make_d(_, b: B, c: C):
return D(b, c)
app.ext.add_dependency(A, make_a)
app.ext.add_dependency(B, make_b)
app.ext.add_dependency(C, make_c)
app.ext.add_dependency(D, make_d)
Hello. I make class views dynamically from SA models for REST api. And in ReDoc all summary for methods like last class.
Class view generate like this:
....................
views.append(type(f"VI_{name}",
(HTTPMethodView,),
{'model': model,
'_url': f'/{name}/<id:int>',
'get': op.summary(f"Get obj {name}")(viewItem_get),
'put': op.summary(f"Update obj {name}")(viewItem_put),
'patch': op.summary(f"Change obj {name}")(viewItem_patch),
'delete': op.summary(f"Delete obj {name}")(viewItem_delete)}))
.........................
for view in views:
blueprint.add_route(view.as_view(), view._url)
I find way to generate custom summary and all openapi fields for every my dynamic class-view.
I use Manjaro + firefox. Sanic v22.3.1 and sanic-ext==22.3.1
I try deepcopy, copy all objects and copy func-methods for set unique names for every gen but it not help all.
Describe the bug
set template search pathes via
app.config.TEMPLATING_PATH_TO_TEMPLATES = xxx
# OR
app.ext.config.TEMPLATING_PATH_TO_TEMPLATES = xxx
not working, sanic just use default templates
dir
exception log
Traceback (most recent call last):
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 843, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/Users/chendx/xxx/server/__init__.py", line 1, in <module>
from .app import PigaiZiApp
File "/Users/chendx/xxx/server/app.py", line 2, in <module>
from .demo_handler import *
File "/Users/chendx/xxx/server/demo_handler.py", line 8, in <module>
@app.ext.template('demo.jinja2')
File "/Users/chendx/anaconda3/envs/pigai/lib/python3.8/site-packages/sanic_ext/bootstrap.py", line 151, in template
return self.templating.template(template_name, **kwargs)
File "/Users/chendx/anaconda3/envs/pigai/lib/python3.8/site-packages/sanic_ext/extensions/templating/engine.py", line 30, in template
template = self.environment.get_template(file_name)
File "/Users/chendx/anaconda3/envs/pigai/lib/python3.8/site-packages/jinja2/environment.py", line 883, in get_template
return self._load_template(name, self.make_globals(globals))
File "/Users/chendx/anaconda3/envs/pigai/lib/python3.8/site-packages/jinja2/environment.py", line 857, in _load_template
template = self.loader.load(self, name, globals)
File "/Users/chendx/anaconda3/envs/pigai/lib/python3.8/site-packages/jinja2/loaders.py", line 115, in load
source, filename, uptodate = self.get_source(environment, name)
File "/Users/chendx/anaconda3/envs/pigai/lib/python3.8/site-packages/jinja2/loaders.py", line 197, in get_source
raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: demo.jinja2
Additional context
I check the sanic_ext source code, before custom config been set, the template extend has been inited.
The swagger page generated by openapi extension depends on pagecdn.io/lib/swagger-ui/latest/
. My application will be deployed in an environment where it will not be possible to resolve this dependency at runtime.
Please make the source for these resources configurable with pagecdn.io/lib/swagger-ui/latest/
as default. This will allow me to serve them from the application itself or a server nearby.
Describe the bug
When using Parameter class to describe a method parameter, the description is not included in the resulting openapi.json
To reproduce
The following code tests 3 ways of documenting parameters, the output is almost identical except that test2 is missing the description in the output
@app.route('/test1/<val1>')
async def get_test1(request, val1: int):
"""
openapi:
---
operationId: get.test1
parameters:
- name: val1
in: path
description: val1 path param
required: true
schema:
type: integer
format: int32
"""
return HTTPResponse("ok")
@app.route('/test2/<val1>')
@openapi.parameter(parameter=Parameter(name='val1', schema=int, location='path', description='val1 path param'))
async def get_test2(request, val1: int):
return HTTPResponse("ok")
@app.route('/test3/<val1>')
@openapi.parameter('val1', description='val1 path param', required=True, schema=int)
async def get_test3(request, val1: int):
return HTTPResponse("ok")
Expected behavior
The description part of Parameter should be included in the openapi.json output
Environment (please complete the following information):
OS: Windows 10
Python: 3.10
Sanic: 21.9.3
sanic-ext: 21.9.3
How can I use openapi with class based views? Doc section for that is empty.
Is your feature request related to a problem? Please describe your use case.
Currently the config value for API_SCHEMES
is interpreted as-is but is used as an iterable, which makes it hard to use when it is defined using environment variables, as these are always strings.
Describe the solution you'd like
It would be nice if, in cases in which it is a string, it would split the string automatically (maybe in ,
) to convert it into a list.
Additional context
N/A.
#from pydantic.dataclasses import dataclass
from pydantic.dataclasses import dataclass
@dataclass
class RegisterBody:
name: str
email: str
password: str
@validator('name')
def validate_name(cls, v):
if len(v) < 5:
raise ValueError('name must be at leasth 5 characters')
@app.post("/register")
@openapi.body({"application/json": RegisterBody})
@validate(json=RegisterBody)
async def register(request, body):
return json({})
The code above produces the OpenAPI /docs
page to populate a validator_name
field in the Swagger UI, while for the actual mode it is not a field. Tried with the builting python dataclasses and the Pydantic wrapper too.
Sanic v22.3.2
Sanic-ext v22.3.2
Arch Linux x86_64
Python 3.10.4
Describe the bug
Using the openapi.definition
decorator to set tag
raises an exception.
To Reproduce
Use the decorator like so:
@openapi.definition(
tag="Enterprises",
)
Expected behavior
The tag is set for the route just like when using @openapi.tag
.
Environment (please complete the following information):
Describe the bug
TypeError: build_spec() missing 1 required positional argument: 'loop'
upon running the serverCode snippet
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/sanic/app.py", line 1140, in _listener
maybe_coro = listener(app) # type: ignore
TypeError: build_spec() missing 1 required positional argument: 'loop'
Additonal Logs
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/usr/local/lib/python3.9/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python3.9/site-packages/sanic/server/runners.py", line 130, in serve
loop.run_until_complete(app._server_event("init", "before"))
File "uvloop/loop.pyx", line 1501, in uvloop.loop.Loop.run_until_complete
File "/usr/local/lib/python3.9/site-packages/sanic/app.py", line 1581, in _server_event
await self.dispatch(
File "/usr/local/lib/python3.9/site-packages/sanic/signals.py", line 193, in dispatch
return await dispatch
File "/usr/local/lib/python3.9/site-packages/sanic/signals.py", line 163, in _dispatch
retval = await maybe_coroutine
File "/usr/local/lib/python3.9/site-packages/sanic/app.py", line 1142, in _listener
maybe_coro = listener(app, loop) # type: ignore
File "/usr/local/lib/python3.9/site-packages/sanic_ext/extensions/openapi/blueprint.py", line 142, in build_spec
operation.parameter(
File "/usr/local/lib/python3.9/site-packages/sanic_ext/extensions/openapi/builders.py", line 99, in parameter
Parameter.make(name, schema, location, **kwargs)
File "/usr/local/lib/python3.9/site-packages/sanic_ext/extensions/openapi/definitions.py", line 236, in make
return Parameter(name, Schema.make(schema), location, **kwargs)
File "/usr/local/lib/python3.9/site-packages/sanic_ext/extensions/openapi/types.py", line 182, in make
return Object.make(value, **kwargs)
File "/usr/local/lib/python3.9/site-packages/sanic_ext/extensions/openapi/types.py", line 279, in make
{k: Schema.make(v) for k, v in _properties(value).items()},
File "/usr/local/lib/python3.9/site-packages/sanic_ext/extensions/openapi/types.py", line 319, in _properties
for k, v in {**get_type_hints(cls), **fields}.items()
File "/usr/local/lib/python3.9/typing.py", line 1454, in get_type_hints
for name, value in ann.items():
AttributeError: 'getset_descriptor' object has no attribute 'items'
Environment (please complete the following information):
Additional context
Without sanic-ext package, it works fine but then I have CORS issues.
Describe the bug
Authorization header specification using @openapi.parameter is not included Swagger UI request.
The UI shows the Input field, but the CURL example does not include the header and the request made does not include the header either.
Screenshots
The screenshots of Swagger UI if this bug is related to it.
To Reproduce
from sanic.response import text
from sanic_ext import Extend
from sanic_ext.extensions.openapi import openapi
app = Sanic("MyHelloWorldApp")
Extend(app)
@app.get("/")
@openapi.parameter("Authorization", str, "header", required=True)
async def hello_world(request):
return text("Hello, world.")
if __name__ == "__main__":
app.run(
host="0.0.0.0",
port=8002,
auto_reload=True,
)
Expected behavior
I expect receive
curl -X 'GET' \
'http://localhost:8002/ \
-H 'accept: application/json' \
-H 'Authorization: token123'
Environment:
Describe the bug
Sanic-Ext fails to inject dependencies to @app.websocket()
handlers.
Screenshots
To Reproduce
Try this code:
from sanic import Sanic
app = Sanic('test')
class Foo:
pass
app.ext.add_dependency(Foo, lambda req: None)
@app.websocket('/foo')
async def handler(req, ws, foo: Foo):
pass
app.run(port=4321, debug=True)
Visit http://127.0.0.1:4321/
, then execute new WebSocket('ws://127.0.0.1:4321/foo')
in browser console.
This error will be thrown:
Traceback (most recent call last):
File "handle_request", line 83, in handle_request
)
File "C:\Users\xmcp\AppData\Roaming\Python\Python38\site-packages\sanic\app.py", line 1002, in _websocket_handler
fut = ensure_future(handler(request, ws, *args, **kwargs))
TypeError: handler() missing 1 required positional argument: 'foo'
Expected behavior
It should not throw an error.
Environment (please complete the following information):
Additional context
Sanic wraps a functools.partial
to the websocket handler, effectively erasing the type signature of the handler function. Therefore sanic_ext.extensions.injection.injector
(screenshot below) cannot get its type hint:
Describe the bug
On an OPTIONS HTTP request, no Access-Control headers is returned if the Origin is set
To Reproduce
Simple "Hello world" server from the documentation pages
from sanic import Sanic
from sanic.response import text
from sanic_ext import Extend
from sanic_cors import CORS
app = Sanic("MyHelloWorldApp")
Extend(app)
app.config.CORS_ORIGINS = "*"
@app.get("/")
async def hello_world(request):
return text("Hello, world.")
app.run()
On the client side:
[dalexandre@dalexandre ~]$ http OPTIONS :8000 Access-Control-Request-Method:GET
HTTP/1.1 204 No Content
access-control-allow-headers: *
access-control-allow-methods: HEAD,OPTIONS,GET
access-control-allow-origin: *
access-control-max-age: 5
allow: HEAD,GET,OPTIONS
connection: keep-alive
[dalexandre@dalexandre ~]$ http OPTIONS :8000 Access-Control-Request-Method:GET Origin:test
HTTP/1.1 204 No Content
allow: HEAD,GET,OPTIONS
connection: keep-alive
Expected behavior
Headers have to be returned wether or not the Origin header is set
Environment (please complete the following information):
Sanic 21.9.3 + Sanic-ext 22.1.2
Describe the bug
When making a dataclass with py 3.10 optional/union syntax, I get a stack trace on server start. I did work around the issue by converting to Optional[str]
.
To Reproduce
@dataclass
class Comment:
name: str
# Note py 3.10 syntax here
comment: str | None
@api_bp.post("/foo")
@validate(json=Comment)
async def stock_buy(request: Request):
pass
File "$ENV/lib/python3.10/site-packages/sanic_ext/extras/validation/decorator.py", line 20, in validate
schemas = {
File "$ENV/lib/python3.10/site-packages/sanic_ext/extras/validation/decorator.py", line 21, in <dictcomp>
key: generate_schema(param)
File "$ENV/lib/python3.10/site-packages/sanic_ext/extras/validation/setup.py", line 60, in generate_schema
return make_schema({}, param) if isclass(param) else param
File "$ENV/lib/python3.10/site-packages/sanic_ext/extras/validation/schema.py", line 37, in make_schema
make_schema(agg, hint.hint)
File "$ENV/lib/python3.10/site-packages/sanic_ext/extras/validation/schema.py", line 27, in make_schema
elif item.__name__ not in agg and is_dataclass(item):
AttributeError: 'types.UnionType' object has no attribute '__name__'. Did you mean: '__ne__'?
Expected behavior
No stacky boi 😄
Environment (please complete the following information):
sanic[ext]==21.12.1
# via -r requirements.in
sanic-ext==22.1.2
# via sanic
sanic-routing==0.7.2
# via sanic
Additional context
Optional[str]
works for now!
Describe the bug
If multiple route handlers have the same name, sanic-ext will treat them as one route during dependency injection, causing a HTTP 500 because passed kwargs
does not correspond to the function signature.
Screenshots
To Reproduce
Consider this code:
from sanic import Sanic, response
app = Sanic('test')
class Object:
pass
app.ext.add_dependency(Object, lambda req: None)
@app.route('/foo')
def handler(req, foo: Object):
return response.text('foo')
@app.route('/bar')
def handler(req, bar: Object):
return response.text('bar')
app.run(debug=True)
Then visit /foo
. It shows a HTTP 500 error:
Traceback (most recent call last):
File "handle_request", line 81, in handle_request
error_logger,
TypeError: handler() got an unexpected keyword argument 'bar'
Expected behavior
I'm not sure whether it is discouraged to use a same name (handler
in the example above) across different route handlers.
I believe sanic-ext should either support this or warn users about this caveat.
Environment (please complete the following information):
Additional context
Maybe the signature_registry
should use something else as the key.
Describe the bug
Class / static methods are included in the example request body.
To Reproduce
Create a dataclass add a class/static method to it and set it to a requestbody with @openapi.body({"application/json": Class})
Expected behavior
We pass an instance of a class so class and static methods should not be in the documentation.
Environment (please complete the following information):
Describe the bug
cdn cdnjs.cloudflare.com 的地址,能否支持自己配置?因为有时候国内第一次访问不通,需要开VPN访问之后缓存一下才行,很不友好啊
Screenshots
国内第一次访问时,一直pending,场景率很高
Describe the bug
When defining a parameter and using a type such as openapi.Integer()
, typing checks fail because there's no available overload that matches.
To Reproduce
from sanic_ext import openapi
...
@app.route("/")
@openapi.parameter("scope", openapi.Integer())
async def do_stuff(*_, **_):
...
Expected behavior
There's no typing errors.
Environment (please complete the following information):
Additional context
N/A
Describe the bug
Starting two instances of sanic (on different ports) using create_server
with different routes assigned to them. The API specification will merge all routes together
Expected behavior
The specification builder should use the request.app
to determine which routes are applicable for the specification to be used, and only show those routes.
Additional context
As seen here The specification is built from a singleton, if this is in the same heap as the main application then all routes are merged together.
Describe the bug
While using the @validate
decorator on a class-based view, I get the following exception:
[...]python/site-packages/sanic_ext/extras/validation/decorator.py", line 39, in decorated_function
data=request.json,
AttributeError: 'SomeResource' object has no attribute 'json'
And this is how my Class Based view look like:
class SomeResource(HTTPMethodView):
@validate(json=ResourceSchema)
async def post(self, request):
pass
Additional Notes
Diving quickly into the code of the decorator, it seems like we are not handling the class case of the views, where the first argument of the method is the class instance and not the request instance (like in the function-based view):
async def decorated_function(request: Request, *args, **kwargs):
I think a solution for that can be to use the kwargs rather than the position, but I am not too familiar with the codebase just yet, so there might be a more suitable solution for this repo.
Describe the bug
Pydantic models have a config member that is obviously not meant included in a request.
As of sanic-ext 22.6.1 the vanilla for manually defined pydantic models it is left out. However when using tortoise-orm's pydantic_model_creator to generate the models, the Config field gets left in and special objects like enums are ignored.
Screenshots
pydantic_model_creator
To Reproduce
pydantic model creator
from tortoise.contrib.pydantic import pydantic_model_creator
from models import Customer
model = pydantic_model_creator(Customer, name="customer_in", exclude_readonly=True)
from pydantic import BaseModel
from models import Gender
class CustomerIn(BaseModel):
name: str
email: str
gender: Gender
....
model = CustomerIn
Attach them both to a route and see the differences.
Expected behavior
These two methods need to provide the same outcome.
Environment (please complete the following information):
Additional context
I'm using the latest stable pydantic and tortoise-orm versions from pypi.
Describe the bug
Missed the possibility to point out allowed values of path or query string parameters in sanic-ext openapi decorator.
I remember it was possible when openapi docs were settled with @doc
decorator. But in @openapi.parameter
I don't see any possible keys to set such values. Key kwargs
decieves me in this case.
Expected behavior
For example I have a parameter which shall accept an int
in range from 0
to 2
.
I'd like to point it out in swagger and to make this field accept not any input, but only preset choices: 0
, 1
and 2
.
So I expect to find in @openapi.parameter
a key like allowed_values
or something like that:
@openapi.parameter(..., allowed_values=list(range(3)))
Additional question
Is it possible to make swagger fields to be dependant from the value set in other fields?
For example I have a field device
with allowed values screen
, phone
, tablet
and a field size
.
Each kind of device has its correspondent range of sizes, e.g.: screens: 15''-40'', phone: 5''-8'', tablets: 9''-15''.
Depending on the device
value field size
shall allow in swagger to choose any size from the correspondent range.
Is it possible?
Nowadays, the Sanic OpenAPI extension does not permit to add example requests and responses to your documented views.
I suggest to add a field example
to definitions.Response, definitions.RequestBody and definitions.Parameter to support OpenAPI examples.
The class definitions.Components references to OpenAPI examples -I guess-, but is unused. Maybe it's a WIP.
I don't know if it's on the roadmap or actually documented (since I read carefully the documentation and the code and I didn't find any traces of this feature, besides in this class) but it could be a cool feature. The thing that, personally, I miss the most about the OpenAPI standard.
I could open a PR too, if need. I'd be glad to contribute to Sanic Extensions. Just need an approval that what I suggest makes sense!
Thanks ❤️
The openapi.definition decorator with a definitions.RequestBody instance or a non-empty dictionary as the body
parameter transforms the target function to a coroutine.
For example, in my case, I'm using the decorator in a sync view...
@bp.get("/top/tracks")
@openapi.definition(
body=oa.RequestBody(serializers.TopTracksRequest, required=True),
summary="Fetch top tracks",
response=[
oa.Response(serializers.TopTracksResponse),
oa.Response(
Error, status=404, description="not found. note: sync is required."
),
oa.Response(Error, status=400, description="bad request"),
],
)
@authenticate
def fetch_top_tracks(request):
...
... that uses the Django ORM to retrieve data, and the only way I have to avoid the Django async safety check is to remove the definitions.RequestBody definition as a body
(or perform an ugly workaround).
The code block that triggers the view actually detects the function as a coroutine and calls it as such
When I remove the body
parameter, everything works as expected and the view is not triggered as a coroutine.
@bp.get("/top/tracks")
@openapi.definition(
#body=oa.RequestBody(serializers.TopTracksRequest, required=True),
summary="Fetch top tracks",
response=[
oa.Response(serializers.TopTracksResponse),
oa.Response(
Error, status=404, description="not found. note: sync is required."
),
oa.Response(Error, status=400, description="bad request"),
],
)
@authenticate
def fetch_top_tracks(request):
...
I don't know if this behaviour is expected, my guess is that it has something to do with this check. Maybe it could be useful to add fully sync context support to this decorator, or add a flag to avoid explicitly this kind of behaviour.
Maybe it's totally expected, I'm missing the reasoning behind it and the body
parameter is not expected to be used in a sync context (or needs some unknown -by me- configuration).
Anyway, if the behaviour is expected, I would love to know the reasoning behind it. Also, it could be useful to have it in the documentation since I couldn't understand what was happening easily. If it's already in the documentation and I couldn't find it; Send me a link! 😉
Thanks a lot!
Loving Sanic ❤️
Describe the bug
When setting a TypedDict
as a response value, if it has fields marked as Optional
, they will be displayed as type object
.
To Reproduce
from typing import TypedDict
class AutonomousSystem(TypedDict):
asn: str
description: Optional[str]
organisation: Optional[str]
company: Optional[str]
scope: int
@openapi.response(200, {"application/json": List[AutonomousSystem]})
Expected behavior
The field is shown as nullable
, not object
.
Environment (please complete the following information):
Dataclass Optional List fails with TypeError: Value '<factory>' must be a typing.List[str]
#! /usr/bin/env python3
import json
from dataclasses import field, dataclass
from http import HTTPStatus
from typing import Optional, List
from sanic import Sanic
from sanic.response import empty
from sanic_ext import validate
app = Sanic(name='name')
@dataclass
class SearchRequest:
names: Optional[List[str]] = field(default_factory=list)
# Proving that the dataclass is valid.
SearchRequest()
SearchRequest(names=['foo', 'bar'])
@app.post('/search')
@validate(json=SearchRequest)
def search(_, body: SearchRequest):
print(body.names)
return empty()
# This request succeeds with 204.
_, response = app.test_client.post('/search', content=json.dumps({'names': ['foo', 'bar']}))
assert response.status_code == HTTPStatus.NO_CONTENT, response.status
# This request fails with 400.
_, response = app.test_client.post('/search', content=json.dumps({}))
assert response.status_code == HTTPStatus.NO_CONTENT, response.status
[2022-01-13 09:19:30 -0700] [8488] [INFO]
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Sanic v21.12.1 │
│ Goin' Fast @ ('127.0.0.1', 50961) http://... │
├───────────────────────┬──────────────────────────────────────────────────────────────────────────┤
│ │ mode: production, single worker │
│ ▄███ █████ ██ │ server: sanic │
│ ██ │ python: 3.9.5 │
│ ▀███████ ███▄ │ platform: Linux-4.19.0-18-amd64-x86_64-with-glibc2.28 │
│ ██ │ packages: sanic-routing==0.7.2, sanic-testing==0.8.2, sanic-ext==21.12.1 │
│ ████ ████████▀ │ │
│ │ │
│ Build Fast. Run Fast. │ │
└───────────────────────┴──────────────────────────────────────────────────────────────────────────┘
[2022-01-13 09:19:30 -0700] [8488] [WARNING] Sanic is running in PRODUCTION mode. Consider using '--debug' or '--dev' while actively developing your application.
[2022-01-13 09:19:30 -0700] [8488] [INFO] Sanic Extensions:
[2022-01-13 09:19:30 -0700] [8488] [INFO] > http
[2022-01-13 09:19:30 -0700] [8488] [INFO] > openapi [http://('127.0.0.1', 50961) http://.../docs]
[2022-01-13 09:19:30 -0700] [8488] [INFO] > injection [0]
[2022-01-13 09:19:30 -0700] [8488] [INFO] http://127.0.0.1:50961/search
['foo', 'bar']
[2022-01-13 09:19:30 -0700] - (sanic.access)[INFO][127.0.0.1:59530]: POST http://127.0.0.1:50961/search 204 0
[2022-01-13 09:19:30 -0700] [8488] [INFO] Starting worker [8488]
[2022-01-13 09:19:30 -0700] [8488] [INFO] Stopping worker [8488]
[2022-01-13 09:19:30 -0700] [8488] [INFO] Server Stopped
[2022-01-13 09:19:30 -0700] [8488] [INFO]
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Sanic v21.12.1 │
│ Goin' Fast @ http://127.0.0.1:50961 │
├───────────────────────┬──────────────────────────────────────────────────────────────────────────┤
│ │ mode: production, single worker │
│ ▄███ █████ ██ │ server: sanic │
│ ██ │ python: 3.9.5 │
│ ▀███████ ███▄ │ platform: Linux-4.19.0-18-amd64-x86_64-with-glibc2.28 │
│ ██ │ packages: sanic-routing==0.7.2, sanic-testing==0.8.2, sanic-ext==21.12.1 │
│ ████ ████████▀ │ │
│ │ │
│ Build Fast. Run Fast. │ │
└───────────────────────┴──────────────────────────────────────────────────────────────────────────┘
[2022-01-13 09:19:30 -0700] [8488] [WARNING] Sanic is running in PRODUCTION mode. Consider using '--debug' or '--dev' while actively developing your application.
[2022-01-13 09:19:30 -0700] [8488] [INFO] Sanic Extensions:
[2022-01-13 09:19:30 -0700] [8488] [INFO] > http
[2022-01-13 09:19:30 -0700] [8488] [INFO] > openapi [http://127.0.0.1:50961/docs]
[2022-01-13 09:19:30 -0700] [8488] [INFO] > injection [0]
[2022-01-13 09:19:30 -0700] [8488] [INFO] http://127.0.0.1:50961/search
[2022-01-13 09:19:30 -0700] [8488] [ERROR] Exception occurred while handling uri: 'http://127.0.0.1:50961/search'
Traceback (most recent call last):
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/check.py", line 93, in check_data
hydration_values[key] = hint.validate(
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/check.py", line 46, in validate
_check_nullability(value, self.nullable, self.allowed, schema)
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/check.py", line 120, in _check_nullability
allowed[0].validate(value, schema)
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/check.py", line 58, in validate
value = _check_list(
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/check.py", line 145, in _check_list
raise ValueError(f"Value '{value}' must be a {hint}")
ValueError: Value '<factory>' must be a typing.List[str]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/validators.py", line 24, in validate_body
return validator(model, body)
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/validators.py", line 36, in _validate_annotations
return check_data(model, body, schema, allow_multiple, allow_coerce)
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/check.py", line 100, in check_data
raise TypeError(e)
TypeError: Value '<factory>' must be a typing.List[str]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "handle_request", line 83, in handle_request
)
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/decorator.py", line 37, in decorated_function
await do_validation(
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/setup.py", line 41, in do_validation
validation = validate_body(validator, model, data)
File "/home/user/projects/project/venv/lib/python3.9/site-packages/sanic_ext/extras/validation/validators.py", line 26, in validate_body
raise ValidationError(
sanic_ext.exceptions.ValidationError: Invalid request body: SearchRequest. Error: Value '<factory>' must be a typing.List[str]
[2022-01-13 09:19:30 -0700] - (sanic.access)[INFO][127.0.0.1:59532]: POST http://127.0.0.1:50961/search 400 796
[2022-01-13 09:19:30 -0700] [8488] [INFO] Starting worker [8488]
[2022-01-13 09:19:30 -0700] [8488] [INFO] Stopping worker [8488]
[2022-01-13 09:19:30 -0700] [8488] [INFO] Server Stopped
Traceback (most recent call last):
File "/home/user/.config/JetBrains/PyCharm2021.3/scratches/scratch_1.py", line 36, in <module>
assert response.status_code == HTTPStatus.NO_CONTENT, response.status
AssertionError: 400
To Reproduce
Run the script provided above.
Expected behavior
I would expect that the body
param would be a None
or maybe an empty dict.
Environment (please complete the following information):
Describe the bug
Templating middleware initializes request context in after_server_start
handler however
at this point the server is already serving requests.
Screenshots
Traceback (most recent call last):
File "handle_request", line 62, in handle_request
SanicException,
File "_run_request_middleware", line 28, in _run_request_middleware
AnyStr,
File "/app/.venv/lib/python3.10/site-packages/sanic_ext/extensions/templating/extension.py", line 50, in attach_request
request.app.ctx.__request__.set(request)
AttributeError: 'types.SimpleNamespace' object has no attribute '__request__'
To Reproduce
Start the Sanic server and immediately make a request.
Expected behavior
When the server is ready to handle requests the context variable should already be set.
Environment
Google Cloud Platform / Cloud Run / docker image: python:3.10-slim-bullseye
Dependencies handled by Poetry
Jinja2 = "^3.1.2"
sanic = { version = "^22.3.2", extras = ["ext"] }
Additional context
__request__
is not a good name for this, it implies that it is a magic variable typically belonging to Python itself, PEP8 suggests never inventing new ones.
I am guessing the intent was name mangling, but that only works on class attributes when prefixed with two underscores.
Load the context with before_server_start
handler instead?
On closer inspection it does not appear that __request__
is used at all and this whole initialization chain can be stripped out?
Mitigations
# I need templates: Reach into the `app._future_middleware` and fish out the broken function.
>>> app._future_middleware[0].middleware.__module__
'sanic_ext.extensions.templating.extension'
# I don't need templates: Initializing extensions manually without builtins, then picking the modules used.
>>> app.extend(built_in_extensions=False, extensions=[HTTPExtension, OpenAPIExtension])
Is your feature request related to a problem? Please describe your use case.
I am actively using attrs across my applications and I believe that with the speed, versatility, and functionality, attrs is a must-have addition to the validator decorator. Currently, validator supports pydantic, dataclasses, and custom implementation, but it would be really great to see attrs on this list.
Thank you very much.
Here's a simple example of how the class with attrs would look like:
import attrs
@attrs.define
class AttrsParameterValidator:
parameter_a: str
parameter_b: int
parameter_c: list
Please find documentation for attrs here: https://www.attrs.org/en/stable/
Describe the solution you'd like
Same behavior as pydantic or dataclasses, but for attrs.
Additional context
N/A
Hello Sanic Developers. I'm creating an API using sanic_jwt to make a user authentication. I've already documented some custom routes, but I want to write the /auth routes provided by sanic_jwt. Is it possible doing that using an external document? If so, how can I do that?
Is your feature request related to a problem? Please describe your use case.
I'm migrating from Flask to Sanic, and currently I'm using Flask's render_template_string to render a jinja template from a string.
If I understand correctly, Sanic only supports rendering from a file.
Describe the solution you'd like
I would like something like this:
TEAMPLATE = '''
<!DOCTYPE html>
<html>
<head>
<style>
</style>
</head>
<body>
<script>
</script>
</body>
</html>'''
rendered = render_from_string(TEMPLATE, context)
Describe the bug
Sorry my english may be very poor.
I have created an API that accepts both "path" and "query" type parameters, then I added OpenAPI YAML docstring under my API function, but the "query" type parameters were not displayed on the Redoc properly.
I also tried to use the openapi.parameter
decorators but didn't work too.
Screenshots
To Reproduce
this is my route file:
userbp = Blueprint("usermgr")
# 配置url
userbp.add_route(UserView.as_view(), "/user/<pk:strorempty>")
this is my view function's docstring:
openapi:
---
summary: 查看用户信息
description: 查看个人信息, 参数id在path中为可选项, 不传id是查询全部用户, 传id查看指定用户的简要信息
operationId: userget
tags:
- 标签1
- 标签2
parameters:
- pkparameter:
name: pk
in: path
description: 用户唯一标识
required: false
- queryparameter:
name: query
in: query
description: 用户对象的查询条件。
schema:
type: object
properties:
username:
type: string
description: 用户名, 使用in查询
- pageparameter:
name: page
in: query
description: 页码, 默认为1
- limitparameter:
name: limit
in: query
description: 每页数量限制
- orderparameter:
name: order
in: query
description: 排序规则
responses:
'200':
description: 用户信息
content:
application/json:
schema:
type: object
properties:
code:
type: string
description: 返回码
info:
type: string
description: 说明
data:
type: array
items:
type: object
properties:
pk:
type: string
description: 用户唯一标识
username:
type: string
description: 用户名
headimg:
type: string
description: 用户头像的base64信息
level:
type: string
description: 用户等级
description: 用户数据
Environment (please complete the following information):
Is your feature request related to a problem? Please describe your use case.
When raise a validation error the HTTP status code 400 is returned always.
Describe the solution you'd like
I would like to choose another HTTP status code explicitly when raise an error validation.
Describe the bug
When a parameter has a defined location such as "path" or "header" and is defined using the openapi.definition
decorator, the location seems to be ignored and is shown as "query".
To Reproduce
Use the decorator like so:
@openapi.definition(
parameter=[
openapi.Parameter("enterprise", str, "path"),
],
)
Expected behavior
The parameter is marked as whatever type it's set to, and isn't overriden by the fallback value.
Environment (please complete the following information):
Is your feature request related to a problem? Please describe your use case.
Currently when a parameter is defined for injection, the injection happens before anything else. This makes it hard for usecases such as ratelimiting or authentication middlewares, as these are executed after the injection. In cases where handlers should be ratelimited to reduce load (on a database, for example) this makes ratelimiting useless, as the database is always hit before the user has a chance to stop the request. Another usecase would be checking for a valid authentication before injecting any parameters.
Describe the solution you'd like
A few possibilities considered:
@no_auto_inject
and @inject
decorators to determine where injection should occur)Additional context
N/A
Describe the bug
Injector does not recognize the inherited request class as Sanic request class.
Screenshots
Not applicable
To Reproduce
from sanic import Sanic, Request as SanicRequest
class InheritedRequest(SanicRequest):
...
app = Sanic("TEST_SERVER", request_class=InheritedRequest)
class Something:
async def execute(self):
...
async def get_something(request: InheritedRequest):
return Something
app.ext.add_dependency(Something, get_something)
app.run(port=5000, dev=True)
Expected behavior
The injector should skip the request parameter, so that the error not occurs.
Environment (please complete the following information):
Additional context
File "C:\Users\<truncated>\lib\site-packages\sanic_ext\extensions\injection\injector.py", line 31, in finalize_injections
injection_registry.finalize(router_types)
File "C:\Users\<truncated>\lib\site-packages\sanic_ext\extensions\injection\registry.py", line 32, in finalize
constructor.prepare(self, allowed_types)
File "C:\Users\<truncated>\lib\site-packages\sanic_ext\extensions\injection\constructor.py", line 81, in prepare
raise InitError(
sanic_ext.exceptions.InitError: Unable to resolve dependencies for 'get_something'. Could not find the following dependencies:
- request: <class '__main__.InheritedRequest'>.
Make sure the dependencies are declared using ext.injection. See https://sanicframework.org/en/plugins/sanic-ext/injection.html#injecting-services for more details.
Describe the bug
I previously used app.static
to configure our static files directory. However, after installing ext, GET requests for static files throw 405s and log the following
[2022-01-13 12:04:35 -0500] [11337] [DEBUG] Dispatching signal: http.routing.before
[2022-01-13 12:04:35 -0500] - (sanic.access)[INFO][127.0.0.1:60821]: GET http://0.0.0.0:8000/static/pickadate/picker.js 405 777
Screenshots
N/A
To Reproduce
APP = Sanic(...)
static_directory = os.path.abspath("frontend")
APP.static("/static", static_directory)
APP.static("/", os.path.join(static_directory, "index.html"))
APP.extend(config=Config(oas=False))
Expected behavior
GET requests to the /static/
routes return 200s.
Environment (please complete the following information):
Thank you!
app = Sanic("test")
@app.route("/test1")
@openapi.document(
ExternalDocumentation("http://example.com/mor1111", "Find more info her111e")
)
async def handler1(request: Request):
return text("ok")
@app.route("/test2")
async def handler2(request: Request):
"""
openapi:
---
summary: This is a summary.
externalDocs:
description: Find more info here
url: http://example.com/more
"""
return text("ok")
app.run()
The externalDocs
field of path /test1
is incorrect:
{
"openapi": "3.0.0",
"info": {
"title": "API",
"version": "1.0.0",
"contact": {}
},
"paths": {
"\/test1": {
"get": {
"operationId": "get~handler1",
"summary": "Handler1",
"externalDocs": {
"url": {
"url": "http:\/\/example.com\/mor1111",
"description": "Find more info her111e"
},
"description": null
},
"responses": {
"default": {
"description": "OK"
}
}
}
},
"\/test2": {
"get": {
"operationId": "get~handler2",
"summary": "This is a summary.",
"externalDocs": {
"description": "Find more info here",
"url": "http:\/\/example.com\/more"
},
"responses": {
"default": {
"description": "OK"
}
}
}
}
},
"tags": [],
"servers": [],
"security": []
}
Describe the bug
When the Sanic app is ran with debug=True
, it seems like sanic-ext
is being instantiated twice (maybe due to the reloader?).
To Reproduce
Create a Sanic app, load sanic-ext
into it and run it using debug mode.
Expected behavior
Only one instance of sanic-ext
is instantiated, or at least it's only logged once
Environment (please complete the following information):
Additional context
N/A
Dependency injection should have an optional flag to reuse objects from a cache instead of recreating them every time.
Not sure if it's related to sanic-ext at all or just missing understanding of dataclasses on my side ;)
I'm trying to describe and validate a json body w/ optional parameters.
from dataclasses import dataclass
from typing import List, Optional
from sanic import Sanic
from sanic.response import json
from sanic_ext import openapi, validate
app = Sanic("Test")
@dataclass
class Test:
foo: str
bar: int
foobar: Optional[List[str]]
@app.post("/")
@openapi.body({"application/json": Test})
@validate(Test)
async def test(request, body: Test):
return json({"message": "Hello world!"})
This renders as request body describing foobar as object or nullable.
Validation now fails when foobar is omitted (correct so far)
$ curl -d '{"foo": "string", "bar": 1337}' http://127.0.0.1:8000/
{"description":"Bad Request","status":400,"message":"Invalid request body: Test. Error: missing a required argument: 'foobar'"}
Now, if i set a default for foobar
like
@dataclass
class Test:
foo: str
bar: int
foobar: Optional[List[str]] = None
the request works as expected:
$ curl -d '{"foo": "string", "bar": 1337}' http://127.0.0.1:8000/
{"message":"Hello world!"}
But now API docs renders as object
without nullable
:
How can i combine the two? Show foobar
as nullable
in docs and pass validation w/ parameter omitted?
Do i need 2 seperate dataclasses?
Also, is it possible to show foobar
as list of strings in the docs instead of object (derives from Optional
, i guess).
Hi,
Just testing templating module, it looks great, but one feature is terrible, by default autoescape of Jinja2 is ON and lots of symbols is escaped. solution is simple:
app.ext.environment.autoescape = False
I use url_for() function in Flask and it' will be good to add it to templating
Thanks
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.