# 注入(Injection)

依赖注入是一种根据定义的函数签名向响应程序中添加参数的方法。 这在许多情况下很有用,例如:

  • 基于请求头获取对象(如当前 Session 用户)
  • 将某些对象重构为指定的格式
  • 通过 request 对象对数据进行预处理
  • 自动注入服务

当您 扩展 应用程序时,响应程序上会挂载一个 注入 方法。该方法接受以下参数:

  • : 一些独特的类,将成为对象的类型
  • 构造函数(可选): 将返回该类型的函数

让我们通过一些例子来实际体会一下:

# 基本实现(Basic implementation)

最简单的例子就是用它来重写一个值。

如果您想要基于匹配的路径参数生成一个模型,这可能会很有用。

@dataclass
class IceCream:
    flavor: str
    def __str__(self) -> str:
        return f"{self.flavor.title()} (Yum!)"
ext.injection(IceCream)
@app.get("/<flavor:str>")
async def ice_cream(request, flavor: IceCream):
    return text(f"You chose: {flavor}")
$ curl localhost:8000/chocolate
You chose Chocolate (Yum!)

# 附加构造函数(Additional constructors)

有时您可能还需要传递一个构造函数。这可能是一个函数,甚至可能是一个充当构造函数的类方法。在这个例子中,我们正在创建一个名为 Person.create 的注入。

同样需要注意的是,在这个例子中,我们实际上是在注入 两个对象 !当然并不需要这样,但是我们将基于函数签名注入对象。

当构造函数没有传递给 ext.injection 时,将通过调用该类型来创建对象。

@dataclass
class PersonID:
    person_id: int
@dataclass
class Person:
    person_id: PersonID
    name: str
    age: int
    @classmethod
    async def create(cls, request: Request, person_id: int):
        return cls(person_id=PersonID(person_id), name="noname", age=111)
ext.injection(Person, Person.create)
ext.injection(PersonID)
@app.get("/person/<person_id:int>")
async def person_details(
        request: Request, person_id: PersonID, person: Person
):
    return text(f"{person_id}\n{person}")
$ curl localhost:8000/person/123
PersonID(person_id=123)
Person(person_id=PersonID(person_id=123), name='noname', age=111)

# 来自 Request 的对象(Objects from the Request

有时,您可能希望从请求中提取细节并对它们进行预处理。例如,您可以将请求 JSON 转换为 Python 对象,然后基于数据库查询添加一些额外的逻辑。

注意

如果您计划使用这种方法,您应该注意到注入操作实际上是在 Sanic 读取请求体之前发生的。请求头应该已经被处理。因此,如果您确实想要访问请求体,您将需要手动消费,如本例所示。

await request.receive_body()

这可以用于以下情况:

  • 使用中间件对 request.ctx 进行预处理并添加内容
  • 使用装饰器对请求处理程序进行预处理并注入参数
@dataclass
class UserProfile:
    user: User
    age: int = field(default=0)
    email: str = field(default="")
    def __json__(self):
        return ujson.dumps(
            {
                "name": self.user.name,
                "age": self.age,
                "email": self.email,
            }
        )
async def fake_request_to_db(body):
    today = date.today()
    email = f'{body["name"]}@something.com'.lower()
    difference = today - date.fromisoformat(body["birthday"])
    age = int(difference.days / 365)
    return UserProfile(
        User(body["name"]),
        age=age,
        email=email,
    )
async def compile_profile(request: Request):
    await request.receive_body()
    profile = await fake_request_to_db(request.json)
    return profile
ext.injection(UserProfile, compile_profile)
@app.patch("/profile")
async def update_profile(request, profile: UserProfile):
    return json(profile)
$ curl localhost:8000/profile -X PATCH -d '{"name": "Alice", "birthday": "2000-01-01"}'
{
    "name":"Alice",
    "age":21,
    "email":"alice@something.com"
}

# 注入服务(Injecting services)

创建数据库连接池之类的对象并将它们存储在 app.ctx 对象上是一种常见的模式。这使得它们可以在整个应用程序中使用,这是一种非常方便的做法。

但是这样做的缺点是您将不再拥有一个类型化的对象可以使用。您可以通过注入来解决这个问题。

class FakeConnection:
    async def execute(self, query: str, **arguments):
        return "result"
@app.before_server_start
async def setup_db(app, _):
    app.ctx.db_conn = FakeConnection()
def get_db(request: Request):
    return request.app.ctx.db_conn
ext.injection(FakeConnection, get_db)
@app.get("/")
async def handler(request, conn: FakeConnection):
    response = await conn.execute("...")
    return text(response)
$ curl localhost:8000/
result
MIT Licensed
Copyright © 2018-present Sanic Community Organization

~ Made with ❤️ and ☕️ ~