almost finished..
This commit is contained in:
parent
c1eb1c06ea
commit
3a78fd393d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.venv
|
||||||
|
db.sqlite3
|
136
README.md
136
README.md
@ -3,7 +3,6 @@
|
|||||||
# OpenAPI3 Framworks Compare
|
# OpenAPI3 Framworks Compare
|
||||||
|
|
||||||
本文会横向对比几种支持OpenAPI文档生成的工具/框架。
|
本文会横向对比几种支持OpenAPI文档生成的工具/框架。
|
||||||
|
|
||||||
OpenAPI Specification(下文简称OAS)定义了一个标准的、语言无关的 RESTful API 接口规范,它可以同时允许开发人员和操作系统查看并理解某个服务的功能,而无需访问源代码,文档或网络流量检查(既方便人类学习和阅读,也方便机器阅读)。正确定义 OAS 后,开发者可以使用最少的实现逻辑来理解远程服务并与之交互。
|
OpenAPI Specification(下文简称OAS)定义了一个标准的、语言无关的 RESTful API 接口规范,它可以同时允许开发人员和操作系统查看并理解某个服务的功能,而无需访问源代码,文档或网络流量检查(既方便人类学习和阅读,也方便机器阅读)。正确定义 OAS 后,开发者可以使用最少的实现逻辑来理解远程服务并与之交互。
|
||||||
|
|
||||||
此外,文档生成工具可以使用 OpenAPI 规范来生成 API 文档,代码生成工具可以生成各种编程语言下的服务端和客户端代码,测试代码和其他用例。OpenAPI官方提供了各种语言的服务端和客户端的代码生成工具,比较著名的如[OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator),当然也有很多优秀的第三方开发者开发的工具。
|
此外,文档生成工具可以使用 OpenAPI 规范来生成 API 文档,代码生成工具可以生成各种编程语言下的服务端和客户端代码,测试代码和其他用例。OpenAPI官方提供了各种语言的服务端和客户端的代码生成工具,比较著名的如[OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator),当然也有很多优秀的第三方开发者开发的工具。
|
||||||
@ -47,46 +46,43 @@ OpenAPI Specification(下文简称OAS)定义了一个标准的、语言无
|
|||||||
|
|
||||||
## 2. 参赛选手介绍
|
## 2. 参赛选手介绍
|
||||||
|
|
||||||
| 语言 | 框架 | Web功能完备 | OpenAPI版本 | Github Stars | 当前版本 | first release date |
|
| 语言 | 框架 | Web功能完备 | 使用OpenAPI版本 | Github Stars | Contributors | Commit Activity | 当前版本 | first release date |
|
||||||
|--------|--------------------------------------------------------------------------|-------------|-------------|--------------------------------------------------------------------------------|------------------------------------------------------------------------------------|--------------------|
|
|--------|--------------------------------------------------------------------------|-------------|-----------------|--------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|--------------------|
|
||||||
| Go | [goa](https://github.com/goadesign/goa) | 是 | 3 |  |  | 2016-08-03 |
|
| Go | [goa](https://github.com/goadesign/goa) | 是 | 3 |  |  |  |  | 2016-08-03 |
|
||||||
| Go | [swag](https://github.com/swaggo/swag) | 否 | 2 |  |  | 2017-11-30 |
|
| Go | [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) | 是 | 2 |  |  |  |  | 2016-07-11 |
|
||||||
| Go | [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) | 是 | 2 |  |  | 2016-07-11 |
|
| Go | [swag](https://github.com/swaggo/swag) | 否 | 2 |  |  |  |  | 2017-11-30 |
|
||||||
| Go | [fizz](https://github.com/wI2L/fizz) | 是 | 3 |  |  | 2019-11-06 |
|
| Go | [fizz](https://github.com/wI2L/fizz) | 是 | 3 |  |  |  |  | 2019-11-06 |
|
||||||
| Python | [Django REST framework](https://github.com/encode/django-rest-framework) | 是 | 3 |  |  | 2011-02-22 |
|
| Python | [Django REST framework](https://github.com/encode/django-rest-framework) | 是 | 3 |  |  |  |  | 2011-02-22 |
|
||||||
| Python | [FastAPI](https://github.com/tiangolo/fastapi) | 是 | 3 |  |  | 2018-12-16 |
|
| Python | [FastAPI](https://github.com/tiangolo/fastapi) | 是 | 3 |  |  |  |  | 2018-12-16 |
|
||||||
| Rust | [Poem](https://github.com/poem-web/poem) | 是 | 3 |  |  | 2021-10-14 |
|
| Rust | [Poem](https://github.com/poem-web/poem) | 是 | 3 |  |  |  |  | 2021-10-14 |
|
||||||
|
|
||||||
## 3. 详细对比
|
## 3. 详细对比
|
||||||
|
|
||||||
所有框架均会从以下几个方面进行讨论
|
所有框架均会从以下几个方面进行讨论
|
||||||
|
> 所有评价均为个人意见,欢迎不同声音:D
|
||||||
|
|
||||||
1. 开发过程
|
1. 开发过程
|
||||||
2. OAS的实现方式: 探究这些工具是如何从代码生成对应sepc文件的,并会导航一些关键代码,方便读者进行深入探究。
|
2. OAS的实现方式: 探究这些工具是如何从代码生成对应sepc文件的,并会导航一些关键代码,方便读者进行深入探究。
|
||||||
3. 优点:该框架的一些特色或者相比其他框架有优势的地方
|
3. 优点:该框架的一些特色或者相比其他框架有优势的地方
|
||||||
4. 缺点:该框架的一些劣势,或者不使用的理由
|
4. 缺点:该框架的一些劣势,或者不使用的理由
|
||||||
|
|
||||||
> 所有评价均为个人意见,欢迎不同声音:D
|
以及下文中所涉及项目的完整代码均保存在本仓库中。
|
||||||
|
|
||||||
|
|
||||||
### 1. Goa
|
### 1. Goa
|
||||||
|
|
||||||
#### 1. 开发过程
|
#### 1. 开发过程
|
||||||
|
|
||||||
1. 安装goa提供的代码生成工具
|
1. 安装goa提供的代码生成工具,创建一个design文件夹,用于描述API
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install goa.design/goa/v3/cmd/goa@v3
|
go install goa.design/goa/v3/cmd/goa@v3
|
||||||
```
|
|
||||||
|
|
||||||
2. 创建一个design文件夹,用于描述API
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p goa_example/design
|
mkdir -p goa_example/design
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 使用goa提供的dsl描述API [desigin.go](go/goa_example/design/design.go)
|
2. 使用goa提供的dsl描述API [desigin.go](go/goa_example/design/design.go)
|
||||||
|
|
||||||
4. 生成代码模板
|
3. 生成代码模板
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
goa gen goa_example/design
|
goa gen goa_example/design
|
||||||
@ -94,7 +90,7 @@ OpenAPI Specification(下文简称OAS)定义了一个标准的、语言无
|
|||||||
|
|
||||||
该步骤会生成gen文件夹中的所有文件,包括design文件中定义的所有接口信息、Model描述、验证方式、Protobuf描述(如果有)等所有相关信息的Go描述
|
该步骤会生成gen文件夹中的所有文件,包括design文件中定义的所有接口信息、Model描述、验证方式、Protobuf描述(如果有)等所有相关信息的Go描述
|
||||||
|
|
||||||
5. (optional) 生成实现的一个example
|
4. (optional) 生成实现的一个example
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
goa example goa_example/design
|
goa example goa_example/design
|
||||||
@ -102,7 +98,7 @@ OpenAPI Specification(下文简称OAS)定义了一个标准的、语言无
|
|||||||
|
|
||||||
该步骤会生成cmd文件夹,包括http server和 grpc server的启动代码
|
该步骤会生成cmd文件夹,包括http server和 grpc server的启动代码
|
||||||
和项目根目录/{service_name}.go的文件,其中包含了各方法的默认实现(fmt.Println())
|
和项目根目录/{service_name}.go的文件,其中包含了各方法的默认实现(fmt.Println())
|
||||||
6. 完成代码实现
|
5. 完成代码实现
|
||||||
|
|
||||||
接下来只需要修改各方法中的具体实现为真实业务逻辑即可[service1.go](go/goa_example/service1.go)
|
接下来只需要修改各方法中的具体实现为真实业务逻辑即可[service1.go](go/goa_example/service1.go)
|
||||||
|
|
||||||
@ -422,7 +418,7 @@ Django REST Framework(以下简称DRF)是著名开源组织[encode](https://www.
|
|||||||
|
|
||||||
#### 1. 开发过程
|
#### 1. 开发过程
|
||||||
|
|
||||||
创建python虚拟环境、Django项目初始化相关的流程由于比较冗长,涉及到的上下文信息比较多,这里就不再赘述,感兴趣可以去看[Django官方文档](https://docs.djangoproject.com/en/4.0/)和[Django REST Framework官方文档](https://www.django-rest-framework.org),我们现在只关注他是怎么描述Schema的。
|
创建python虚拟环境、Django项目初始化相关的流程和上下文信息较多,这里就不再赘述,感兴趣可以去看[Django官方文档](https://docs.djangoproject.com/en/4.0/)和[Django REST Framework官方文档](https://www.django-rest-framework.org),我们现在只关注他是怎么描述Schema的。
|
||||||
|
|
||||||
在DRF中,描述数据model的结构叫做Serializer。官方提供了非常多的内置的序列化方法,包括各种可接受的数据类型,然后用户在定义Serializer的时候使用kwargs为各个参数字段添加约束信息。serializer.Serializer类本身提供了 request → serializer → response 数据流的转化能力。用户只需要继承这些能力即可。例如
|
在DRF中,描述数据model的结构叫做Serializer。官方提供了非常多的内置的序列化方法,包括各种可接受的数据类型,然后用户在定义Serializer的时候使用kwargs为各个参数字段添加约束信息。serializer.Serializer类本身提供了 request → serializer → response 数据流的转化能力。用户只需要继承这些能力即可。例如
|
||||||
|
|
||||||
@ -438,25 +434,41 @@ class SnippetSerializer(serializers.Serializer):
|
|||||||
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
|
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
|
||||||
```
|
```
|
||||||
|
|
||||||
也提供了直接继承Django Model的能力
|
也提供了直接继承Django Model的能力,下面是一个完整的Student CURD的示例
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class Snippet(models.Model):
|
# models.py
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
class Student(models.Model):
|
||||||
title = models.CharField(max_length=100, blank=True, default='')
|
class YearInSchool(models.TextChoices):
|
||||||
code = models.TextField()
|
FRESHMAN = "FR", "Freshman"
|
||||||
linenos = models.BooleanField(default=False)
|
SOPHOMORE = "SO", "Sophomore"
|
||||||
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
|
JUNIOR = "JR", "Junior"
|
||||||
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
|
SENIOR = "SR", "Senior"
|
||||||
|
GRADUATE = "GR", "Graduate"
|
||||||
|
name = models.CharField(max_length=100, help_text="学生姓名")
|
||||||
|
year_in_school = models.CharField(
|
||||||
|
max_length=2,
|
||||||
|
choices=YearInSchool.choices,
|
||||||
|
default=YearInSchool.FRESHMAN,
|
||||||
|
help_text="该学生所在的学年",
|
||||||
|
)
|
||||||
|
date_of_birth = models.DateField(help_text="该学生的生日")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['created']
|
ordering = ["created_at"]
|
||||||
|
|
||||||
|
# serializers.py
|
||||||
class SnippetSerializer(serializers.ModelSerializer):
|
class StudentSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Snippet
|
model = Student
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
# views.py
|
||||||
|
class StudentView(viewsets.ModelViewSet):
|
||||||
|
serializer_class = StudentSerializer
|
||||||
|
queryset = Student.objects.all()
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. OAS的实现方式
|
#### 2. OAS的实现方式
|
||||||
@ -486,37 +498,7 @@ class SnippetSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
##### 1. 编写业务逻辑main.py
|
##### 1. 编写业务逻辑main.py
|
||||||
|
|
||||||
```python
|
查看[main.py](./python/fastapi_example/main.py)
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
class Item(BaseModel):
|
|
||||||
id: Optional[int] = None
|
|
||||||
name: str = Field(description="The name of the item", regex="^[a-zA-Z0-9]*$")
|
|
||||||
price: float
|
|
||||||
is_offer: Optional[bool] = None
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
def read_root():
|
|
||||||
return {"Hello": "World"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/items/{item_id}")
|
|
||||||
def read_item(item_id: int, q: Optional[str] = None):
|
|
||||||
return {"item_id": item_id, "q": q}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/items")
|
|
||||||
def create_item(item: Item) -> Item:
|
|
||||||
return item
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
##### 2. 使用uvicorn启动服务
|
##### 2. 使用uvicorn启动服务
|
||||||
|
|
||||||
@ -526,33 +508,45 @@ uvicorn main:app
|
|||||||
|
|
||||||
#### 2. OAS的实现方式
|
#### 2. OAS的实现方式
|
||||||
|
|
||||||
通过Python提供的类型注解功能,程序在启动的时候,就能够得到所有入参内容、和返回结果,包括他们的类型信息。开发体验更像是直接写好处理逻辑的函数,然后框架把这些函数按照一定的规则用HTTP的形式实现。
|
通过Python提供的类型注解功能,程序在启动的时候,在装饰器的地方就能够得到所装饰的函数的所有入参内容、和返回结果,以此为依据生成加上路由信息,和装饰器内提供的其他参数信息,一起生成OpenAPI中所需的内容。
|
||||||
|
包括他们的类型信息。开发体验更像是直接写好处理逻辑的函数,然后框架把这些函数按照一定的规则用HTTP的形式实现。
|
||||||
|
|
||||||
#### 3. 优点
|
#### 3. 优点
|
||||||
|
|
||||||
1. 所见即所得,开发接口的时候非常直观,效率很高,而且提供了十分丰富的其他功能使得参数可以复用。
|
1. 所见即所得,开发接口的时候非常直观,效率很高,而且提供了十分丰富的其他功能使得参数可以复用。
|
||||||
2. 设计清晰易懂,用户层面不需要知道太多细节,不强制添加对应的类型注解(当然对应的也就无法得到足够的文档信息)
|
2. 设计清晰易懂,用户层面不需要知道太多细节,用户可以按需要添加对应的类型注解。
|
||||||
3. 可以直接利用python语言提供的强大生态资源。
|
3. 可以直接利用python语言提供的强大生态资源。
|
||||||
|
|
||||||
#### 4. 缺点
|
#### 4. 缺点
|
||||||
|
|
||||||
1. 除了语言与公司技术栈不太相符外,找不到明显的缺点。
|
1. 与公司技术栈不太匹配。
|
||||||
|
|
||||||
### 7. Poem
|
### 7. Poem
|
||||||
|
|
||||||
|
该框架是当前Rust语言下百花齐放的各种异步Web框架中的第一个支持OpenAPI的框架。虽然发布时间不久,但是已经提供了比较完善的支持。
|
||||||
|
|
||||||
#### 1. 开发过程
|
#### 1. 开发过程
|
||||||
|
|
||||||
|
这里采用官方用例提供的一个模拟用户CURD的例子
|
||||||
|
查看代码[main.rs](./rust/poem/src/main.rs)
|
||||||
|
|
||||||
#### 2. OAS的实现方式
|
#### 2. OAS的实现方式
|
||||||
|
|
||||||
WIP
|
1. 字段属性、路由信息采用poem-openapi-derive库提供的宏来描述
|
||||||
|
2. 函数的入参和返回用于判断接口的Request和Response,这里有点类似FastAPI,只不过python用的类型注解来判断参数类型,rust使用的是泛型。
|
||||||
|
3. 编译的时候,这些宏在全部展开,配合[Doc Comment](https://doc.rust-lang.org/rust-by-example/meta/doc.html)可以得到OpenAPI所需的全部信息。
|
||||||
|
|
||||||
|
|
||||||
#### 3. 优点
|
#### 3. 优点
|
||||||
|
|
||||||
WIP
|
1. Rust语言提供零成本抽象能力、零GC、保证内存安全等特性使其性能非常优异。而且语言为用户提供了非常丰富的抽象能力、和特性。
|
||||||
|
2. 框架本身提供的宏非常简洁。Go1.18宏正式启用后,可以调研是否可以借鉴思想。
|
||||||
|
|
||||||
|
|
||||||
#### 4. 缺点
|
#### 4. 缺点
|
||||||
|
|
||||||
WIP
|
1. Rust的学习成本比起Go语言要高,市场上相关人才比较少。
|
||||||
|
2. 框架太年轻了,需要经过时间和开发者的考验。
|
||||||
|
|
||||||
## 4. FAQ
|
## 4. FAQ
|
||||||
|
|
||||||
|
0
python/drf_example/drf_example/animals/__init__.py
Normal file
0
python/drf_example/drf_example/animals/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3
python/drf_example/drf_example/animals/admin.py
Normal file
3
python/drf_example/drf_example/animals/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
python/drf_example/drf_example/animals/apps.py
Normal file
6
python/drf_example/drf_example/animals/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AnimalsConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "animals"
|
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 4.0.3 on 2022-03-10 10:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Student',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='学生姓名', max_length=100)),
|
||||||
|
('year_in_school', models.CharField(choices=[('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate')], default='FR', help_text='该学生所在的学年', max_length=2)),
|
||||||
|
('date_of_birth', models.DateField(help_text='该学生的生日')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
Binary file not shown.
Binary file not shown.
27
python/drf_example/drf_example/animals/models.py
Normal file
27
python/drf_example/drf_example/animals/models.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
|
||||||
|
class YearInSchool(models.TextChoices):
|
||||||
|
FRESHMAN = "FR", "Freshman"
|
||||||
|
SOPHOMORE = "SO", "Sophomore"
|
||||||
|
JUNIOR = "JR", "Junior"
|
||||||
|
SENIOR = "SR", "Senior"
|
||||||
|
GRADUATE = "GR", "Graduate"
|
||||||
|
|
||||||
|
|
||||||
|
class Student(models.Model):
|
||||||
|
name = models.CharField(max_length=100, help_text="学生姓名")
|
||||||
|
year_in_school = models.CharField(
|
||||||
|
max_length=2,
|
||||||
|
choices=YearInSchool.choices,
|
||||||
|
default=YearInSchool.FRESHMAN,
|
||||||
|
help_text="该学生所在的学年",
|
||||||
|
)
|
||||||
|
date_of_birth = models.DateField(help_text="该学生的生日")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["created_at"]
|
8
python/drf_example/drf_example/animals/serializers.py
Normal file
8
python/drf_example/drf_example/animals/serializers.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Student
|
||||||
|
|
||||||
|
|
||||||
|
class StudentSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Student
|
||||||
|
fields = "__all__"
|
3
python/drf_example/drf_example/animals/tests.py
Normal file
3
python/drf_example/drf_example/animals/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
8
python/drf_example/drf_example/animals/views.py
Normal file
8
python/drf_example/drf_example/animals/views.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
from .serializers import StudentSerializer
|
||||||
|
from .models import Student
|
||||||
|
|
||||||
|
|
||||||
|
class StudentView(viewsets.ModelViewSet):
|
||||||
|
serializer_class = StudentSerializer
|
||||||
|
queryset = Student.objects.all()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
16
python/drf_example/drf_example/drf_example/asgi.py
Normal file
16
python/drf_example/drf_example/drf_example/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for drf_example project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_example.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
125
python/drf_example/drf_example/drf_example/settings.py
Normal file
125
python/drf_example/drf_example/drf_example/settings.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"""
|
||||||
|
Django settings for drf_example project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.0.3.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "django-insecure-gt64$h6a$cmx_7&)g2bs=h1q@)-a_cq)vq(zit%mj#2xnch9t*"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"rest_framework",
|
||||||
|
"animals",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "drf_example.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [BASE_DIR / "templates"],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "drf_example.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
44
python/drf_example/drf_example/drf_example/urls.py
Normal file
44
python/drf_example/drf_example/drf_example/urls.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"""drf_example URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/4.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
from rest_framework import routers
|
||||||
|
from rest_framework.schemas import get_schema_view
|
||||||
|
from animals.views import StudentView
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register("student", StudentView)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", include(router.urls)),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path(
|
||||||
|
"swagger/",
|
||||||
|
TemplateView.as_view(
|
||||||
|
template_name="swagger.html",
|
||||||
|
extra_context={"schema_url": "openapi-schema"},
|
||||||
|
),
|
||||||
|
name="swagger-ui",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"openapi",
|
||||||
|
get_schema_view(
|
||||||
|
title="Your Project", description="API for all things …", version="1.0.0"
|
||||||
|
),
|
||||||
|
name="openapi-schema",
|
||||||
|
),
|
||||||
|
]
|
16
python/drf_example/drf_example/drf_example/wsgi.py
Normal file
16
python/drf_example/drf_example/drf_example/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for drf_example project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_example.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
22
python/drf_example/drf_example/manage.py
Executable file
22
python/drf_example/drf_example/manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_example.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
176
python/drf_example/drf_example/openapi-schema.yml
Normal file
176
python/drf_example/drf_example/openapi-schema.yml
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
openapi: 3.0.2
|
||||||
|
info:
|
||||||
|
title: ''
|
||||||
|
version: ''
|
||||||
|
paths:
|
||||||
|
/student/:
|
||||||
|
get:
|
||||||
|
operationId: listStudents
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- student
|
||||||
|
post:
|
||||||
|
operationId: createStudent
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- student
|
||||||
|
/student/{id}/:
|
||||||
|
get:
|
||||||
|
operationId: retrieveStudent
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: A unique integer value identifying this student.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- student
|
||||||
|
put:
|
||||||
|
operationId: updateStudent
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: A unique integer value identifying this student.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- student
|
||||||
|
patch:
|
||||||
|
operationId: partialUpdateStudent
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: A unique integer value identifying this student.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Student'
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- student
|
||||||
|
delete:
|
||||||
|
operationId: destroyStudent
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: A unique integer value identifying this student.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- student
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Student:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: "\u5B66\u751F\u59D3\u540D"
|
||||||
|
maxLength: 100
|
||||||
|
year_in_school:
|
||||||
|
enum:
|
||||||
|
- FR
|
||||||
|
- SO
|
||||||
|
- JR
|
||||||
|
- SR
|
||||||
|
- GR
|
||||||
|
type: string
|
||||||
|
description: "\u8BE5\u5B66\u751F\u6240\u5728\u7684\u5B66\u5E74"
|
||||||
|
date_of_birth:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
description: "\u8BE5\u5B66\u751F\u7684\u751F\u65E5"
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- date_of_birth
|
31
python/drf_example/drf_example/templates/swagger.html
Normal file
31
python/drf_example/drf_example/templates/swagger.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Swagger</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: "{% url schema_url %}",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
layout: "BaseLayout",
|
||||||
|
requestInterceptor: (request) => {
|
||||||
|
request.headers['X-CSRFToken'] = "{{ csrf_token }}"
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
149
python/drf_example/poetry.lock
generated
Normal file
149
python/drf_example/poetry.lock
generated
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
[[package]]
|
||||||
|
name = "asgiref"
|
||||||
|
version = "3.5.0"
|
||||||
|
description = "ASGI specs, helper code, and adapters"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django"
|
||||||
|
version = "4.0.3"
|
||||||
|
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
asgiref = ">=3.4.1,<4"
|
||||||
|
sqlparse = ">=0.2.2"
|
||||||
|
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||||
|
bcrypt = ["bcrypt"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "djangorestframework"
|
||||||
|
version = "3.13.1"
|
||||||
|
description = "Web APIs for Django, made easy."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
django = ">=2.2"
|
||||||
|
pytz = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytz"
|
||||||
|
version = "2021.3"
|
||||||
|
description = "World timezone definitions, modern and historical"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0"
|
||||||
|
description = "YAML parser and emitter for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlparse"
|
||||||
|
version = "0.4.2"
|
||||||
|
description = "A non-validating SQL parser."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2021.5"
|
||||||
|
description = "Provider of IANA time zone data"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uritemplate"
|
||||||
|
version = "4.1.1"
|
||||||
|
description = "Implementation of RFC 6570 URI Templates"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "1.1"
|
||||||
|
python-versions = "^3.10"
|
||||||
|
content-hash = "e74b47a6538a1b28c9531278ec4708c89167a7cd1e406f17c9f0cc029c15d115"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
asgiref = [
|
||||||
|
{file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"},
|
||||||
|
{file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"},
|
||||||
|
]
|
||||||
|
django = [
|
||||||
|
{file = "Django-4.0.3-py3-none-any.whl", hash = "sha256:1239218849e922033a35d2a2f777cb8bee18bd725416744074f455f34ff50d0c"},
|
||||||
|
{file = "Django-4.0.3.tar.gz", hash = "sha256:77ff2e7050e3324c9b67e29b6707754566f58514112a9ac73310f60cd5261930"},
|
||||||
|
]
|
||||||
|
djangorestframework = [
|
||||||
|
{file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"},
|
||||||
|
{file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"},
|
||||||
|
]
|
||||||
|
pytz = [
|
||||||
|
{file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
|
||||||
|
{file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
|
||||||
|
]
|
||||||
|
pyyaml = [
|
||||||
|
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||||
|
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||||
|
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
|
||||||
|
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
|
||||||
|
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
|
||||||
|
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
|
||||||
|
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
|
||||||
|
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
|
||||||
|
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
|
||||||
|
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
|
||||||
|
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
|
||||||
|
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
|
||||||
|
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
|
||||||
|
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
|
||||||
|
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
|
||||||
|
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
|
||||||
|
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
|
||||||
|
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
|
||||||
|
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
|
||||||
|
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
|
||||||
|
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
|
||||||
|
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
|
||||||
|
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
|
||||||
|
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
|
||||||
|
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
|
||||||
|
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
|
||||||
|
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
|
||||||
|
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
|
||||||
|
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
|
||||||
|
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
|
||||||
|
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
|
||||||
|
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||||
|
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||||
|
]
|
||||||
|
sqlparse = [
|
||||||
|
{file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
|
||||||
|
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
|
||||||
|
]
|
||||||
|
tzdata = [
|
||||||
|
{file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"},
|
||||||
|
{file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"},
|
||||||
|
]
|
||||||
|
uritemplate = [
|
||||||
|
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
|
||||||
|
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
|
||||||
|
]
|
18
python/drf_example/pyproject.toml
Normal file
18
python/drf_example/pyproject.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "drf_example"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["CaptainNEO <tianpengfei@rcrai.com>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.10"
|
||||||
|
Django = "^4.0.3"
|
||||||
|
djangorestframework = "^3.13.1"
|
||||||
|
PyYAML = "^6.0"
|
||||||
|
uritemplate = "^4.1.1"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
@ -13,16 +13,34 @@ class Item(BaseModel):
|
|||||||
is_offer: Optional[bool] = None
|
is_offer: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
db = {
|
||||||
|
1: Item(id=1, name="Foo", price=19.99),
|
||||||
|
2: Item(id=2, name="Bar", price=29.99),
|
||||||
|
3: Item(id=3, name="Baz", price=39.99),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def read_root():
|
def get_items(
|
||||||
return {"Hello": "World"}
|
limit: int = 10,
|
||||||
|
offset: int = Field(0, description="db offset"),
|
||||||
|
q: Optional[str] = None,
|
||||||
|
):
|
||||||
|
print(limit, offset, q)
|
||||||
|
return [
|
||||||
|
{"item": db[k], "price": db[k].price}
|
||||||
|
for k in sorted(db.keys())[offset : offset + limit]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@app.get("/items/{item_id}")
|
@app.get("/items/{item_id}")
|
||||||
def read_item(item_id: int, q: Optional[str] = None):
|
def read_item(item_id: int):
|
||||||
return {"item_id": item_id, "q": q}
|
return db[item_id]
|
||||||
|
|
||||||
|
|
||||||
@app.post("/items")
|
@app.post("/items")
|
||||||
def create_item(item: Item) -> Item:
|
def create_item(item: Item) -> Item:
|
||||||
|
pid = list(db.keys())[-1] + 1
|
||||||
|
item.id = pid
|
||||||
|
db[pid] = item
|
||||||
return item
|
return item
|
||||||
|
1
rust/poem/.gitignore
vendored
Normal file
1
rust/poem/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
1425
rust/poem/Cargo.lock
generated
Normal file
1425
rust/poem/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
rust/poem/Cargo.toml
Normal file
18
rust/poem/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "rust"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
poem = "1.3.13"
|
||||||
|
poem-openapi = { version = "1.3.13", features = [
|
||||||
|
"email",
|
||||||
|
"swagger-ui",
|
||||||
|
"rapidoc",
|
||||||
|
"redoc",
|
||||||
|
] }
|
||||||
|
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
tracing-subscriber = "0.3.9"
|
||||||
|
slab = "0.4.5"
|
149
rust/poem/src/main.rs
Normal file
149
rust/poem/src/main.rs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
use poem::{listener::TcpListener, Route, Server};
|
||||||
|
use poem_openapi::{
|
||||||
|
param::Path,
|
||||||
|
payload::Json,
|
||||||
|
types::{Email, Password},
|
||||||
|
ApiResponse, Object, OpenApi, OpenApiService, Tags,
|
||||||
|
};
|
||||||
|
use slab::Slab;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Tags)]
|
||||||
|
enum ApiTags {
|
||||||
|
/// Operations about user
|
||||||
|
User,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create user schema
|
||||||
|
#[derive(Debug, Object, Clone, Eq, PartialEq)]
|
||||||
|
struct User {
|
||||||
|
/// Id
|
||||||
|
#[oai(read_only)]
|
||||||
|
id: i64,
|
||||||
|
/// Name
|
||||||
|
#[oai(validator(max_length = 64))]
|
||||||
|
name: String,
|
||||||
|
/// Password
|
||||||
|
#[oai(validator(max_length = 32))]
|
||||||
|
password: Password,
|
||||||
|
email: Email,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update user schema
|
||||||
|
#[derive(Debug, Object, Clone, Eq, PartialEq)]
|
||||||
|
struct UpdateUser {
|
||||||
|
/// Name
|
||||||
|
name: Option<String>,
|
||||||
|
/// Password
|
||||||
|
password: Option<Password>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ApiResponse)]
|
||||||
|
enum CreateUserResponse {
|
||||||
|
/// Returns when the user is successfully created.
|
||||||
|
#[oai(status = 200)]
|
||||||
|
Ok(Json<i64>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ApiResponse)]
|
||||||
|
enum FindUserResponse {
|
||||||
|
/// Return the specified user.
|
||||||
|
#[oai(status = 200)]
|
||||||
|
Ok(Json<User>),
|
||||||
|
/// Return when the specified user is not found.
|
||||||
|
#[oai(status = 404)]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ApiResponse)]
|
||||||
|
enum DeleteUserResponse {
|
||||||
|
/// Returns when the user is successfully deleted.
|
||||||
|
#[oai(status = 200)]
|
||||||
|
Ok,
|
||||||
|
/// Return when the specified user is not found.
|
||||||
|
#[oai(status = 404)]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ApiResponse)]
|
||||||
|
enum UpdateUserResponse {
|
||||||
|
/// Returns when the user is successfully updated.
|
||||||
|
#[oai(status = 200)]
|
||||||
|
Ok,
|
||||||
|
/// Return when the specified user is not found.
|
||||||
|
#[oai(status = 404)]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Api {
|
||||||
|
users: Mutex<Slab<User>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OpenApi]
|
||||||
|
impl Api {
|
||||||
|
/// Create a new user
|
||||||
|
#[oai(path = "/users", method = "post", tag = "ApiTags::User")]
|
||||||
|
async fn create_user(&self, user: Json<User>) -> CreateUserResponse {
|
||||||
|
let mut users = self.users.lock().await;
|
||||||
|
let id = users.insert(user.0) as i64;
|
||||||
|
CreateUserResponse::Ok(Json(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find user by id
|
||||||
|
#[oai(path = "/users/:user_id", method = "get", tag = "ApiTags::User")]
|
||||||
|
async fn find_user(&self, user_id: Path<i64>) -> FindUserResponse {
|
||||||
|
let users = self.users.lock().await;
|
||||||
|
match users.get(user_id.0 as usize) {
|
||||||
|
Some(user) => FindUserResponse::Ok(Json(user.clone())),
|
||||||
|
None => FindUserResponse::NotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete user by id
|
||||||
|
#[oai(path = "/users/:user_id", method = "delete", tag = "ApiTags::User")]
|
||||||
|
async fn delete_user(&self, user_id: Path<i64>) -> DeleteUserResponse {
|
||||||
|
let mut users = self.users.lock().await;
|
||||||
|
let user_id = user_id.0 as usize;
|
||||||
|
if users.contains(user_id) {
|
||||||
|
users.remove(user_id);
|
||||||
|
DeleteUserResponse::Ok
|
||||||
|
} else {
|
||||||
|
DeleteUserResponse::NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update user by id
|
||||||
|
#[oai(path = "/users/:user_id", method = "put", tag = "ApiTags::User")]
|
||||||
|
async fn put_user(&self, user_id: Path<i64>, update: Json<UpdateUser>) -> UpdateUserResponse {
|
||||||
|
let mut users = self.users.lock().await;
|
||||||
|
match users.get_mut(user_id.0 as usize) {
|
||||||
|
Some(user) => {
|
||||||
|
if let Some(name) = update.0.name {
|
||||||
|
user.name = name;
|
||||||
|
}
|
||||||
|
if let Some(password) = update.0.password {
|
||||||
|
user.password = password;
|
||||||
|
}
|
||||||
|
UpdateUserResponse::Ok
|
||||||
|
}
|
||||||
|
None => UpdateUserResponse::NotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), std::io::Error> {
|
||||||
|
if std::env::var_os("RUST_LOG").is_none() {
|
||||||
|
std::env::set_var("RUST_LOG", "poem=debug");
|
||||||
|
}
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let api_service =
|
||||||
|
OpenApiService::new(Api::default(), "Users", "1.0").server("http://localhost:3000/api");
|
||||||
|
let ui = api_service.rapidoc();
|
||||||
|
|
||||||
|
Server::new(TcpListener::bind("127.0.0.1:3000"))
|
||||||
|
.run(Route::new().nest("/api", api_service).nest("/", ui))
|
||||||
|
.await
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user