committed by
GitHub
47 changed files with 996 additions and 37 deletions
@ -0,0 +1,101 @@ |
|||
# Usando Dataclasses |
|||
|
|||
FastAPI é construído em cima do **Pydantic**, e eu tenho mostrado como usar modelos Pydantic para declarar requisições e respostas. |
|||
|
|||
Mas o FastAPI também suporta o uso de <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> da mesma forma: |
|||
|
|||
```Python hl_lines="1 7-12 19-20" |
|||
{!../../docs_src/dataclasses/tutorial001.py!} |
|||
``` |
|||
|
|||
Isso ainda é suportado graças ao **Pydantic**, pois ele tem <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">suporte interno para `dataclasses`</a>. |
|||
|
|||
Então, mesmo com o código acima que não usa Pydantic explicitamente, o FastAPI está usando Pydantic para converter essas dataclasses padrão para a versão do Pydantic. |
|||
|
|||
E claro, ele suporta o mesmo: |
|||
|
|||
* validação de dados |
|||
* serialização de dados |
|||
* documentação de dados, etc. |
|||
|
|||
Isso funciona da mesma forma que com os modelos Pydantic. E na verdade é alcançado da mesma maneira por baixo dos panos, usando Pydantic. |
|||
|
|||
/// info | Informação |
|||
|
|||
Lembre-se de que dataclasses não podem fazer tudo o que os modelos Pydantic podem fazer. |
|||
|
|||
Então, você ainda pode precisar usar modelos Pydantic. |
|||
|
|||
Mas se você tem um monte de dataclasses por aí, este é um truque legal para usá-las para alimentar uma API web usando FastAPI. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Dataclasses em `response_model` |
|||
|
|||
Você também pode usar `dataclasses` no parâmetro `response_model`: |
|||
|
|||
```Python hl_lines="1 7-13 19" |
|||
{!../../docs_src/dataclasses/tutorial002.py!} |
|||
``` |
|||
|
|||
A dataclass será automaticamente convertida para uma dataclass Pydantic. |
|||
|
|||
Dessa forma, seu esquema aparecerá na interface de documentação da API: |
|||
|
|||
<img src="/img/tutorial/dataclasses/image01.png"> |
|||
|
|||
## Dataclasses em Estruturas de Dados Aninhadas |
|||
|
|||
Você também pode combinar `dataclasses` com outras anotações de tipo para criar estruturas de dados aninhadas. |
|||
|
|||
Em alguns casos, você ainda pode ter que usar a versão do Pydantic das `dataclasses`. Por exemplo, se você tiver erros com a documentação da API gerada automaticamente. |
|||
|
|||
Nesse caso, você pode simplesmente trocar as `dataclasses` padrão por `pydantic.dataclasses`, que é um substituto direto: |
|||
|
|||
```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } |
|||
{!../../docs_src/dataclasses/tutorial003.py!} |
|||
``` |
|||
|
|||
1. Ainda importamos `field` das `dataclasses` padrão. |
|||
|
|||
2. `pydantic.dataclasses` é um substituto direto para `dataclasses`. |
|||
|
|||
3. A dataclass `Author` inclui uma lista de dataclasses `Item`. |
|||
|
|||
4. A dataclass `Author` é usada como o parâmetro `response_model`. |
|||
|
|||
5. Você pode usar outras anotações de tipo padrão com dataclasses como o corpo da requisição. |
|||
|
|||
Neste caso, é uma lista de dataclasses `Item`. |
|||
|
|||
6. Aqui estamos retornando um dicionário que contém `items`, que é uma lista de dataclasses. |
|||
|
|||
O FastAPI ainda é capaz de <abbr title="converter os dados para um formato que pode ser transmitido">serializar</abbr> os dados para JSON. |
|||
|
|||
7. Aqui o `response_model` está usando uma anotação de tipo de uma lista de dataclasses `Author`. |
|||
|
|||
Novamente, você pode combinar `dataclasses` com anotações de tipo padrão. |
|||
|
|||
8. Note que esta *função de operação de rota* usa `def` regular em vez de `async def`. |
|||
|
|||
Como sempre, no FastAPI você pode combinar `def` e `async def` conforme necessário. |
|||
|
|||
Se você precisar de uma atualização sobre quando usar qual, confira a seção _"Com pressa?"_ na documentação sobre [`async` e `await`](../async.md#in-a-hurry){.internal-link target=_blank}. |
|||
|
|||
9. Esta *função de operação de rota* não está retornando dataclasses (embora pudesse), mas uma lista de dicionários com dados internos. |
|||
|
|||
O FastAPI usará o parâmetro `response_model` (que inclui dataclasses) para converter a resposta. |
|||
|
|||
Você pode combinar `dataclasses` com outras anotações de tipo em muitas combinações diferentes para formar estruturas de dados complexas. |
|||
|
|||
Confira as dicas de anotação no código acima para ver mais detalhes específicos. |
|||
|
|||
## Saiba Mais |
|||
|
|||
Você também pode combinar `dataclasses` com outros modelos Pydantic, herdar deles, incluí-los em seus próprios modelos, etc. |
|||
|
|||
Para saber mais, confira a <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">documentação do Pydantic sobre dataclasses</a>. |
|||
|
|||
## Versão |
|||
|
|||
Isso está disponível desde a versão `0.67.0` do FastAPI. 🔖 |
@ -0,0 +1,205 @@ |
|||
# Recursos Estáticos Personalizados para a UI de Documentação (Hospedagem Própria) |
|||
|
|||
A documentação da API usa **Swagger UI** e **ReDoc**, e cada um deles precisa de alguns arquivos JavaScript e CSS. |
|||
|
|||
Por padrão, esses arquivos são fornecidos por um <abbr title="Content Delivery Network: Um serviço, normalmente composto por vários servidores, que fornece arquivos estáticos, como JavaScript e CSS. É comumente usado para providenciar esses arquivos do servidor mais próximo do cliente, melhorando o desempenho.">CDN</abbr>. |
|||
|
|||
Mas é possível personalizá-los, você pode definir um CDN específico ou providenciar os arquivos você mesmo. |
|||
|
|||
## CDN Personalizado para JavaScript e CSS |
|||
|
|||
Vamos supor que você deseja usar um <abbr title="Content Delivery Network">CDN</abbr> diferente, por exemplo, você deseja usar `https://unpkg.com/`. |
|||
|
|||
Isso pode ser útil se, por exemplo, você mora em um país que restringe algumas URLs. |
|||
|
|||
### Desativar a documentação automática |
|||
|
|||
O primeiro passo é desativar a documentação automática, pois por padrão, ela usa o CDN padrão. |
|||
|
|||
Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`: |
|||
|
|||
```Python hl_lines="8" |
|||
{!../../docs_src/custom_docs_ui/tutorial001.py!} |
|||
``` |
|||
|
|||
### Incluir a documentação personalizada |
|||
|
|||
Agora você pode criar as *operações de rota* para a documentação personalizada. |
|||
|
|||
Você pode reutilizar as funções internas do FastAPI para criar as páginas HTML para a documentação e passar os argumentos necessários: |
|||
|
|||
* `openapi_url`: a URL onde a página HTML para a documentação pode obter o esquema OpenAPI para a sua API. Você pode usar aqui o atributo `app.openapi_url`. |
|||
* `title`: o título da sua API. |
|||
* `oauth2_redirect_url`: você pode usar `app.swagger_ui_oauth2_redirect_url` aqui para usar o padrão. |
|||
* `swagger_js_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **JavaScript**. Este é o URL do CDN personalizado. |
|||
* `swagger_css_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **CSS**. Este é o URL do CDN personalizado. |
|||
|
|||
E de forma semelhante para o ReDoc... |
|||
|
|||
```Python hl_lines="2-6 11-19 22-24 27-33" |
|||
{!../../docs_src/custom_docs_ui/tutorial001.py!} |
|||
``` |
|||
|
|||
/// tip | Dica |
|||
|
|||
A *operação de rota* para `swagger_ui_redirect` é um auxiliar para quando você usa OAuth2. |
|||
|
|||
Se você integrar sua API com um provedor OAuth2, você poderá autenticar e voltar para a documentação da API com as credenciais adquiridas. E interagir com ela usando a autenticação OAuth2 real. |
|||
|
|||
Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse auxiliar de "redirecionamento". |
|||
|
|||
/// |
|||
|
|||
### Criar uma *operação de rota* para testar |
|||
|
|||
Agora, para poder testar se tudo funciona, crie uma *operação de rota*: |
|||
|
|||
```Python hl_lines="36-38" |
|||
{!../../docs_src/custom_docs_ui/tutorial001.py!} |
|||
``` |
|||
|
|||
### Teste |
|||
|
|||
Agora, você deve ser capaz de ir para a documentação em <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, e recarregar a página, ela carregará esses recursos do novo CDN. |
|||
|
|||
## Hospedagem Própria de JavaScript e CSS para a documentação |
|||
|
|||
Hospedar o JavaScript e o CSS pode ser útil se, por exemplo, você precisa que seu aplicativo continue funcionando mesmo offline, sem acesso aberto à Internet, ou em uma rede local. |
|||
|
|||
Aqui você verá como providenciar esses arquivos você mesmo, no mesmo aplicativo FastAPI, e configurar a documentação para usá-los. |
|||
|
|||
### Estrutura de Arquivos do Projeto |
|||
|
|||
Vamos supor que a estrutura de arquivos do seu projeto se pareça com isso: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
``` |
|||
|
|||
Agora crie um diretório para armazenar esses arquivos estáticos. |
|||
|
|||
Sua nova estrutura de arquivos poderia se parecer com isso: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
└── static/ |
|||
``` |
|||
|
|||
### Baixe os arquivos |
|||
|
|||
Baixe os arquivos estáticos necessários para a documentação e coloque-os no diretório `static/`. |
|||
|
|||
Você provavelmente pode clicar com o botão direito em cada link e selecionar uma opção semelhante a `Salvar link como...`. |
|||
|
|||
**Swagger UI** usa os arquivos: |
|||
|
|||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a> |
|||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a> |
|||
|
|||
E o **ReDoc** usa os arquivos: |
|||
|
|||
* <a href="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a> |
|||
|
|||
Depois disso, sua estrutura de arquivos deve se parecer com: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
└── static |
|||
├── redoc.standalone.js |
|||
├── swagger-ui-bundle.js |
|||
└── swagger-ui.css |
|||
``` |
|||
|
|||
### Prover os arquivos estáticos |
|||
|
|||
* Importe `StaticFiles`. |
|||
* "Monte" a instância `StaticFiles()` em um caminho específico. |
|||
|
|||
```Python hl_lines="7 11" |
|||
{!../../docs_src/custom_docs_ui/tutorial002.py!} |
|||
``` |
|||
|
|||
### Teste os arquivos estáticos |
|||
|
|||
Inicialize seu aplicativo e vá para <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>. |
|||
|
|||
Você deverá ver um arquivo JavaScript muito longo para o **ReDoc**. |
|||
|
|||
Esse arquivo pode começar com algo como: |
|||
|
|||
```JavaScript |
|||
/*! |
|||
* ReDoc - OpenAPI/Swagger-generated API Reference Documentation |
|||
* ------------------------------------------------------------- |
|||
* Version: "2.0.0-rc.18" |
|||
* Repo: https://github.com/Redocly/redoc |
|||
*/ |
|||
!function(e,t){"object"==typeof exports&&"object"==typeof m |
|||
|
|||
... |
|||
``` |
|||
|
|||
Isso confirma que você está conseguindo fornecer arquivos estáticos do seu aplicativo e que você colocou os arquivos estáticos para a documentação no local correto. |
|||
|
|||
Agora, podemos configurar o aplicativo para usar esses arquivos estáticos para a documentação. |
|||
|
|||
### Desativar a documentação automática para arquivos estáticos |
|||
|
|||
Da mesma forma que ao usar um CDN personalizado, o primeiro passo é desativar a documentação automática, pois ela usa o CDN padrão. |
|||
|
|||
Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`: |
|||
|
|||
```Python hl_lines="9" |
|||
{!../../docs_src/custom_docs_ui/tutorial002.py!} |
|||
``` |
|||
|
|||
### Incluir a documentação personalizada para arquivos estáticos |
|||
|
|||
E da mesma forma que com um CDN personalizado, agora você pode criar as *operações de rota* para a documentação personalizada. |
|||
|
|||
Novamente, você pode reutilizar as funções internas do FastAPI para criar as páginas HTML para a documentação e passar os argumentos necessários: |
|||
|
|||
* `openapi_url`: a URL onde a página HTML para a documentação pode obter o esquema OpenAPI para a sua API. Você pode usar aqui o atributo `app.openapi_url`. |
|||
* `title`: o título da sua API. |
|||
* `oauth2_redirect_url`: Você pode usar `app.swagger_ui_oauth2_redirect_url` aqui para usar o padrão. |
|||
* `swagger_js_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **JavaScript**. Este é o URL do CDN personalizado. **Este é o URL que seu aplicativo está fornecendo**. |
|||
* `swagger_css_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **CSS**. **Esse é o que seu aplicativo está fornecendo**. |
|||
|
|||
E de forma semelhante para o ReDoc... |
|||
|
|||
```Python hl_lines="2-6 14-22 25-27 30-36" |
|||
{!../../docs_src/custom_docs_ui/tutorial002.py!} |
|||
``` |
|||
|
|||
/// tip | Dica |
|||
|
|||
A *operação de rota* para `swagger_ui_redirect` é um auxiliar para quando você usa OAuth2. |
|||
|
|||
Se você integrar sua API com um provedor OAuth2, você poderá autenticar e voltar para a documentação da API com as credenciais adquiridas. E, então, interagir com ela usando a autenticação OAuth2 real. |
|||
|
|||
Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse auxiliar de "redirect". |
|||
|
|||
/// |
|||
|
|||
### Criar uma *operação de rota* para testar arquivos estáticos |
|||
|
|||
Agora, para poder testar se tudo funciona, crie uma *operação de rota*: |
|||
|
|||
```Python hl_lines="39-41" |
|||
{!../../docs_src/custom_docs_ui/tutorial002.py!} |
|||
``` |
|||
|
|||
### Teste a UI de Arquivos Estáticos |
|||
|
|||
Agora, você deve ser capaz de desconectar o WiFi, ir para a documentação em <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, e recarregar a página. |
|||
|
|||
E mesmo sem Internet, você será capaz de ver a documentação da sua API e interagir com ela. |
@ -0,0 +1,121 @@ |
|||
# Requisições Personalizadas e Classes da APIRoute |
|||
|
|||
Em algum casos, você pode querer sobreescrever a lógica usada pelas classes `Request`e `APIRoute`. |
|||
|
|||
Em particular, isso pode ser uma boa alternativa para uma lógica em um middleware |
|||
|
|||
Por exemplo, se você quiser ler ou manipular o corpo da requisição antes que ele seja processado pela sua aplicação. |
|||
|
|||
/// danger | Perigo |
|||
|
|||
Isso é um recurso "avançado". |
|||
|
|||
Se você for um iniciante em **FastAPI** você deve considerar pular essa seção. |
|||
|
|||
/// |
|||
|
|||
## Casos de Uso |
|||
|
|||
Alguns casos de uso incluem: |
|||
|
|||
* Converter requisições não-JSON para JSON (por exemplo, <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>). |
|||
* Descomprimir corpos de requisição comprimidos com gzip. |
|||
* Registrar automaticamente todos os corpos de requisição. |
|||
|
|||
## Manipulando codificações de corpo de requisição personalizadas |
|||
|
|||
Vamos ver como usar uma subclasse personalizada de `Request` para descomprimir requisições gzip. |
|||
|
|||
E uma subclasse de `APIRoute` para usar essa classe de requisição personalizada. |
|||
|
|||
### Criar uma classe `GzipRequest` personalizada |
|||
|
|||
/// tip | Dica |
|||
|
|||
Isso é um exemplo de brincadeira para demonstrar como funciona, se você precisar de suporte para Gzip, você pode usar o [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank} fornecido. |
|||
|
|||
/// |
|||
|
|||
Primeiro, criamos uma classe `GzipRequest`, que irá sobrescrever o método `Request.body()` para descomprimir o corpo na presença de um cabeçalho apropriado. |
|||
|
|||
Se não houver `gzip` no cabeçalho, ele não tentará descomprimir o corpo. |
|||
|
|||
Dessa forma, a mesma classe de rota pode lidar com requisições comprimidas ou não comprimidas. |
|||
|
|||
```Python hl_lines="8-15" |
|||
{!../../docs_src/custom_request_and_route/tutorial001.py!} |
|||
``` |
|||
|
|||
### Criar uma classe `GzipRoute` personalizada |
|||
|
|||
Em seguida, criamos uma subclasse personalizada de `fastapi.routing.APIRoute` que fará uso do `GzipRequest`. |
|||
|
|||
Dessa vez, ele irá sobrescrever o método `APIRoute.get_route_handler()`. |
|||
|
|||
Esse método retorna uma função. E essa função é o que irá receber uma requisição e retornar uma resposta. |
|||
|
|||
Aqui nós usamos para criar um `GzipRequest` a partir da requisição original. |
|||
|
|||
```Python hl_lines="18-26" |
|||
{!../../docs_src/custom_request_and_route/tutorial001.py!} |
|||
``` |
|||
|
|||
/// note | Detalhes Técnicos |
|||
|
|||
Um `Request` também tem um `request.receive`, que é uma função para "receber" o corpo da requisição. |
|||
|
|||
Um `Request` também tem um `request.receive`, que é uma função para "receber" o corpo da requisição. |
|||
|
|||
O dicionário `scope` e a função `receive` são ambos parte da especificação ASGI. |
|||
|
|||
E essas duas coisas, `scope` e `receive`, são o que é necessário para criar uma nova instância de `Request`. |
|||
|
|||
Para aprender mais sobre o `Request` confira a <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">documentação do Starlette sobre Requests</a>. |
|||
|
|||
/// |
|||
|
|||
A única coisa que a função retornada por `GzipRequest.get_route_handler` faz de diferente é converter o `Request` para um `GzipRequest`. |
|||
|
|||
Fazendo isso, nosso `GzipRequest` irá cuidar de descomprimir os dados (se necessário) antes de passá-los para nossas *operações de rota*. |
|||
|
|||
Depois disso, toda a lógica de processamento é a mesma. |
|||
|
|||
Mas por causa das nossas mudanças em `GzipRequest.body`, o corpo da requisição será automaticamente descomprimido quando for carregado pelo **FastAPI** quando necessário. |
|||
|
|||
## Acessando o corpo da requisição em um manipulador de exceção |
|||
|
|||
/// tip | Dica |
|||
|
|||
Para resolver esse mesmo problema, é provavelmente muito mais fácil usar o `body` em um manipulador personalizado para `RequestValidationError` ([Tratando Erros](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). |
|||
|
|||
Mas esse exemplo ainda é valido e mostra como interagir com os componentes internos. |
|||
|
|||
/// |
|||
|
|||
Também podemos usar essa mesma abordagem para acessar o corpo da requisição em um manipulador de exceção. |
|||
|
|||
Tudo que precisamos fazer é manipular a requisição dentro de um bloco `try`/`except`: |
|||
|
|||
```Python hl_lines="13 15" |
|||
{!../../docs_src/custom_request_and_route/tutorial002.py!} |
|||
``` |
|||
|
|||
Se uma exceção ocorrer, a instância `Request` ainda estará em escopo, então podemos ler e fazer uso do corpo da requisição ao lidar com o erro: |
|||
|
|||
```Python hl_lines="16-18" |
|||
{!../../docs_src/custom_request_and_route/tutorial002.py!} |
|||
``` |
|||
|
|||
## Classe `APIRoute` personalizada em um router |
|||
|
|||
você também pode definir o parametro `route_class` de uma `APIRouter`; |
|||
|
|||
```Python hl_lines="26" |
|||
{!../../docs_src/custom_request_and_route/tutorial003.py!} |
|||
``` |
|||
|
|||
Nesse exemplo, as *operações de rota* sob o `router` irão usar a classe `TimedRoute` personalizada, e terão um cabeçalho extra `X-Response-Time` na resposta com o tempo que levou para gerar a resposta: |
|||
|
|||
```Python hl_lines="13-20" |
|||
{!../../docs_src/custom_request_and_route/tutorial003.py!} |
|||
``` |
@ -0,0 +1,91 @@ |
|||
|
|||
# Extendendo o OpenAPI |
|||
|
|||
Existem alguns casos em que pode ser necessário modificar o esquema OpenAPI gerado. |
|||
|
|||
Nesta seção, você verá como fazer isso. |
|||
|
|||
## O processo normal |
|||
|
|||
O processo normal (padrão) é o seguinte: |
|||
|
|||
Uma aplicação (instância) do `FastAPI` possui um método `.openapi()` que deve retornar o esquema OpenAPI. |
|||
|
|||
Como parte da criação do objeto de aplicação, uma *operação de rota* para `/openapi.json` (ou para o que você definir como `openapi_url`) é registrada. |
|||
|
|||
Ela apenas retorna uma resposta JSON com o resultado do método `.openapi()` da aplicação. |
|||
|
|||
Por padrão, o que o método `.openapi()` faz é verificar se a propriedade `.openapi_schema` tem conteúdo e retorná-lo. |
|||
|
|||
Se não tiver, ele gera o conteúdo usando a função utilitária em `fastapi.openapi.utils.get_openapi`. |
|||
|
|||
E essa função `get_openapi()` recebe como parâmetros: |
|||
|
|||
* `title`: O título do OpenAPI, exibido na documentação. |
|||
* `version`: A versão da sua API, por exemplo, `2.5.0`. |
|||
* `openapi_version`: A versão da especificação OpenAPI utilizada. Por padrão, a mais recente: `3.1.0`. |
|||
* `summary`: Um resumo curto da API. |
|||
* `description`: A descrição da sua API, que pode incluir markdown e será exibida na documentação. |
|||
* `routes`: Uma lista de rotas, que são cada uma das *operações de rota* registradas. Elas são obtidas de `app.routes`. |
|||
|
|||
/// info | Informação |
|||
|
|||
O parâmetro `summary` está disponível no OpenAPI 3.1.0 e superior, suportado pelo FastAPI 0.99.0 e superior. |
|||
|
|||
/// |
|||
|
|||
## Sobrescrevendo os padrões |
|||
|
|||
Com as informações acima, você pode usar a mesma função utilitária para gerar o esquema OpenAPI e sobrescrever cada parte que precisar. |
|||
|
|||
Por exemplo, vamos adicionar <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">Extensão OpenAPI do ReDoc para incluir um logo personalizado</a>. |
|||
|
|||
### **FastAPI** Normal |
|||
|
|||
Primeiro, escreva toda a sua aplicação **FastAPI** normalmente: |
|||
|
|||
```Python hl_lines="1 4 7-9" |
|||
{!../../docs_src/extending_openapi/tutorial001.py!} |
|||
``` |
|||
|
|||
### Gerar o esquema OpenAPI |
|||
|
|||
Em seguida, use a mesma função utilitária para gerar o esquema OpenAPI, dentro de uma função `custom_openapi()`: |
|||
|
|||
```Python hl_lines="2 15-21" |
|||
{!../../docs_src/extending_openapi/tutorial001.py!} |
|||
``` |
|||
|
|||
### Modificar o esquema OpenAPI |
|||
|
|||
Agora, você pode adicionar a extensão do ReDoc, incluindo um `x-logo` personalizado ao "objeto" `info` no esquema OpenAPI: |
|||
|
|||
```Python hl_lines="22-24" |
|||
{!../../docs_src/extending_openapi/tutorial001.py!} |
|||
``` |
|||
|
|||
### Armazenar em cache o esquema OpenAPI |
|||
|
|||
Você pode usar a propriedade `.openapi_schema` como um "cache" para armazenar o esquema gerado. |
|||
|
|||
Dessa forma, sua aplicação não precisará gerar o esquema toda vez que um usuário abrir a documentação da sua API. |
|||
|
|||
Ele será gerado apenas uma vez, e o mesmo esquema armazenado em cache será utilizado nas próximas requisições. |
|||
|
|||
```Python hl_lines="13-14 25-26" |
|||
{!../../docs_src/extending_openapi/tutorial001.py!} |
|||
``` |
|||
|
|||
### Sobrescrever o método |
|||
|
|||
Agora, você pode substituir o método `.openapi()` pela sua nova função. |
|||
|
|||
```Python hl_lines="29" |
|||
{!../../docs_src/extending_openapi/tutorial001.py!} |
|||
``` |
|||
|
|||
### Verificar |
|||
|
|||
Uma vez que você acessar <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>, verá que está usando seu logo personalizado (neste exemplo, o logo do **FastAPI**): |
|||
|
|||
<img src="/docs/en/docs/img/tutorial/extending-openapi/image01.png"> |
@ -0,0 +1,7 @@ |
|||
# Testando a Base de Dados |
|||
|
|||
Você pode estudar sobre bases de dados, SQL e SQLModel na <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">documentação de SQLModel</a>. 🤓 |
|||
|
|||
Aqui tem um mini <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">tutorial de como usar SQLModel com FastAPI</a>. ✨ |
|||
|
|||
Esse tutorial inclui uma sessão sobre <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">testar bases de dados SQL</a>. 😎 |
@ -0,0 +1,184 @@ |
|||
# Modelos de Parâmetros do Cabeçalho |
|||
|
|||
Se você possui um grupo de **parâmetros de cabeçalho** relacionados, você pode criar um **modelo do Pydantic** para declará-los. |
|||
|
|||
Isso vai lhe permitir **reusar o modelo** em **múltiplos lugares** e também declarar validações e metadadados para todos os parâmetros de uma vez. 😎 |
|||
|
|||
/// note | Nota |
|||
|
|||
Isso é possível desde a versão `0.115.0` do FastAPI. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Parâmetros do Cabeçalho com um Modelo Pydantic |
|||
|
|||
Declare os **parâmetros de cabeçalho** que você precisa em um **modelo do Pydantic**, e então declare o parâmetro como `Header`: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="9-14 18" |
|||
{!> ../../docs_src/header_param_models/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="9-14 18" |
|||
{!> ../../docs_src/header_param_models/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="10-15 19" |
|||
{!> ../../docs_src/header_param_models/tutorial001_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.10+ non-Annotated |
|||
|
|||
/// tip | Dica |
|||
|
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="7-12 16" |
|||
{!> ../../docs_src/header_param_models/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ non-Annotated |
|||
|
|||
/// tip | Dica |
|||
|
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="9-14 18" |
|||
{!> ../../docs_src/header_param_models/tutorial001_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip | Dica |
|||
|
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="7-12 16" |
|||
{!> ../../docs_src/header_param_models/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
O **FastAPI** irá **extrair** os dados de **cada campo** a partir dos **cabeçalhos** da requisição e te retornará o modelo do Pydantic que você definiu. |
|||
|
|||
### Checando a documentação |
|||
|
|||
Você pode ver os headers necessários na interface gráfica da documentação em `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/header-param-models/image01.png"> |
|||
</div> |
|||
|
|||
### Proibindo Cabeçalhos adicionais |
|||
|
|||
Em alguns casos de uso especiais (provavelmente não muito comuns), você pode querer **restringir** os cabeçalhos que você quer receber. |
|||
|
|||
Você pode usar a configuração dos modelos do Pydantic para proibir (`forbid`) quaisquer campos `extra`: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../docs_src/header_param_models/tutorial002_an_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../docs_src/header_param_models/tutorial002_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../docs_src/header_param_models/tutorial002_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.10+ non-Annotated |
|||
|
|||
/// tip | Dica |
|||
|
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../docs_src/header_param_models/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ non-Annotated |
|||
|
|||
/// tip | Dica |
|||
|
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../docs_src/header_param_models/tutorial002_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip | Dica |
|||
|
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../docs_src/header_param_models/tutorial002.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Se um cliente tentar enviar alguns **cabeçalhos extra**, eles irão receber uma resposta de **erro**. |
|||
|
|||
Por exemplo, se o cliente tentar enviar um cabeçalho `tool` com o valor `plumbus`, ele irá receber uma resposta de **erro** informando que o parâmetro do cabeçalho `tool` não é permitido: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["header", "tool"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "plumbus", |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Resumo |
|||
|
|||
Você pode utilizar **modelos do Pydantic** para declarar **cabeçalhos** no **FastAPI**. 😎 |
@ -0,0 +1,21 @@ |
|||
# 部署 |
|||
|
|||
部署 **FastAPI** 應用程式相對容易。 |
|||
|
|||
## 部署是什麼意思 |
|||
|
|||
**部署** 應用程式指的是執行一系列必要的步驟,使其能夠 **讓使用者存取和使用**。 |
|||
|
|||
對於一個 **Web API**,部署通常涉及將其放置在 **遠端伺服器** 上,並使用性能優良且穩定的 **伺服器程式**,確保使用者能夠高效、無中斷地存取應用程式,且不會遇到問題。 |
|||
|
|||
這與 **開發** 階段形成鮮明對比,在 **開發** 階段,你會不斷更改程式碼、破壞程式碼、修復程式碼,然後停止和重新啟動伺服器等。 |
|||
|
|||
## 部署策略 |
|||
|
|||
根據你的使用場景和使用工具,有多種方法可以實現此目的。 |
|||
|
|||
你可以使用一些工具自行 **部署伺服器**,你也可以使用能為你完成部分工作的 **雲端服務**,或其他可能的選項。 |
|||
|
|||
我將向你展示在部署 **FastAPI** 應用程式時你可能應該記住的一些主要概念(儘管其中大部分適用於任何其他類型的 Web 應用程式)。 |
|||
|
|||
在接下來的部分中,你將看到更多需要記住的細節以及一些技巧。 ✨ |
@ -0,0 +1,83 @@ |
|||
# FastAPI CLI |
|||
|
|||
**FastAPI CLI** 是一個命令列程式,能用來運行你的 FastAPI 應用程式、管理你的 FastAPI 專案等。 |
|||
|
|||
當你安裝 FastAPI(例如使用 `pip install "fastapi[standard]"`),它會包含一個叫做 `fastapi-cli` 的套件,這個套件提供了 `fastapi` 命令。 |
|||
|
|||
要運行你的 FastAPI 應用程式來進行開發,你可以使用 `fastapi dev` 命令: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u> |
|||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font> |
|||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font> |
|||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files |
|||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> |
|||
|
|||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮ |
|||
│ │ |
|||
│ 🐍 main.py │ |
|||
│ │ |
|||
╰──────────────────────╯ |
|||
|
|||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font> |
|||
<font color="#3465A4">INFO </font> Found importable FastAPI app |
|||
|
|||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮ |
|||
│ │ |
|||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │ |
|||
│ │ |
|||
╰──────────────────────────╯ |
|||
|
|||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font> |
|||
|
|||
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span> |
|||
|
|||
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] |
|||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit) |
|||
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font> |
|||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>] |
|||
<font color="#4E9A06">INFO</font>: Waiting for application startup. |
|||
<font color="#4E9A06">INFO</font>: Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
`fastapi` 命令列程式就是 **FastAPI CLI**。 |
|||
|
|||
FastAPI CLI 接收你的 Python 程式路徑(例如 `main.py`),並自動檢測 FastAPI 實例(通常命名為 `app`),確定正確的引入模組流程,然後運行該應用程式。 |
|||
|
|||
在生產環境,你應該使用 `fastapi run` 命令。 🚀 |
|||
|
|||
**FastAPI CLI** 內部使用了 <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>,這是一個高效能、適合生產環境的 ASGI 伺服器。 😎 |
|||
|
|||
## `fastapi dev` |
|||
|
|||
執行 `fastapi dev` 會啟動開發模式。 |
|||
|
|||
預設情況下,**auto-reload** 功能是啟用的,當你對程式碼進行修改時,伺服器會自動重新載入。這會消耗較多資源,並且可能比禁用時更不穩定。因此,你應該只在開發環境中使用此功能。它也會在 IP 位址 `127.0.0.1` 上監聽,這是用於你的機器與自身通訊的 IP 位址(`localhost`)。 |
|||
|
|||
## `fastapi run` |
|||
|
|||
執行 `fastapi run` 會以生產模式啟動 FastAPI。 |
|||
|
|||
預設情況下,**auto-reload** 功能是禁用的。它也會在 IP 位址 `0.0.0.0` 上監聽,表示會監聽所有可用的 IP 地址,這樣任何能與該機器通訊的人都可以公開存取它。這通常是你在生產環境中運行應用程式的方式,例如在容器中運行時。 |
|||
|
|||
在大多數情況下,你會(也應該)有一個「終止代理」來處理 HTTPS,這取決於你如何部署你的應用程式,你的服務供應商可能會為你做這件事,或者你需要自己設置它。 |
|||
|
|||
/// tip |
|||
|
|||
你可以在[部署文件](deployment/index.md){.internal-link target=_blank}中了解更多相關資訊。 |
|||
|
|||
/// |
@ -0,0 +1,13 @@ |
|||
# 使用指南 - 範例集 |
|||
|
|||
在這裡,你將會看到 **不同主題** 的範例或「如何使用」的指南。 |
|||
|
|||
大多數這些想法都是 **獨立** 的,在大多數情況下,你只需要研究那些直接適用於 **你的專案** 的東西。 |
|||
|
|||
如果有些東西看起來很有趣且對你的專案很有用的話再去讀它,否則你可能可以跳過它們。 |
|||
|
|||
/// tip |
|||
|
|||
如果你想要以結構化的方式 **學習 FastAPI**(推薦),請前往[教學 - 使用者指南](../tutorial/index.md){.internal-link target=_blank}逐章閱讀。 |
|||
|
|||
/// |
@ -0,0 +1,102 @@ |
|||
# 教學 - 使用者指南 |
|||
|
|||
本教學將一步一步展示如何使用 **FastAPI** 及其大多數功能。 |
|||
|
|||
每個部分都是在前一部分的基礎上逐步建置的,但內容結構是按主題分開的,因此你可以直接跳到任何特定的部分,解決你具體的 API 需求。 |
|||
|
|||
它也被設計成可作為未來的參考,讓你隨時回來查看所需的內容。 |
|||
|
|||
## 運行程式碼 |
|||
|
|||
所有程式碼區塊都可以直接複製和使用(它們實際上是經過測試的 Python 檔案)。 |
|||
|
|||
要運行任何範例,請將程式碼複製到 `main.py` 檔案,並使用以下命令啟動 `fastapi dev`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u> |
|||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font> |
|||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font> |
|||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files |
|||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> |
|||
|
|||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮ |
|||
│ │ |
|||
│ 🐍 main.py │ |
|||
│ │ |
|||
╰──────────────────────╯ |
|||
|
|||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font> |
|||
<font color="#3465A4">INFO </font> Found importable FastAPI app |
|||
|
|||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮ |
|||
│ │ |
|||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │ |
|||
│ │ |
|||
╰──────────────────────────╯ |
|||
|
|||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font> |
|||
|
|||
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
|||
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span> |
|||
|
|||
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] |
|||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit) |
|||
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font> |
|||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>] |
|||
<font color="#4E9A06">INFO</font>: Waiting for application startup. |
|||
<font color="#4E9A06">INFO</font>: Application startup complete. |
|||
</pre> |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
**強烈建議** 你編寫或複製程式碼、進行修改並在本地端運行。 |
|||
|
|||
在編輯器中使用它,才能真正體會到 FastAPI 的好處,可以看到你只需編寫少量程式碼,以及所有的型別檢查、自動補齊等功能。 |
|||
|
|||
--- |
|||
|
|||
## 安裝 FastAPI |
|||
|
|||
第一步是安裝 FastAPI。 |
|||
|
|||
確保你建立一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank},啟用它,然後 **安裝 FastAPI**: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "fastapi[standard]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// note |
|||
|
|||
當你使用 `pip install "fastapi[standard]"` 安裝時,會包含一些預設的可選標準相依項。 |
|||
|
|||
如果你不想包含那些可選的相依項,你可以使用 `pip install fastapi` 來安裝。 |
|||
|
|||
/// |
|||
|
|||
## 進階使用者指南 |
|||
|
|||
還有一個 **進階使用者指南** 你可以稍後閱讀。 |
|||
|
|||
**進階使用者指南** 建立在這個教學之上,使用相同的概念,並教你一些額外的功能。 |
|||
|
|||
但首先你應該閱讀 **教學 - 使用者指南**(你正在閱讀的內容)。 |
|||
|
|||
它被設計成你可以使用 **教學 - 使用者指南** 來建立一個完整的應用程式,然後根據你的需求,使用一些額外的想法來擴展它。 |
Loading…
Reference in new issue