Odoo FastAPI端点
项目描述
此插件提供将FastAPI框架无缝集成到Odoo的基础。
此集成允许您使用FastAPI的所有优点,根据标准的Python类型提示构建自定义API。
什么是构建API?
API是一组可以从外部世界调用的函数。API的目标是提供一种方式,从外部世界与您的应用程序交互,而无需了解其内部工作原理。在构建API时,一个常见的错误是暴露您应用程序的所有内部函数,从而在外部世界与您的内部数据模型和业务逻辑之间创建紧密耦合。这不是一个好主意,因为这会使更改内部数据模型和业务逻辑变得非常困难,否则会破坏外部世界。
在构建API时,您定义了外部世界与您的应用程序之间的合同。此合同由您公开的函数和接受的参数定义。此合同是API。当您更改内部数据模型和业务逻辑时,您仍然可以保持相同的API合同,因此您不会破坏外部世界。即使您更改了实现,只要您保持相同的API合同,外部世界仍然会工作。这就是API的美丽之处,这也是为什么设计一个好的API如此重要的原因。
一个好的API设计是为了稳定且易于使用。它旨在提供与特定用例相关的高级功能。它旨在通过隐藏内部数据模型和业务逻辑的复杂性来便于使用。在构建API时常见的错误是暴露应用程序的所有内部函数,并让外界处理内部数据模型和业务逻辑的复杂性。别忘了,从事务的角度来看,对API函数的每次调用都是一个事务。这意味着如果特定的用例需要多次调用您的API,您应该提供一个函数,在单个事务中完成所有工作。这就是为什么API方法被称为高级和原子函数。
目录
用法
什么是使用fastapi构建API?
FastAPI是一个现代、快速(高性能)、基于Python 3.7+标准Python类型提示构建API的Web框架。此插件让您利用fastapi框架的优势,并使用Odoo。
在您开始之前,我们必须定义一些术语
应用程序:FastAPI应用程序是一组路由、依赖项和其他组件的集合,可以用来构建Web应用程序。
路由器:路由器是一组可以挂载到应用程序中的路由。
路由:路由是HTTP方法和路径之间的映射,定义了当用户请求该路径时应该发生什么。
依赖项:依赖项是一个可调用对象,可以用来从用户请求中获取一些信息,或者在调用请求处理器之前执行一些操作。
请求:请求是一个对象,它包含用户浏览器在HTTP请求过程中发送的所有信息。
响应:响应是一个对象,它包含用户浏览器构建结果页面所需的所有信息。
处理器:处理器是一个函数,它接受一个请求并返回一个响应。
中间件:中间件是一个函数,它接受一个请求和一个处理器,并返回一个响应。
FastAPI框架基于以下原则
快速:非常高的性能,与NodeJS和Go相当(归功于Starlette和Pydantic)。[Python框架中速度最快之一]
快速编码:提高开发特性的速度约200%至300%。
更少错误:减少大约40%的人为(开发者)引起的错误。
直观:出色的编辑器支持。无处不在的完成。减少调试时间。
简单:设计得易于使用和学习。减少阅读文档的时间。
简洁:最小化代码重复。每个参数声明提供多个功能。更少的错误。
健壮:获得可用于生产的代码。带有自动交互式文档。
基于标准:基于(且完全兼容)APIs的开放标准:OpenAPI(以前称为Swagger)和JSON Schema。
开源:FastAPI完全开源,遵循MIT许可。
第一步是安装fastapi插件。您可以使用以下命令完成此操作
$ pip install odoo-addon-fastapi
一旦插件安装完成,您就可以开始构建您的API。您需要做的第一件事是创建一个新的依赖“fastapi”的插件。例如,让我们创建一个名为my_demo_api的插件。
然后,您需要通过定义一个继承自‘fastapi.endpoint’的模型来声明您的应用程序,并将应用程序名称添加到app字段中。例如
from odoo import fields, models
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
app: str = fields.Selection(
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"}
)
‘fastapi.endpoint’模型是所有端点的基础模型。端点实例是fastapi应用程序在Odoo中的挂载点。当您创建一个新的端点时,您可以在‘app’字段中定义您想要挂载的应用程序,以及在‘path’字段中定义您想要挂载的路径。
figure:: static/description/endpoint_create.png
FastAPI端点
借助‘fastapi.endpoint’模型,您可以创建任意数量的端点,并在每个端点中挂载任意数量的应用程序。端点也是您定义应用程序配置参数的地方。一个典型的例子是在端点路径访问应用程序时,您想要使用的认证方法。
现在,您可以创建您的第一个路由器。为此,您需要在您的fastapi_endpoint模块中定义一个名为例如‘demo_api_router’的全局变量
from fastapi import APIRouter
from odoo import fields, models
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
app: str = fields.Selection(
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"}
)
# create a router
demo_api_router = APIRouter()
为了使路由器对您的应用程序可用,您需要将其添加到您的fastapi_endpoint模型中的_get_fastapi_routers方法返回的路由器列表中。
from fastapi import APIRouter
from odoo import api, fields, models
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
app: str = fields.Selection(
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"}
)
def _get_fastapi_routers(self):
if self.app == "demo":
return [demo_api_router]
return super()._get_fastapi_routers()
# create a router
demo_api_router = APIRouter()
现在,您可以为您的路由器添加路由。例如,让我们添加一个返回合作伙伴列表的路由。
from typing import Annotated
from fastapi import APIRouter
from pydantic import BaseModel
from odoo import api, fields, models
from odoo.api import Environment
from odoo.addons.fastapi.dependencies import odoo_env
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
app: str = fields.Selection(
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"}
)
def _get_fastapi_routers(self):
if self.app == "demo":
return [demo_api_router]
return super()._get_fastapi_routers()
# create a router
demo_api_router = APIRouter()
class PartnerInfo(BaseModel):
name: str
email: str
@demo_api_router.get("/partners", response_model=list[PartnerInfo])
def get_partners(env: Annotated[Environment, Depends(odoo_env)]) -> list[PartnerInfo]:
return [
PartnerInfo(name=partner.name, email=partner.email)
for partner in env["res.partner"].search([])
]
现在,您可以启动Odoo服务器,安装您的插件并为您的应用程序创建一个新的端点实例。完成后,点击docs URL以访问应用程序的交互式文档。
在尝试测试您的应用程序之前,您需要在端点实例上定义将用于运行应用程序的用户。您可以通过设置‘user_id’字段来完成此操作。此信息是最重要的,因为它构成了您应用程序安全性的基础。您在端点实例中定义的用户将用于运行应用程序并访问数据库。这意味着该用户将能够访问他在Odoo中可以访问的所有数据。为了确保您应用程序的安全性,您应该创建一个新用户,该用户仅用于运行您的应用程序,并且无法访问数据库。
<record
id="my_demo_app_user"
model="res.users"
context="{'no_reset_password': True, 'no_reset_password': True}"
>
<field name="name">My Demo Endpoint User</field>
<field name="login">my_demo_app_user</field>
<field name="groups_id" eval="[(6, 0, [])]" />
</record>
同时,您应该创建一个新的组,该组将用于定义将运行您的应用程序的用户的访问权限。此组应包含预定义的组‘FastAPI Endpoint Runner’。此组定义了用户访问其所属端点实例所需的最小访问权限
访问其自己的用户记录
访问与其用户记录链接的合作伙伴记录
现在,您可以测试您的应用程序。您可以通过点击您已定义的路由的“Try it out”按钮来完成此操作。请求的结果将在“Response”部分显示,并包含合作伙伴列表。
<record id="my_demo_app_group" model="res.groups">
<field name="name">My Demo Endpoint Group</field>
<field name="users" eval="[(4, ref('my_demo_app_user'))]" />
<field name="implied_ids" eval="[(4, ref('fastapi.group_fastapi_endpoint_runner'))]" />
</record>
注意
注意
‘FastAPI Endpoint Runner’组确保用户无法访问上述3种以外的任何信息。这意味着,对于您想要从应用程序访问的每条信息,您都需要创建适当的ACLs和记录规则。(请参阅在路由处理程序中管理安全性)从项目开始到测试阶段,始终使用专用用户和特定组是一个好习惯。这将迫使您为端点定义适当的安全规则。
处理Odoo环境
《odoo.addons.fastapi.dependencies》模块提供了一组函数,您可以使用这些函数将可重用的依赖注入到您的路由中。例如,‘odoo_env’函数返回当前odoo环境。您可以使用它来从您的路由处理程序访问odoo模型和数据库。
from typing import Annotated
from odoo.api import Environment
from odoo.addons.fastapi.dependencies import odoo_env
@demo_api_router.get("/partners", response_model=list[PartnerInfo])
def get_partners(env: Annotated[Environment, Depends(odoo_env)]) -> list[PartnerInfo]:
return [
PartnerInfo(name=partner.name, email=partner.email)
for partner in env["res.partner"].search([])
]
如您所见,您可以使用‘Depends’函数将依赖注入到您的路由处理程序中。该‘Depends’函数由‘fastapi’框架提供。您可以使用它将任何依赖注入到您的路由处理程序中。由于您的处理程序是一个Python函数,获取odoo环境的唯一方法是将它作为依赖注入。fastapi插件提供了一组可以作为依赖使用的函数。
‘odoo_env’:返回当前odoo环境。
‘fastapi_endpoint’:返回当前fastapi端点模型实例。
‘authenticated_partner’:返回已认证的合作伙伴。
‘authenticated_partner_env’:返回包含已认证合作伙伴id的环境的当前odoo环境。
默认情况下,‘odoo_env’和‘fastapi_endpoint’依赖项无需额外工作即可使用。
注意
即使‘odoo_env’和‘authenticated_partner_env’返回当前odoo环境,它们也不相同。‘odoo_env’依赖项返回未修改的环境,而‘authenticated_partner_env’将已认证合作伙伴id添加到环境的上下文中。正如将在专门介绍安全的章节在路由处理程序中管理安全中解释的那样,上下文中已认证合作伙伴id的存在是允许您强制执行端点方法安全性的关键信息。因此,对于所有非公开方法,您应始终使用‘authenticated_partner_env’依赖项而不是‘odoo_env’依赖项。
依赖注入机制
‘odoo_env’依赖项依赖于一个简单的实现,该实现从在请求处理开始时由特定请求调度器初始化的ContextVar变量中检索当前odoo环境。
‘fastapi_endpoint’依赖项依赖于由‘fastapi’模块提供的‘dependency_overrides’机制。(有关依赖项_overrides机制的更多详细信息,请参阅fastapi文档)。如果您查看‘fastapi_endpoint’依赖项的当前实现,您将看到该方法依赖于两个参数:‘endpoint_id’和‘env’。这些参数本身也是依赖项。
def fastapi_endpoint_id() -> int:
"""This method is overriden by default to make the fastapi.endpoint record
available for your endpoint method. To get the fastapi.endpoint record
in your method, you just need to add a dependency on the fastapi_endpoint method
defined below
"""
def fastapi_endpoint(
_id: Annotated[int, Depends(fastapi_endpoint_id)],
env: Annotated[Environment, Depends(odoo_env)],
) -> "FastapiEndpoint":
"""Return the fastapi.endpoint record"""
return env["fastapi.endpoint"].browse(_id)
如您所见,这些依赖项之一是‘fastapi_endpoint_id’依赖项,没有具体的实现。该方法用作在创建fastapi应用程序时必须实现/提供的契约。这就是依赖项_overrides机制的力量所在。
如果您查看‘FastapiEndpoint’模型的‘_get_app’方法,您将看到‘fastapi_endpoint_id’依赖项被通过注册一个返回当前fastapi端点模型实例id的特定方法来覆盖。
def _get_app(self) -> FastAPI:
app = FastAPI(**self._prepare_fastapi_endpoint_params())
for router in self._get_fastapi_routers():
app.include_router(prefix=self.root_path, router=router)
app.dependency_overrides[dependencies.fastapi_endpoint_id] = partial(
lambda a: a, self.id
)
这种机制非常强大,允许您将任何依赖项注入到您的路由处理程序中,并且还可以定义一个抽象依赖项,该依赖项可以被任何其他插件使用,其实施可能取决于端点配置。
身份验证机制
为了使我们的应用程序与特定的认证机制解耦,我们将使用‘authenticated_partner’依赖项。对于‘fastapi_endpoint’,这个依赖项依赖于一个抽象依赖项。
当您定义路由处理程序时,您可以将其作为参数注入到路由处理程序中的‘authenticated_partner’依赖项。
from odoo.addons.base.models.res_partner import Partner
@demo_api_router.get("/partners", response_model=list[PartnerInfo])
def get_partners(
env: Annotated[Environment, Depends(odoo_env)], partner: Annotated[Partner, Depends(authenticated_partner)]
) -> list[PartnerInfo]:
return [
PartnerInfo(name=partner.name, email=partner.email)
for partner in env["res.partner"].search([])
]
在这个阶段,您的处理器尚未绑定到特定的身份验证机制,而是仅期望获得一个作为依赖项的合作伙伴。根据您的需求,您可以为您的应用程序实现不同的身份验证机制。FastAPI 插件提供了一种使用“BasicAuth”方法的默认身份验证机制。此身份验证机制在 “odoo.addons.fastapi.dependencies” 模块中实现,并依赖于 “fastapi.security” 模块提供的功能。
def authenticated_partner(
env: Annotated[Environment, Depends(odoo_env)],
security: Annotated[HTTPBasicCredentials, Depends(HTTPBasic())],
) -> "res.partner":
"""Return the authenticated partner"""
partner = env["res.partner"].search(
[("email", "=", security.username)], limit=1
)
if not partner:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Basic"},
)
if not partner.check_password(security.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Basic"},
)
return partner
如您所见,“authenticated_partner” 依赖项依赖于由 “fastapi.security” 模块提供的 “HTTPBasic” 依赖项。在这个示例实现中,我们只是检查提供的凭证是否可用于在 Odoo 中验证用户身份。如果身份验证成功,我们返回与验证用户关联的合作伙伴记录。
在某些情况下,您可能希望实现一个更复杂的身份验证机制,该机制可以依赖于令牌或会话。在这种情况下,您可以通过注册一个返回已验证合作伙伴的特定方法来覆盖 “authenticated_partner” 依赖项。此外,您还可以在 FastAPI 端点模型实例上使其可配置。
要实现它,您只需为您的每个身份验证机制实现一个特定方法,并允许用户在创建新的 FastAPI 端点时选择这些方法之一。假设我们想允许通过 API 密钥或基本身份验证进行身份验证。由于基本身份验证已经实现,我们只需实现 API 密钥身份验证机制。
from fastapi.security import APIKeyHeader
def api_key_based_authenticated_partner_impl(
api_key: Annotated[str, Depends(
APIKeyHeader(
name="api-key",
description="In this demo, you can use a user's login as api key.",
)
)],
env: Annotated[Environment, Depends(odoo_env)],
) -> Partner:
"""A dummy implementation that look for a user with the same login
as the provided api key
"""
partner = env["res.users"].search([("login", "=", api_key)], limit=1).partner_id
if not partner:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect API Key"
)
return partner
对于“BasicAuth”身份验证机制,我们同样依赖于 “fastapi.security” 模块提供的原生安全依赖项。
现在我们已经为我们的两种身份验证机制提供了实现,我们可以通过在 FastAPI 端点模型上添加选择字段来允许用户选择这些身份验证机制之一。
from odoo import fields, models
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
app: str = fields.Selection(
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"}
)
demo_auth_method = fields.Selection(
selection=[("api_key", "Api Key"), ("http_basic", "HTTP Bacic")],
string="Authenciation method",
)
注意
一个好的做法是在您应用程序的特定配置字段前加上您的应用程序名称。这将避免在其他“app”扩展“fastapi.endpoint”模型时与其他应用程序发生冲突。
现在我们已经有一个允许用户选择身份验证方法的选项字段,我们可以在应用程序实例化时使用依赖项覆盖机制来提供正确的 “authenticated_partner” 依赖项实现。
from odoo.addons.fastapi.dependencies import authenticated_partner
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
app: str = fields.Selection(
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"}
)
demo_auth_method = fields.Selection(
selection=[("api_key", "Api Key"), ("http_basic", "HTTP Bacic")],
string="Authenciation method",
)
def _get_app(self) -> FastAPI:
app = super()._get_app()
if self.app == "demo":
# Here we add the overrides to the authenticated_partner_impl method
# according to the authentication method configured on the demo app
if self.demo_auth_method == "http_basic":
authenticated_partner_impl_override = (
authenticated_partner_from_basic_auth_user
)
else:
authenticated_partner_impl_override = (
api_key_based_authenticated_partner_impl
)
app.dependency_overrides[
authenticated_partner_impl
] = authenticated_partner_impl_override
return app
要了解依赖项覆盖机制的工作原理,您可以查看 FastAPI 插件提供的示例应用程序。如果您在 FastAPI 端点表单视图中选择“demo”应用程序,您将看到身份验证方法是可配置的。您还可以看到,根据配置在 FastAPI 端点上的身份验证方法,文档将发生变化。
注意
截至撰写本文时,依赖项覆盖机制不受 FastAPI 文档生成器的支持。一个修复方案已经提出并等待合并。您可以在 github 上跟踪修复方案的进度。
管理应用程序的配置参数
正如我们在上一节中看到的,您可以在 FastAPI 端点模型上添加配置字段,允许用户配置您的应用程序(就像为任何扩展的 Odoo 模型一样)。当您需要在路由处理器中访问这些配置字段时,您可以使用 “odoo.addons.fastapi.dependencies.fastapi_endpoint” 依赖项方法检索与当前请求关联的“fastapi.endpoint”记录。
from pydantic import BaseModel, Field
from odoo.addons.fastapi.dependencies import fastapi_endpoint
class EndpointAppInfo(BaseModel):
id: str
name: str
app: str
auth_method: str = Field(alias="demo_auth_method")
root_path: str
model_config = ConfigDict(from_attributes=True)
@demo_api_router.get(
"/endpoint_app_info",
response_model=EndpointAppInfo,
dependencies=[Depends(authenticated_partner)],
)
async def endpoint_app_info(
endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)],
) -> EndpointAppInfo:
"""Returns the current endpoint configuration"""
# This method show you how to get access to current endpoint configuration
# It also show you how you can specify a dependency to force the security
# even if the method doesn't require the authenticated partner as parameter
return EndpointAppInfo.model_validate(endpoint)
FastAPI 端点的某些配置字段可能会影响应用程序的实例化方式。例如,在上一节中,我们看到了配置在“fastapi.endpoint”记录上的身份验证方法被用来提供当应用程序实例化时正确的 “authenticated_partner” 实现。为确保在修改用于应用程序实例化的配置元素时应用程序被重新实例化,您必须覆盖 “_fastapi_app_fields” 方法,将影响应用程序实例化的字段名称添加到返回的列表中。
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
app: str = fields.Selection(
selection_add=[("demo", "Demo Endpoint")], ondelete={"demo": "cascade"}
)
demo_auth_method = fields.Selection(
selection=[("api_key", "Api Key"), ("http_basic", "HTTP Bacic")],
string="Authenciation method",
)
@api.model
def _fastapi_app_fields(self) -> List[str]:
fields = super()._fastapi_app_fields()
fields.append("demo_auth_method")
return fields
处理语言
FastAPI 插件通过解析请求中的 Accept-Language 标头来确定要使用的语言。这种解析遵循 RFC 7231 规范。这意味着语言由头部中第一个被 Odoo 支持的语言(注意优先级顺序)确定。如果头部中没有找到语言,则使用 Odoo 默认语言。然后使用这种语言初始化由路由处理程序使用的 Odoo 环境上下文。这一切使得语言管理变得非常简单。您无需担心。此功能还默认记录在应用程序生成的 openapi 文档中,以指导 API 消费者如何请求特定语言。
如何扩展现有应用程序
当您开发 FastAPI 应用程序时,在原生 Python 应用程序中无法扩展现有应用程序。这种限制不适用于 FastAPI 插件,因为 FastAPI 端点模型被设计为可扩展的。然而,扩展现有应用程序的方法与扩展 Odoo 模型的方法不同。
首先,重要的是要记住,当您定义一个路由时,您实际上是在定义客户端和服务器之间的一个合同。这个合同由路由路径、方法(GET、POST、PUT、DELETE 等)、参数和响应定义。如果您想扩展现有应用程序,您必须确保合同不被破坏。任何对合同的更改都将遵循 Liskov 替换原则。这意味着客户端不应受到更改的影响。
这实际上意味着您不能更改现有路由的路径或方法。您不能更改参数的名称或响应的类型。您不能添加新的参数或新的响应。您不能删除参数或响应。如果您想更改合同,您必须创建一个新的路由。
您能改变什么?
您可以更改路由处理程序的实现。
您可以覆盖路由处理程序的依赖关系。
您可以添加新的路由处理程序。
您可以扩展作为路由处理程序参数或响应使用的模型。
让我们看看如何做到这一点。
更改路由处理器的实现
假设您想更改路由处理程序 ‘/demo/echo’ 的实现。由于路由处理程序只是一个 Python 方法,这可能会显得很繁琐,因为我们不在模型方法中,因此无法利用 Odoo 继承机制。
然而,FastAPI 插件提供了一种实现方式。借助 ‘odoo_env’ 依赖方法,您可以访问当前的 Odoo 环境。有了这个环境,您可以访问注册表以及因此您想要委托实现的模型。如果您想更改路由处理程序 ‘/demo/echo’ 的实现,您只需要从定义实现的模型中继承并覆盖 ‘echo’ 方法。
from pydantic import BaseModel
from fastapi import Depends, APIRouter
from odoo import models
from odoo.addons.fastapi.dependencies import odoo_env
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
def _get_fastapi_routers(self) -> List[APIRouter]:
routers = super()._get_fastapi_routers()
routers.append(demo_api_router)
return routers
demo_api_router = APIRouter()
@demo_api_router.get(
"/echo",
response_model=EchoResponse,
dependencies=[Depends(odoo_env)],
)
async def echo(
message: str,
odoo_env: Annotated[Environment, Depends(odoo_env)],
) -> EchoResponse:
"""Echo the message"""
return EchoResponse(message=odoo_env["demo.fastapi.endpoint"].echo(message))
class EchoResponse(BaseModel):
message: str
class DemoEndpoint(models.AbstractModel):
_name = "demo.fastapi.endpoint"
_description = "Demo Endpoint"
def echo(self, message: str) -> str:
return message
class DemoEndpointInherit(models.AbstractModel):
_inherit = "demo.fastapi.endpoint"
def echo(self, message: str) -> str:
return f"Hello {message}"
注意
将业务逻辑实现在外部路由处理程序中是一种良好的编程实践。这样,您可以轻松测试业务逻辑,而无需测试路由处理程序。在上面的例子中,业务逻辑是在模型 ‘demo.fastapi.endpoint’ 的 ‘echo’ 方法中实现的。路由处理程序只是将实现委托给此方法。
覆盖路由处理器的依赖项
正如您之前所看到的,FastAPI 的依赖注入机制非常强大。通过设计您的路由处理程序依赖于具有特定功能范围的依赖项,您可以在不修改路由处理程序的情况下轻松更改依赖项的实现。采用这种设计,您甚至可以定义必须由具体应用程序实现的抽象依赖项。这就是我们之前示例中的 ‘authenticated_partner’ 依赖项的情况。(您可以在文件 ‘odoo/addons/fastapi/dependencies.py’ 中找到此依赖项的实现,并在文件 ‘odoo/addons/fastapi/models/fastapi_endpoint_demo.py’ 中使用它)
添加新的路由处理器
假设您想添加一个新的路由处理程序 ‘/demo/echo2’。您可能会倾向于在您的新的扩展中通过导入现有应用程序的路由器并向其中添加新的路由处理程序来实现这一点。
from odoo.addons.fastapi.models.fastapi_endpoint_demo import demo_api_router
@demo_api_router.get(
"/echo2",
response_model=EchoResponse,
dependencies=[Depends(odoo_env)],
)
async def echo2(
message: str,
odoo_env: Annotated[Environment, Depends(odoo_env)],
) -> EchoResponse:
"""Echo the message"""
echo = odoo_env["demo.fastapi.endpoint"].echo2(message)
return EchoResponse(message=f"Echo2: {echo}")
这种方法的缺点是,即使应用程序被调用的是未安装您新扩展的不同数据库,您也会无条件地将新的路由处理程序添加到现有应用程序中。
解决方案是在您的新的扩展中定义一个新的路由器,并将其添加到继承自 ‘fastapi.endpoint’ 模型的 ‘_get_fastapi_routers’ 方法返回的路由器列表中。
class FastapiEndpoint(models.Model):
_inherit = "fastapi.endpoint"
def _get_fastapi_routers(self) -> List[APIRouter]:
routers = super()._get_fastapi_routers()
if self.app == "demo":
routers.append(additional_demo_api_router)
return routers
additional_demo_api_router = APIRouter()
@additional_demo_api_router.get(
"/echo2",
response_model=EchoResponse,
dependencies=[Depends(odoo_env)],
)
async def echo2(
message: str,
odoo_env: Annotated[Environment, Depends(odoo_env)],
) -> EchoResponse:
"""Echo the message"""
echo = odoo_env["demo.fastapi.endpoint"].echo2(message)
return EchoResponse(message=f"Echo2: {echo}")
这样,只有当应用程序被调用的是安装了您新扩展的数据库时,新的路由器才会被添加到应用程序的路由器列表中。
扩展用作路由处理器参数或响应的模型
FastAPI Python 库使用 Pydantic 库来定义模型。默认情况下,一旦定义了一个模型,就无法扩展它。然而,一个名为 extendable_pydantic 的配套 Python 库提供了一种使用继承与 Pydantic 模型扩展现有模型的方法。如果单独使用,您需要负责指导此库应用于模型的一组扩展及其应用顺序。这并不方便。幸运的是,存在一个专门的 Odoo 扩展来使此过程完全透明。这个扩展叫做 odoo-addon-extendable-fastapi。
当您想允许其他扩展扩展 Pydantic 模型时,您必须首先使用专用元类将模型定义为可扩展模型。
from pydantic import BaseModel
from extendable_pydantic import ExtendableModelMeta
class Partner(BaseModel, metaclass=ExtendableModelMeta):
name = 0.1
model_config = ConfigDict(from_attributes=True)
与任何其他 Pydantic 模型一样,您现在可以使用此模型作为参数或作为路由处理程序的响应。您还可以使用使用 Pydantic 定义的模型的所有功能。
@demo_api_router.get(
"/partner",
response_model=Location,
dependencies=[Depends(authenticated_partner)],
)
async def partner(
partner: Annotated[ResPartner, Depends(authenticated_partner)],
) -> Partner:
"""Return the location"""
return Partner.model_validate(partner)
如果您需要在模型 ‘Partner’ 中添加新字段,您可以通过定义一个新的模型来扩展它,该模型继承自模型 ‘Partner’。
from typing import Optional
from odoo.addons.fastapi.models.fastapi_endpoint_demo import Partner
class PartnerExtended(Partner, extends=Partner):
email: Optional[str]
如果您的扩展安装在一个数据库中,对路由处理程序 ‘/demo/partner’ 的调用将返回一个包含新字段 ‘email’ 的响应,如果 Odoo 记录提供了该值。
{
"name": "John Doe",
"email": "jhon.doe@acsone.eu"
}
如果您的扩展未安装在一个数据库中,对路由处理程序 ‘/demo/partner’ 的调用将只返回合作伙伴的名称。
{
"name": "John Doe"
}
注意
还必须遵守 Liskov 替代原则。这意味着如果扩展了一个模型,您必须添加新必需字段或为新可选字段提供默认值。
在路由处理器中管理安全性
默认情况下,使用 ‘fastapi.endpoint’ 模型实例上配置的用户处理路由处理程序。默认用户是公共用户。您之前已经看到如何定义将用于强制执行合作伙伴身份验证的依赖项。当方法依赖于此依赖项时,将添加 'authenticated_partner_id' 键到合作伙伴环境的上下文中。(如果您不需要将合作伙伴作为依赖项,但需要获取包含已认证用户的上下文,您可以使用依赖项 'authenticated_partner_env' 而不是 'authenticated_partner'。)
FastAPI 扩展了 'ir.rule' 模型,将包含认证合作伙伴 ID 的键 'authenticated_partner_id' 添加到安全规则的评估上下文中。
如前一部分简短介绍的那样,当你开发一个FastAPI应用程序并且希望以高效且可追踪的方式保护你的数据时,一个好的实践是
创建一个特定于应用程序的新用户,但没有任何访问权限。
为应用程序创建一个特定的安全组并将用户添加到该组中。(此组必须隐含“AFastAPI Endpoint Runner”组,该组提供最小访问权限)
对于每个你想保护的模式
为该模型添加一个‘ir.model.access’记录,允许读取对模型的访问并添加组到记录中。
为该模型创建一个新的‘ir.rule’记录,该记录通过在规则的域中使用键‘authenticated_partner_id’限制对模型记录的访问权限,以仅允许经过身份验证的合作伙伴访问。(如果方法是公共的,则可使用‘fastapi.endpoint’模型实例上定义的用户)
当你需要访问经过身份验证的合作伙伴或确保服务由经过身份验证的合作伙伴调用时,在你的处理程序上添加对‘authenticated_partner’的依赖。
<record
id="my_demo_app_user"
model="res.users"
context="{'no_reset_password': True, 'no_reset_password': True}"
>
<field name="name">My Demo Endpoint User</field>
<field name="login">my_demo_app_user</field>
<field name="groups_id" eval="[(6, 0, [])]" />
</record>
<record id="my_demo_app_group" model="res.groups">
<field name="name">My Demo Endpoint Group</field>
<field name="users" eval="[(4, ref('my_demo_app_user'))]" />
<field name="implied_ids" eval="[(4, ref('group_fastapi_endpoint_runner'))]" />
</record>
<!-- acl for the model 'sale.order' -->
<record id="sale_order_demo_app_access" model="ir.model.access">
<field name="name">My Demo App: access to sale.order</field>
<field name="model_id" ref="model_sale_order"/>
<field name="group_id" ref="my_demo_app_group"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- a record rule to allows the authenticated partner to access only its sale orders -->
<record id="demo_app_sale_order_rule" model="ir.rule">
<field name="name">Sale Order Rule</field>
<field name="model_id" ref="model_sale_order"/>
<field name="domain_force">[('partner_id', '=', authenticated_partner_id)]</field>
<field name="groups" eval="[(4, ref('my_demo_app_group'))]"/>
</record>
如何测试您的fastapi应用程序
多亏了starlette测试客户端,你可以非常简单地测试你的FastAPI应用程序。使用测试客户端,你可以像调用真实的HTTP端点一样调用你的路由处理程序。测试客户端在‘fastapi.testclient’模块中可用。
再次感谢依赖注入机制,它允许你将通常由FastAPI应用程序的正常请求处理提供的依赖项的具体实现注入到测试客户端中。(例如,你可以注入‘authenticated_partner’的模拟来测试你的路由处理程序在合作伙伴未经过身份验证时的行为,你也可以注入对odoo_env等的模拟)
fastapi插件提供了一个用于测试用例的基类,你可以使用它来编写测试。此基类是‘odoo.fastapi.tests.common.FastAPITransactionCase’。此类主要提供了‘_create_test_client’方法,你可以使用它为你的FastAPI应用程序创建测试客户端。此方法封装了测试客户端的创建和依赖项的注入。它还确保odoo环境在路由处理程序的上下文中可用。此方法旨在在需要测试应用程序或需要测试特定路由器时使用(因此,在未提供FastAPI端点的插件中定义路由器的测试很容易)。
使用此基类,编写一个路由处理程序的测试就像
from odoo.fastapi.tests.common import FastAPITransactionCase
from odoo.addons.fastapi import dependencies
from odoo.addons.fastapi.routers import demo_router
class FastAPIDemoCase(FastAPITransactionCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.default_fastapi_running_user = cls.env.ref("fastapi.my_demo_app_user")
cls.default_fastapi_authenticated_partner = cls.env["res.partner"].create({"name": "FastAPI Demo"})
def test_hello_world(self) -> None:
with self._create_test_client(router=demo_router) as test_client:
response: Response = test_client.get("/demo/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertDictEqual(response.json(), {"Hello": "World"})
在上一个示例中,我们为demo_router创建了一个测试客户端。我们可以创建整个应用程序的测试客户端,而不是指定路由器而是指定应用程序。
from odoo.fastapi.tests.common import FastAPITransactionCase
from odoo.addons.fastapi import dependencies
from odoo.addons.fastapi.routers import demo_router
class FastAPIDemoCase(FastAPITransactionCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.default_fastapi_running_user = cls.env.ref("fastapi.my_demo_app_user")
cls.default_fastapi_authenticated_partner = cls.env["res.partner"].create({"name": "FastAPI Demo"})
def test_hello_world(self) -> None:
demo_endpoint = self.env.ref("fastapi.fastapi_endpoint_demo")
with self._create_test_client(app=demo_endpoint._get_app()) as test_client:
response: Response = test_client.get(f"{demo_endpoint.root_path}/demo/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertDictEqual(response.json(), {"Hello": "World"})
开发fastapi应用程序时的总体考虑
开发FastAPI应用程序需要遵循一些好的实践,以确保应用程序健壮且易于维护。以下是一些
路由处理程序必须尽可能简单。它不应包含任何业务逻辑。业务逻辑必须实现在服务层。路由处理程序必须只调用服务层并返回服务层的结果。为了便于业务逻辑的扩展,你的服务层可以实现在其他插件可以继承的Odoo抽象模型中。
路由处理程序不应暴露Odoo的内部数据结构和API。它应提供客户端需要的API。更广泛地说,一个应用程序提供了一组服务,这些服务针对一组特定的用例,这些用例属于一个定义良好的功能域。你必须始终记住,即使你升级Odoo版本或修改业务逻辑,你的API也将保持不变。
路由处理程序是一个工作单元。在设计API时,必须确保使用案例的完整性由单个事务保证。如果您需要执行多个事务来完成一个使用案例,那么您会引入数据不一致或客户端代码额外复杂性的风险。
正确处理错误。当发生错误时,路由处理程序必须返回适当的错误响应。错误响应必须与API的其余部分保持一致。错误响应必须在API文档中记录。默认情况下,‘odoo-addon-fastapi’模块处理在‘odoo.exceptions’模块中定义的常见异常类型,并返回带有相应HTTP状态码的正确错误响应。路由处理程序中的错误必须始终返回一个带有与200不同的HTTP状态码的错误响应。错误响应必须包含一个可供用户显示的可读消息。错误响应还可以包含一个可供客户端用于以特定方式处理错误的机器可读代码。
通过pydantic模型设计JSON文档时,您必须使用适当的数据类型。例如,您必须使用数据类型‘datetime.date’来表示日期而不是字符串。您还必须正确定义字段上的约束。例如,如果字段是可选的,您必须使用数据类型‘typing.Optional’。 pydantic 提供了您定义JSON文档所需的一切。
始终为您的路由处理程序使用适当的pydantic模型作为请求和/或响应。pydantic模型字段的约束必须适用于特定的使用案例。例如,如果您的路由处理程序用于创建销售订单,则pydantic模型不得包含字段‘id’,因为销售订单的id将由路由处理程序生成。但如果id之后是必需的,则响应的pydantic模型必须包含作为必需的字段‘id’。
在您的JSON文档中使用描述性的属性名称。例如,避免使用提供键值对扁平列表的文档。
在您的JSON文档的字段命名中保持一致。例如,如果您使用‘id’表示销售订单的id,您必须使用‘id’来表示所有其他对象的id。
在您的字段命名风格中保持一致。始终首选下划线而不是驼峰式。
始终使用复数来命名包含项目列表的字段。例如,如果您有一个包含销售订单行的字段‘lines’,您必须使用‘lines’而不是‘line’。
如果您没有提供用于检索可用记录列表的特定路由处理程序,则不能期望客户端会提供特定记录在odoo中的标识符(例如承运人的id)。有时,客户端必须与odoo共享特定记录的身份,以便能够执行针对该记录的特定操作(例如,支付处理方式因支付获取者而异)。在这种情况下,您必须提供允许客户端和odoo识别记录的特定属性。支付获取器上的字段‘provider’允许您在odoo中识别特定记录。这种类型的方案允许客户端和odoo在不需要依赖于记录id的情况下识别记录。(这将确保在odoo中更改记录id时(例如,在其他数据库上运行测试时),客户端不会中断。)
始终为同一类型的对象使用相同的名称。例如,如果您有一个包含销售订单行的字段‘lines’,则必须在所有其他JSON文档中为同一类型的对象使用相同的名称。
以相同的方式管理您JSON文档中对象的关联关系。默认情况下,您应该在JSON文档中返回相关对象的id。但这并不总是可行或方便,因此您也可以在JSON文档中返回相关对象。返回相关对象id的主要优势是允许您避免N+1问题。在JSON文档中返回相关对象的主要优势是允许您避免检索相关对象的额外调用。通过考虑每种方法的优缺点,您可以选择最适合您用例的方法。一旦完成,您必须保持管理同一对象关联关系的一致性。
将字段命名为与相应Odoo模型中的字段相同的名称并不总是明智。例如,在表示销售订单的文档中,您不能使用“order_line”来表示销售订单行列表的字段。除了容易混淆且不符合最佳实践外,“order_line”名称也不是自动描述性的。使用“lines”名称要好得多。
保持防御性编程方法。如果您提供了一个返回记录列表的路由处理程序,您必须确保列表的计算时间不要太长或不会耗尽您的服务器资源。例如,对于搜索路由处理程序,您必须确保默认情况下搜索限制在合理的记录数。
前一个点的推论是,搜索处理程序必须始终使用分页机制,并具有合理的默认页面大小。结果列表必须包含在JSON文档中,该文档包含与您的搜索条件匹配的系统中的记录数以及给定页面和大小上的记录列表。
对服务名称使用复数。例如,如果您提供了一个允许您管理销售订单的服务,您必须使用名称“sale_orders”,而不是“sale_order”。
……还有很多。
我们本可以写一本书来讨论设计API时应遵循的最佳实践,但我们在这里就停止。这份清单是我们ACSONE SA/NV经验的结晶,并且随着时间的推移而发展。这是一种我们提供给开始设计API的新开发者的急救包。这个急救包必须与阅读一些有用的资源链接一起使用,如REST指南。在技术层面,fastapi文档提供了大量有用的信息,以及许多示例。最后但并非最不重要的是,pydantic文档也非常有用。
杂项
搜索路由处理器的开发
“odoo-addon-fastapi”模块提供了2个有用的代码片段,帮助您在编写搜索路由的路由处理程序时保持一致性。
一个依赖方法,用于指定所有搜索路由处理程序的分页参数:‘odoo.addons.fastapi.paging’。
一个PagedCollection pydantic模型,用于返回搜索路由处理程序的结果,该结果包含在包含记录计数的JSON文档中。
from typing import Annotated
from pydantic import BaseModel
from odoo.api import Environment
from odoo.addons.fastapi.dependencies import paging, authenticated_partner_env
from odoo.addons.fastapi.schemas import PagedCollection, Paging
class SaleOrder(BaseModel):
id: int
name: str
model_config = ConfigDict(from_attributes=True)
@router.get(
"/sale_orders",
response_model=PagedCollection[SaleOrder],
response_model_exclude_unset=True,
)
def get_sale_orders(
paging: Annotated[Paging, Depends(paging)],
env: Annotated[Environment, Depends(authenticated_partner_env)],
) -> PagedCollection[SaleOrder]:
"""Get the list of sale orders."""
count = env["sale.order"].search_count([])
orders = env["sale.order"].search([], limit=paging.limit, offset=paging.offset)
return PagedCollection[SaleOrder](
count=count,
items=[SaleOrder.model_validate(order) for order in orders],
)
注意
“odoo.addons.fastapi.schemas.Paging”和“odoo.addons.fastapi.schemas.PagedCollection” pydantic模型不是设计为可扩展的,以避免在“odoo-addon-fastapi”模块和“odoo-addon-extendable”模块之间引入依赖关系。
错误处理
错误处理是在设计fastapi与Odoo集成时一个非常重要的主题。默认情况下,在实例化fastapi应用时,fastapi库会声明一个默认的异常处理器,它会捕获由路由处理器引发的任何异常,并返回适当的错误响应。这样做是为了确保应用的服务不会被未处理的异常中断。如果这种实现对于原生fastapi应用来说是有意义的,那么对于fastapi与Odoo的集成来说就不一定适用了。调用Odoo API的事务性是在请求处理过程中由Odoo在根目录实现的。为了确保事务得到正确管理,与Odoo的集成必须确保由路由处理器引发的异常能够正确地传递到Odoo对请求的处理中。这是通过在‘odoo.addons.fastapi.models.error_handlers’模块中对fastapi应用的已注册异常处理器进行猴子补丁来实现的。因此,您将无法在fastapi应用中定义自定义的异常处理器。如果您在应用中添加自定义异常处理器,它将被忽略。
FastAPI插件目录结构
当您开发一个新插件以通过fastapi暴露API时,遵循与API相关的文件相同的目录结构和命名约定是一个好习惯。这将帮助您轻松找到与API相关的文件,并帮助其他开发者理解您的代码。
以下是推荐的目录结构。它基于Python社区在开发fastapi应用时使用的实践。
. ├── x_api │ ├── data │ │ ├── ... .xml │ ├── demo │ │ ├── ... .xml │ ├── i18n │ │ ├── ... .po │ ├── models │ │ ├── __init__.py │ │ ├── fastapi_endpoint.py # your app │ │ └── ... .py │ └── routers │ │ ├── __init__.py │ │ ├── items.py │ │ └── ... .py │ ├── schemas | schemas.py │ │ ├── __init__.py │ │ ├── my_model.py # pydantic model │ │ └── ... .py │ ├── security │ │ ├── ... .xml │ ├── views │ │ ├── ... .xml │ ├── __init__.py │ ├── __manifest__.py │ ├── dependencies.py # custom dependencies │ ├── error_handlers.py # custom error handlers
‘models’目录包含Odoo模型。当您定义一个新应用时,就像其他插件一样,您将在该目录中添加您的模型,该模型继承自‘fastapi.endpoint’模型。
‘routers’目录包含fastapi路由。您将在该目录中添加您的新路由。具有相同前缀的所有路由应分组在同一文件中。例如,所有以‘/items’开头的路由都应定义在‘items.py’文件中。该目录中的‘__init__.py’文件用于导入目录中定义的所有路由,并创建一个全局路由,可以在应用中使用。例如,在您的‘items.py’文件中,您将定义一个路由如下
router = APIRouter(tags=["items"]) router.get("/items", response_model=List[Item]) def list_items(): pass
在‘__init__.py’文件中,您将导入路由并将其添加到全局路由或您的插件中。
from fastapi import APIRouter from .items import router as items_router router = APIRouter() router.include_router(items_router)
‘schemas.py’将用于定义pydantic模型。对于具有许多模型的复杂API,创建一个‘schemas’目录并将模型拆分到不同的文件中会更好。该目录中的‘__init__.py’文件将用于导入目录中定义的所有模型。例如,在您的‘my_model.py’文件中,您将定义一个模型如下
from pydantic import BaseModel class MyModel(BaseModel): name: str description: str = None
在‘__init__.py’文件中,您将导入目录中的文件中的模型类。
from .my_model import MyModel
这将允许从schemas模块始终导入模型,无论模型是分散在多个文件中还是定义在‘schemas.py’文件中。
from x_api_addon.schemas import MyModel
‘dependencies.py’文件包含您将在路由中使用的自定义依赖项。例如,您可以定义一个依赖项来检查用户的访问权限。
‘error_handlers.py’文件包含您将在路由中使用的自定义错误处理器。《Strong>‘odoo-addon-fastapi’模块提供了常见Odoo异常的默认错误处理器。您可能不需要定义自己的错误处理器。但如果您需要这样做,您可以在该文件中定义它们。
接下来是什么?
‘odoo-addon-fastapi’模块仍处于开发初期阶段。它将随着时间的推移而发展,以整合您的反馈并提供缺失的功能。现在,取决于您去尝试它并提供您的反馈。
已知问题/路线图
路线图和已知问题可以在GitHub上找到:路线图 和 已知问题。
FastAPI 模块提供了一个简单的方式来使用WebSockets。遗憾的是,这种支持目前还不适用于 Odoo 框架。挑战很大,因为fastapi的集成依赖于使用特定的中间件,该中间件将Odoo消耗的WSGI请求转换为ASGI请求。问题是是否也可以为WebSockets开发同样的桥梁来流式传输大型响应。
变更日志
17.0.3.0.0 (2024-10-03)
功能
现在在端点模型上出现了一个新参数,允许您禁用创建和存储Odoo用于调用您的应用程序端点的会话文件。这对于防止磁盘空间消耗和I/O操作很有用,如果您的应用程序不需要使用这些会话文件,这些文件主要是由Odoo用来存储已登录用户的会话信息。(#442)
错误修复
修复了带有正文内容的POST请求的重试问题。
在此修复之前,带有正文内容的POST请求的重试会陷入循环并无法完成。这是由于请求输入流在处理请求失败尝试后没有被重置。(#440)
17.0.2.0.0 (2024-10-03)
错误修复
此更改是管理将fastapi应用程序集成到Odoo中的事务方式的全面重写。
在之前的实现中,为了捕获针对fastapi应用程序发出的请求处理中发生的异常并在出错时回滚事务,放置了特定的错误处理器。这是通过使用fastapi应用程序的“add_exception_handler”方法将特定的错误处理器方法注册到fastapi应用程序来完成的。在此实现中,在错误处理器方法中回滚了事务。
由于以下几个原因,这种方法没有按预期工作:
在fastapi级别处理错误阻止了重试机制在数据库并发错误的情况下被触发。这是因为错误在fastapi级别被捕获,并且从未冒泡到请求处理的早期阶段,在该阶段实现了重试机制。
在出错的情况下,环境和注册表的清理没有正确完成。在 ‘odoo.service.model.retrying’ 方法中,您可以看到在数据库引发错误和应用程序引发错误的情况下,清理过程是不同的。
此更改通过确保错误不再在fastapi级别被捕获,并通过事件循环冒泡到WSGI到ASGI转换所需的事件循环中的fastapi处理堆栈来修复这些问题。因此,现在Odoo框架正确地管理了对fastapi应用程序的请求的事务性。
(#422)
17.0.1.0.1 (2024-10-02)
错误修复
修复与最新Odoo版本的不兼容性问题
从 https://github.com/odoo/odoo/commit/cb1d057dcab28cb0b0487244ba99231ee292502e 开始,原始werkzeug HTTPRequest类被包装在一个新类中,以控制开发者使用的属性。这些更改处理了这种新实现,同时也保持了与旧版本的兼容性。(#414)
16.0.1.2.5 (2024-01-17)
错误修复
Odoo进行了更新,现在它在创建和修改ir.rule时检查域。
ir.rule 'Fastapi: Running user rule' 使用一个来自上下文的字段(authenticatepartner_id),这个字段并不总是设置,当Odoo检查域时会导致错误。因此现在它默认设置为False。(#410)
16.0.1.2.3 (2023-12-21)
错误修复
在端点执行中出现异常时,在回滚后关闭数据库游标。
这是为了确保 service/model.py 中的 retrying 方法不尝试将数据刷新到数据库。(#405)
16.0.1.2.2 (2023-12-12)
错误修复
当使用“FastAPITransactionCase”类时,允许将“authenticated_partner_impl”方法的特定覆盖添加到要应用的覆盖列表中。在此更改之前,“overrides”参数中给出的“authenticated_partner_impl”覆盖始终在“FastAPITransactionCase”类的方法“_create_test_client”中被覆盖。现在只有当“authenticated_partner_impl”方法不在要应用的覆盖列表中,并且没有指定特定合作伙伴时,才会进行覆盖。如果在同时指定了“authenticated_partner_impl”方法的覆盖时指定了特定合作伙伴,则会引发错误。(#396)
16.0.1.2.1 (2023-11-03)
错误修复
修复了“PagedCollection”模式中“count”属性的声明中的拼写错误。
由于Pydantic的最近版本将其视为任意参数,拼写错误的参数会触发弃用警告。(#389)
16.0.1.2.0 (2023-10-13)
功能
在“PagedCollection”模式中,将字段“total”替换为字段“count”。字段“total”现在已弃用,将在下一个主要版本中删除。此更改与向后兼容。返回的JSON文档现在将包含字段“total”和“count”,它们具有相同的值。在您的Python代码中,如果使用字段“total”,它将用相同的值填充字段“count”。建议您使用字段“count”而不是“total”,并相应地修改您的代码。(#380)
错误追踪器
错误在GitHub Issues上跟踪。如果在遇到问题,请检查是否已报告您的问题。如果您是第一个发现它的人,请通过提供详细且受欢迎的反馈来帮助我们解决问题。
请不要直接联系贡献者以获取支持或技术问题的帮助。
致谢
贡献者
Laurent Mignon <laurent.mignon@acsone.eu>
维护者
此模块由OCA维护。
OCA,或Odoo社区协会,是一个非营利组织,其使命是支持Odoo功能的协作开发并促进其广泛使用。
当前维护者
此模块是GitHub上的OCA/rest-framework项目的部分。
欢迎您贡献。要了解如何贡献,请访问https://odoo-community.org/page/Contribute。
项目详情
哈希值 for odoo_addon_fastapi-17.0.3.0.0.1-py3-none-any.whl
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 495c233df65e08cee6140cd6f07ab69e8fe477ff44d1fac4b290f262d5b69548 |
|
MD5 | 51d302705672335e2d85481759570463 |
|
BLAKE2b-256 | 2c06f3cfd2647111727c3c549bc58e183bef3589c7497fae2b067a5ca5d564fd |