FAQ¶
设计理念¶
为什么Falcon不带电池?¶
Falcon是为需要高度定制或性能调整的应用程序而设计的。框架的极简设计使开发人员能够为手头的任务选择最佳策略和第三方包。
python生态系统提供了许多优秀的包,您可以从响应程序、钩子和中间件组件中使用这些包。作为一个起点,社区维护了 Falcon add-ons and complementary packages .
为什么Falcon不为每个请求创建一个新的资源实例?¶
Falcon通常尝试最小化它实例化的对象的数量。这样做有两个原因:第一,为了避免创建对象的开销,第二,通过减少高度并发工作负载下所需的对象总数来减少内存使用。因此,在添加路线时,Falcon需要 实例 资源类,而不是类类型。同一个实例将用于服务该路由上传入的所有请求。
如果我的响应者提出错误,会发生什么情况?¶
一般来说,Falcon假设资源响应者(如 on_get()
, on_post()
在大多数情况下,会做正确的事情。换句话说,Falcon并不努力保护响应程序代码不受其影响。
注解
从3.0版开始,框架将不再将未捕获的异常传播到应用服务器。相反,默认情况下, Exception
处理程序将返回HTTP 500响应并将异常详细信息记录到 wsgi.errors
。
虽然Falcon提供了基本的错误处理程序,但它针对资源响应器不会为有效请求引发任何错误的最常见情况进行了优化。考虑到这一点,编写基于Falcon的高质量API要求:
资源响应程序将响应变量设置为正常值。
您的代码经过了良好的测试,代码覆盖率很高。
在每个响应程序中以及在自定义错误处理程序的帮助下,正确地预测、检测和处理错误。
如何为Falcon API生成API文档?¶
当涉及到API文档时,一些开发人员倾向于使用API实现作为用户契约或真相来源(采用实现优先的方法),而其他开发人员则倾向于使用API规范本身作为契约,根据该规范实现和测试API(t以设计为先的方法)。
在灵活性方面存在错误的风险,Falcon不提供现成的API规范支持。然而,在这方面有几个社区项目可用。我们的 Add on Catalog 列出这些项目中的一些,但您也可能希望搜索 PyPI 对于附加包。
如果您对上面提到的“设计优先”方法感兴趣,您可能还需要查看API设计和网关服务,如Tyk、Apariy、Amazon API网关或Google云端点。
性能¶
Falcon是否使用HTTP/2?¶
Falcon是一个WSGi框架,因此不直接为HTTP请求提供服务。但是,您可以通过在应用程序前面部署任何符合HTTP/2的Web服务器或负载平衡器来在HTTP/2和HTTP/1.1之间进行转换,从而获得HTTP/2的大部分好处。最后,我们期望python web服务器(如uwsgi)能够本机支持HTTP/2,从而消除了对转换层的需求。
Falcon线安全吗?¶
Falcon框架本身是线程安全的。例如,新 Request
和 Response
为每个传入的HTTP请求创建对象。但是,连接到路由的每个资源类的单个实例在所有请求之间共享。中间件对象和其他类型的钩子(如自定义错误处理程序)也同样是共享的。因此,只要您以线程安全的方式实现这些类和可调用文件,并确保应用程序使用的任何第三方库也是线程安全的,那么整个WSGi应用程序将是线程安全的。
Falcon支持Asyncio吗?¶
从3.0版开始, ASGI Falcon的味道现在自豪地支持 asyncio
好了!使用 falcon.asgi.App
类创建异步应用程序,并通过 ASGI application server 比如Uvicorn。
或者,受IO限制的WSGI应用程序可以使用经过战斗测试的 gevent 库通过Gunicorn或uWSGI。 meinheld 社区还成功地使用它来支持高吞吐量、低延迟的WSGI服务。
小技巧
请注意,如果您使用Gunicorn,您可以结合使用gevent和PyPy来实现令人印象深刻的性能水平。(不幸的是,uWSGI还不支持同时使用gevent和pyPy。)
Falcon支持WebSocket吗?¶
Falcon的异步风格支持 ASGI WebSocket协议。另请参阅: WebSocket(仅限ASGI) 。
WSGI应用程序可能会尝试利用 uWSGI's native WebSocket support 或 gevent-websocket's GeventWebSocketWorker
为了Gunicorn。
作为一种选择,将WebSocket支持设计为单独的服务可能是有意义的,因为与常规的RESTful API相比,WebSocket支持的性能特征和交互模式非常不同。除了(显然!)Falcon的本地ASGI支持,独立的WebSocket服务也可以通过Aymeric Augustin的Handy实现 websockets 类库。
路由¶
我如何用Falcon实现CORS?¶
为了使网站或SPA能够访问不同域名下托管的API,该API必须实现 Cross-Origin Resource Sharing (CORS) 。对于公共API,在Falcon中实现CORS非常简单,只需将 cors_enable
标志(设置为 True
)实例化时 your application 。
可以通过以下方式进一步定制CORS CORSMiddleware
(有关在Falcon中管理CORS的详细信息,另请参阅 CORS )。
有关更复杂的用例,请查看社区中的Falcon附加组件,例如 falcon-cors ,或尝试其中一个泛型 WSGI CORS libraries available on PyPI 。如果您使用API网关,您还可以查看它在该级别提供了哪些CORS功能。
如何在Falcon中实现重定向?¶
Falcon提供了许多异常类,可以引发这些类来将客户机重定向到不同的位置(另请参见 Redirection )
但是,请注意,如果可能的话,直接使用Web服务器处理永久重定向比在应用程序上为此类请求增加额外的负载更有效。
如何在我的原始应用程序和我迁移到Falcon的部分之间分割请求?¶
在最需要的地方,通常是将应用程序的一部分分割出来,然后在Falcon中重新实现,以提高性能。
如果您有权访问负载均衡器或反向代理配置,我们建议您设置基于路径或子域的规则,以便在原始实现和迁移到Falcon的部分之间拆分请求(例如,通过添加 location
对nginx配置的指令)。
如果上面的方法不是您部署的一个选项,那么您可以实现一个简单的wsgi包装器,它执行相同的操作:
def application(environ, start_response):
try:
# NOTE(kgriffs): Prefer the host header; the web server
# isn't supposed to mess with it, so it should be what
# the client actually sent.
host = environ['HTTP_HOST']
except KeyError:
# NOTE(kgriffs): According to PEP-3333, this header
# will always be present.
host = environ['SERVER_NAME']
if host.startswith('api.'):
return falcon_app(environ, start_response)
elif:
return webapp2_app(environ, start_response)
也见 PEP 3333 有关通过以下方式提供的变量的完整列表: environ
.
如何为同一资源实现过账和获取项目?¶
假设您有以下路线:
# Resource Collection
GET /resources{?marker, limit}
POST /resources
# Resource Item
GET /resources/{id}
PATCH /resources/{id}
DELETE /resources/{id}
您可以通过使用两个Python类来实现这种API,一个类表示单个资源,另一个类表示所述资源的集合。通常将两个类放在同一个模块中(另请参见 this section of the tutorial )
或者,可以使用后缀响应程序将两个路由映射到同一个资源类:
class MyResource:
def on_get(self, req, resp, id):
pass
def on_patch(self, req, resp, id):
pass
def on_delete(self, req, resp, id):
pass
def on_get_collection(self, req, resp):
pass
def on_post_collection(self, req, resp):
pass
# -- snip --
resource = MyResource()
app.add_route('/resources/{id}', resource)
app.add_route('/resources', resource, suffix='collection')
为什么我的URL带有百分比编码的正斜杠 (%2F
)路由不正确?¶
这是WSGI规范的一个不幸之处,它没有提供访问“原始”请求URL的标准方法。根据PEP 3333, the recommended way to reconstruct a request's URL path 正在使用 PATH_INFO
CGI变量,它已经被百分比解码,有效地生成了最初的百分比编码的正斜杠 (%2F
)与逐字传递的其他类型没有区别(并且打算分隔URI字段)。
尽管没有标准化,但一些WSGI服务器将原始URL作为非标准扩展提供;例如,Gunicorn将其公开为 RAW_URI
,uWSGI称之为 REQUEST_URI
等。您可以实现WSGI(或ASGI,参见下面的讨论)中间件组件,以使用原始URL的路径组件覆盖请求路径,请参阅以下食谱中的更多内容: 解码原始URL路径 。
与WSGI不同,ASGI规范确实定义了标准连接HTTP作用域变量名 (raw_path
)用于未修改的HTTP路径。但是,它不是强制性的,一些应用程序服务器可能无法提供它。然而,我们正在探索添加一个可选功能的可能性,以便在该框架的ASGI风格中使用这条原始路径进行路由。
可扩展性¶
如何在Falcon中使用WSGi中间件?¶
实例 falcon.App
是一流的WSGi应用程序,因此您可以使用PEP-3333中概述的标准模式。在主“app”文件中,您只需使用中间件应用程序包装API实例。例如:
import my_restful_service
import some_middleware
app = some_middleware.DoSomethingFancy(my_restful_service.app)
也见 WSGI middleware example 在PEP-3333中给出。
如何将数据从一个钩子传递给响应程序,以及在钩子之间传递?¶
您可以从钩子中注入额外的响应者Kwarg,方法是将它们添加到 参数 迪克特把球打进了钩子。您还可以在 req.context
对象,作为传递上下文信息的一种方式:
def authorize(req, resp, resource, params):
# TODO: Check authentication/authorization
# -- snip --
req.context.role = 'root'
req.context.scopes = ('storage', 'things')
req.context.uid = 0
# -- snip --
@falcon.before(authorize)
def on_post(self, req, resp):
pass
如何在Falcon中编写404和500页的自定义处理程序?¶
当找不到传入请求的路由时,Falcon使用一个默认响应程序,该响应程序只引发一个 HTTPRouteNotFound
,然后框架将呈现为404响应。你可以用 falcon.App.add_error_handler()
要重写此异常类型(或其父类型)的默认处理程序, HTTPNotFound
). 或者,您可以配置您的web服务器来为您转换响应(例如,使用nginx的 error_page
指令)。
默认情况下,非系统退出的异常不继承自 HTTPError
或 HTTPStatus
由Falcon处理,但有一个纯HTTP 500错误。要提供自己的500逻辑,可以为Python的base添加一个自定义错误处理程序 Exception
类型。这不会影响的默认处理程序 HTTPError
和 HTTPStatus
.
看见 错误处理 以及 falcon.App.add_error_handler()
有关更多详细信息,请参阅文档。
请求处理¶
如何验证请求?¶
挂钩和中间件组件可以一起用于对请求进行身份验证和授权。例如,可以使用中间件组件来解析传入的凭据并将结果放入 req.context
。考虑到用户的角色和所请求的资源,下游组件或挂钩随后可以使用此信息来授权请求。
为什么req.stream.read()对于某些请求挂起?¶
这种行为是一个不幸的产物,因为wsgi规范(PEP-3333)没有完全定义请求的身体力学。这在参考文件中讨论 stream
,并以以下形式提供了解决方案: bounded_stream
.
Falcon如何处理请求路径中的尾随斜杠?¶
如果你的应用程序设置 strip_url_path_trailing_slash
到 True
Falcon将规范传入的URI路径,以简化后期处理并提高应用程序逻辑的可预测性。这在实现RESTAPI模式时很有用,该模式不会将尾随斜杠字符解释为引用隐式子资源的名称,而网站通常使用该名称来引用索引页。
例如,启用此选项后,为添加路由 '/foo/bar'
隐式添加的路由 '/foo/bar/'
.换句话说,任何路径的请求都将发送到相同的资源。
警告
如果 strip_url_path_trailing_slash
如果启用,则添加带有尾随斜杠的路由将有效地使其无法从正常路由中访问(理论上讲,仍然可以通过重写中间件中的请求路径来匹配)。
在这种情况下,应该添加不带尾随斜杠的路由(显然除了根路径之外 '/'
),如 '/foo/bar'
在上面的例子中。
注解
从2.0版开始,默认的 strip_url_path_trailing_slash
请求选项已从更改 True
到 False
.
为什么req对象中缺少我的查询参数?¶
如果查询参数没有值,并且 keep_blank_qs_values
请求选项设置为 False
(截至Falcon 2.0+,默认值为 True
),Falcon将忽略该参数。例如,传递 'foo'
或 'foo='
将导致该参数被忽略。
如果您想要识别这些参数,请使用 keep_blank_qs_values
请求选项应设置为 True
(或者简单地在Falcon 2.0+中保持其默认值)。请求选项是为每个实例全局设置的 falcon.App
通过 req_options
财产。例如:
app.req_options.keep_blank_qs_values = True
为什么将参数中的“+”字符转换为空格?¶
这个 +
通常使用字符代替 %20
由于表单参数编码的历史合并,在查询字符串参数中表示空格 (application/x-www-form-urlencoded
)和URI百分比编码。因此,Falcon,转化者 +
对字符串解码时的空格。
为了解决这个问题,RFC3986规定 +
作为保留字符,并建议在需要这些字符的文字值时对其进行百分比编码。 (%2B
如果是 +
)
如何访问已发布的表单参数?¶
默认情况下,Falcon不使用请求体。但是,一个 media handler 对于 application/x-www-form-urlencoded
默认情况下安装了content-type,因此发布的表单可用作 Request.media
零配置:
import falcon
class MyResource:
def on_post(self, req, resp):
# TODO: Handle the submitted URL-encoded form
form = req.media
# NOTE: Falcon chooses the right media handler automatically, but
# if we wanted to differentiate from, for instance, JSON, we
# could check whether req.content_type == falcon.MEDIA_URLENCODED
# or use mimeparse to implement more sophisticated logic.
注解
在Falcon的早期版本中,发布的URL编码表单可以被自动消费并合并到 params
通过设置 auto_parse_form_urlencoded
选择权 True
. Falcon 3.x系列仍然支持此行为。然而,它已经被弃用,取而代之的是 URLEncodedFormHandler
,以及将URL编码的表单数据合并到 params
可能在将来的版本中删除。
发布的表单参数也可以直接从 stream
并通过以下方式进行解析 falcon.uri.parse_query_string()
或 urllib.parse.parse_qs()
。
如何访问已发布的文件?¶
如果文件是 POST
作为 multipart form ,默认的 MultipartFormHandler
可用于高效解析提交的 multipart/form-data
request media 通过迭代多部分 body parts
:
for part in req.media:
# TODO: Do something with the body part
pass
如何将发布的文件(从多部分表单)直接保存到aws3?¶
如前一个答案所强调的 files posted as multipart form , falcon.media.MultipartFormHandler
可用于迭代上载的多部分主体部分。
这个 stream of a body part is a file-like object implementing the read()
method, making it compatible with boto3
's upload_fileobj :
import boto3
# -- snip --
s3 = boto3.client('s3')
for part in req.media:
if part.name == 'myfile':
s3.upload_fileobj(part.stream, 'mybucket', 'mykey')
注解
Falcon不支持任何特定的云服务提供商,以及AWS S3和 boto3
这里引用的只是一个流行的例子。同样的模式可以应用于任何支持直接从类似文件的对象流式传输的存储API。
如何解析嵌套的多部分表单?¶
Falcon不提供解析嵌套多部分表单的官方支持(即,使用嵌套的 multipart/mixed
部分)此时。根据用法被视为已弃用 living HTML5 standard 和 RFC 7578, Section 4.3 .
小技巧
如果你的应用程序绝对必须处理这样的遗留表单,解析器实际上可能能够完成这项任务。更多信息请参见本配方: 解析嵌套的多部分表单 .
如何从查询字符串中检索JSON值?¶
为了从查询字符串中检索JSON编码的值,Falcon提供 get_param_as_json()
方法,示例如下:
import falcon
class LocationResource:
def on_get(self, req, resp):
places = {
'Chandigarh, India': {
'lat': 30.692781,
'long': 76.740875
},
'Ontario, Canada': {
'lat': 43.539814,
'long': -80.246094
}
}
coordinates = req.get_param_as_json('place')
place = None
for (key, value) in places.items():
if coordinates == value:
place = key
break
resp.media = {
'place': place
}
app = falcon.App()
app.add_route('/locations', LocationResource())
在上面的例子中, LocationResource
要求查询字符串包含名为的JSON编码值 'place'
. 使用 get_param_as_json()
方法。给定一个请求URL,例如:
/locations?place={"lat":43.539814,"long":-80.246094}
这个 coordinates 变量将设置为 dict
果不其然。
默认情况下, auto_parse_qs_csv
选项设置为 False
. 上面的例子假设这个默认值。
另一方面,什么时候 auto_parse_qs_csv
设置为 True
,Falcon将查询字符串中的逗号视为分隔逗号分隔列表的文字字符。例如,给定查询字符串 ?c=1,2,3
,Falcon会把这个加到你的 request.params
字典为 {{'c': ['1', '2', '3']}}
.例如,如果试图在查询字符串的值中使用JSON ?c={{"a":1,"b":2}}
,值将被添加到 request.params
以一种意想不到的方式: {{'c': ['{{"a":1', '"b":2}}']}}
.
逗号是一个保留字符,可以根据 RFC 3986 - 2.2. Reserved Characters ,因此一个可能的解决方案是对JSON查询字符串中出现的任何逗号进行百分比编码。
另一个选择是离开 auto_parse_qs_csv
禁用,只使用JSON数组语法代替CSV。
什么时候? auto_parse_qs_csv
未启用,则查询字符串的值 ?c={{"a":1,"b":2}}
将添加到 req.params
字典为 {{'c': '{{"a":1,"b":2}}'}}
. 无论客户机是否选择对请求中的逗号进行百分比编码,都可以使用JSON。在本例中,您可以通过 get_param()
或使用 get_param_as_json()
上述简易方法。
如何处理路由模板字段中的正斜杠?¶
在Falcon 1.3中,我们提供了对 field converters 。我们已经讨论了如何在此功能的基础上支持使用多个路径段。这项工作目前计划在3.0版本之后开始。
同时,您可以通过实现一个Falcon中间件组件来解决这个问题,以便在路径被路由之前重写它。如果您控制了客户端,则可以在相关字段内对正斜杠进行百分比编码,但是请注意,为了访问原始编码的URI,也不可避免地要进行预处理。另见: 为什么我的URL带有百分比编码的正斜杠 (%2F )路由不正确?
如何使代码适应Falcon2.0中的默认上下文类型更改?¶
在Falcon2.0中,默认的请求/响应上下文类型已从dict更改为bare类。现在,您可以简单地设置对象的属性,而不是设置字典项:
# Before Falcon 2.0
req.context['cache_backend'] = MyUltraFastCache.connect()
# Falcon 2.0
req.context.cache_backend = MyUltraFastCache.connect()
新的默认上下文类型以上下文属性链接到dict项的方式模拟类似dict的映射接口,即设置对象属性也设置相应的dict项,反之亦然。因此,现有代码在很大程度上可以在不修改falcon 2.0的情况下工作。不过,建议如上文所述迁移到新接口,因为在将来的版本中,类似dict的映射接口可能会从上下文类型中删除。
警告
如果需要在迁移下混合和匹配这两种方法,请注意设置属性,例如 项目 或 价值观 显然会隐藏相应的映射接口函数。
如果现有项目正在大量使用字典上下文,则可以通过使用自定义请求/响应类型将类型显式重写回dict:
class RequestWithDictContext(falcon.Request):
context_type = dict
class ResponseWithDictContext(falcon.Response):
context_type = dict
# -- snip --
app = falcon.App(request_type=RequestWithDictContext,
response_type=ResponseWithDictContext)
响应处理¶
我什么时候应该使用媒体、数据、文本和流?¶
这四个属性是互斥的,您在定义响应时应该只设置一个。
resp.media
当您想要使用Falcon序列化机制时使用。只需将数据分配给属性,Falcon就会处理其余的事情。
class MyResource:
def on_get(self, req, resp):
resp.media = {'hello': 'World'}
resp.text
和 resp.data
非常相似,它们都允许您设置响应的正文。不同的是, text
获取一个字符串,然后 data
取字节。
class MyResource:
def on_get(self, req, resp):
resp.text = json.dumps({'hello': 'World'})
def on_post(self, req, resp):
resp.data = b'{"hello": "World"}'
resp.stream
允许您设置生成字节的生成器,或设置具有 read()
方法返回字节。对于类似文件的对象,框架将调用 read()
直到溪流耗尽。
class MyResource:
def on_get(self, req, resp):
resp.stream = open('myfile.json', mode='rb')
另请参阅 输出CSV文件 食谱,以了解如何使用 resp.stream
用一台发电机。
如何在datetime类型中使用resp.media?¶
的默认JSON处理程序 resp.media
仅支持下列表格中列出的对象和类型 json.JSONEncoder
。
要在JSON中处理其他类型,可以预先序列化它们,或者创建一个定制的JSON媒体处理程序,该处理程序设置 default Param for json.dumps()
。在反序列化传入请求正文时,您可能还希望实现 object_hook 为 json.loads()
。但是,请注意,设置 default 或 object_hook 参数可能会对(反)序列化的性能产生负面影响。
如果您使用替代的JSON库,还可以查看它是否支持其他数据类型。例如,广受欢迎的 orjson
选择自动序列化 dataclasses
, enums
, datetime
物件等
此外,不同的互联网媒体类型,如YAML, msgpack
等可能支持比JSON更多的数据类型,无论是作为各自(反)序列化格式的一部分,还是通过自定义类型扩展。
参见
看见 定制JSON编码器 获取有关如何使用定制JSON编码器的示例。
Falcon是设置内容长度,还是需要显式设置?¶
Falcon将尝试为您执行此操作,基于 resp.text
, resp.data
或 resp.media
(以响应中设置的值为准,按该顺序选中)。
对于动态生成的内容,您可以选择不设置 content_length
在这种情况下,Falcon将去掉Content-Length报头,并且希望您的WSGI服务器会做正确的事情™(假设您已经告诉服务器启用Keep-Alive,它可能会选择使用分块编码)。
注解
PEP-3333禁止应用程序自行设置逐点报头,例如传输编码。
与WSGI类似, ASGI HTTP connection scope 规范规定,没有内容长度的响应“可以在服务器认为合适的情况下被分块”。
当我引发httperror实例时,为什么返回空的响应主体?¶
Falcon试图将 HTTPError
实例使用其 to_json()
或 to_xml()
方法,根据请求中的Accept头。如果JSON和XML都不可接受,则不会生成响应主体。如果需要,您可以通过 set_error_serializer()
.
我正在设置一个响应主体,但它不会被返回。发生什么事?¶
当根据HTTP规范,不应返回任何主体时,falcon跳过处理响应主体。如果客户机发送一个head请求,框架将始终返回一个空的主体。当响应状态为以下任一状态时,Falcon也将返回空体:
falcon.HTTP_100
falcon.HTTP_204
falcon.HTTP_416
falcon.HTTP_304
如果你有另一个没有归还尸体的案例,那很可能是个虫子! Let us know 所以我们可以帮忙。
如何使用Falcon提供可下载的文件?¶
在 on_get()
Responder方法对于资源,可以通过设置Content Disposition头来通知用户代理下载文件。Falcon包括 downloadable_as
属性以简化此操作:
resp.downloadable_as = 'report.pdf'
也见 输出CSV文件 一个更复杂的动态生成可下载内容示例的配方。
为什么Falcon要把我的标题名改成小写?¶
Falcon在将头名称存储到内部之前总是将其小写 Response
结构,以便使响应头处理简单而高效,因为头名称查找可以使用简单的 dict
. 由于HTTP头不区分大小写,所以这种优化通常不应影响API使用者。
在不太可能的情况下,您绝对必须处理不符合要求的HTTP客户端,希望使用特定的头名称大写,请参阅下面的方法如何使用通用WSGI中间件重写头名称: 响应头名称大写 .
注意,这个问题只适用于Falcon的WSGI风格。这个 ASGI HTTP scope specification 要求HTTP标头名称为小写。
此外,HTTP2标准还要求头字段名必须转换为小写(请参见 RFC 7540, Section 8.1.2 )
Falcon能提供静态文件吗?¶
Falcon通过简单地将打开的文件分配给 resp.stream
as demonstrated in the tutorial .您还可以通过 falcon.App.add_static_route()
.但是,如果可能的话,最好是直接从网络服务器(如nginx)或cdn提供静态文件。
其他¶
如何管理数据库连接?¶
假设数据库库管理自己的连接池,您所需要做的就是初始化客户机并将其实例传递到资源类中。例如,使用sqlacalchemy core:
engine = create_engine('sqlite:///:memory:')
resource = SomeResource(engine)
然后,在 SomeResource
:
# Read from the DB
with self._engine.connect() as connection:
result = connection.execute(some_table.select())
for row in result:
# TODO: Do something with each row
result.close()
# -- snip --
# Write to the DB within a transaction
with self._engine.begin() as connection:
r1 = connection.execute(some_table.select())
# -- snip --
connection.execute(
some_table.insert(),
col1=7,
col2='this is some data'
)
使用数据访问层时,只需将引擎传递到数据访问对象中即可。另请参见 this sample Falcon project 这说明使用ORM和Falcon。
您还可以创建一个中间件组件来自动检查每个请求的数据库连接,但这会使跟踪错误或根据单个请求的需要进行调优变得更加困难。
如果您需要透明地处理错误后的重新连接,或者对于客户机库不支持的其他用例,只需将客户机库封装在处理所有棘手位的管理类中,然后将其传递。
如果您对中间件方法感兴趣,请参阅 falcon-sqla 库可用于以这种方式自动签出和关闭SQLAlChemy连接(尽管它还支持显式上下文管理器模式)。
如何使用ASGI管理我的数据库连接?¶
此示例与上面的示例类似,但它使用ASGI生命周期挂钩来设置连接池,并在应用程序结束时将其处置。该示例使用 psycopg 来连接到PostgreSQL数据库,但类似的模式可能适用于其他异步数据库库。
import psycopg_pool
url = 'postgresql://scott:tiger@127.0.0.1:5432/test'
class AsyncPoolMiddleware:
def __init__(self):
self._pool = None
async def process_startup(self, scope, event):
self._pool = psycopg_pool.AsyncConnectionPool(url)
await self._pool.wait() # created the pooled connections
async def process_shutdown(self, scope, event):
if self._pool:
await self._pool.close()
async def process_request(self, req, resp):
req.context.pool = self._pool
try:
req.context.conn = await self._pool.getconn()
except Exception:
req.context.conn = None
raise
async def process_response(self, req, resp, resource, req_succeeded):
if req.context.conn:
await self._pool.putconn(req.context.conn)
然后,示例资源可以使用该连接或池:
class Numbers:
async def on_get(self, req, resp):
# This endpoint uses the connection created for the request by the Middleware
async with req.context.conn.cursor() as cur:
await cur.execute('SELECT value FROM numbers')
rows = await cur.fetchall()
resp.media = [row[0] for row in rows]
async def on_get_with_pool(self, req, resp):
# This endpoint uses the pool to acquire a connection
async with req.context.pool.connection() as conn:
cur = await conn.execute('SELECT value FROM numbers')
rows = await cur.fetchall()
await cur.close()
resp.media = [row[0] for row in rows]
然后可以将该应用程序用作
from falcon.asgi import App
app = App(middleware=[AsyncPoolMiddleware()])
num = Numbers()
app.add_route('/conn', num)
app.add_route('/pool', num, suffix='with_pool')
推荐的应用程序配置方法是什么?¶
在应用配置上,Falcon并不固执己见。您可以从Python社区维护的任何优秀的通用配置库中自由选择。如果您想要使用标准库或类似的东西,这在很大程度上取决于您 aumbry
正如这一点所证明的那样 Falcon example app 。
(另请参见 配置 我们的一节 Complementary Packages wiki page .您可能还希望在pypi中搜索其他选项)。
选择配置库后,剩下的唯一问题是如何访问整个应用程序的配置选项。
在这个问题上,人们通常分成两个阵营。第一个阵营喜欢实例化一个配置对象,并将其传递给资源类的初始值设定项,因此数据共享是显式的。第二个阵营喜欢创建一个配置模块,并在需要的地方导入它。
使用后一种方法,为了控制实际加载配置的时间,最好不要在配置模块名称空间的顶层实例化它。这样可以避免任何问题性的副作用,这些副作用可能是在每当Python处理配置模块的第一次导入时加载配置所引起的。相反,考虑在模块中实现一个按需返回新的或缓存的配置对象的函数。
如何测试Falcon应用程序?我能用试纸吗?¶
Falcon的测试框架支持 unittest
和 pytest
.事实上,docs中的教程提供了对 testing Falcon apps with pytest .
(另见: Testing )