almost finished..

This commit is contained in:
CaptainNEO 2022-03-10 21:01:51 +08:00
parent c1eb1c06ea
commit 3a78fd393d
38 changed files with 2362 additions and 75 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.venv
db.sqlite3

136
README.md
View File

@ -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 | ![stars](https://img.shields.io/github/stars/goadesign/goa.svg) | ![Release](https://img.shields.io/github/tag/goadesign/goa.svg) | 2016-08-03 | | Go | [goa](https://github.com/goadesign/goa) | 是 | 3 | ![stars](https://img.shields.io/github/stars/goadesign/goa.svg) | ![contributors](https://img.shields.io/github/contributors/goadesign/goa.svg) | ![commit](https://img.shields.io/github/commit-activity/y/goadesign/goa.svg) | ![Release](https://img.shields.io/github/tag/goadesign/goa.svg) | 2016-08-03 |
| Go | [swag](https://github.com/swaggo/swag) | 否 | 2 | ![stars](https://img.shields.io/github/stars/swaggo/swag.svg) | ![Release](https://img.shields.io/github/release/swaggo/swag.svg) | 2017-11-30 | | Go | [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) | 是 | 2 | ![stars](https://img.shields.io/github/stars/grpc-ecosystem/grpc-gateway.svg) | ![contributors](https://img.shields.io/github/contributors/grpc-ecosystem/grpc-gateway.svg) | ![commit](https://img.shields.io/github/commit-activity/y/grpc-ecosystem/grpc-gateway.svg) | ![Release](https://img.shields.io/github/release/grpc-ecosystem/grpc-gateway.svg) | 2016-07-11 |
| Go | [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) | 是 | 2 | ![stars](https://img.shields.io/github/stars/grpc-ecosystem/grpc-gateway.svg) | ![Release](https://img.shields.io/github/release/grpc-ecosystem/grpc-gateway.svg) | 2016-07-11 | | Go | [swag](https://github.com/swaggo/swag) | 否 | 2 | ![stars](https://img.shields.io/github/stars/swaggo/swag.svg) | ![contributors](https://img.shields.io/github/contributors/swaggo/swag.svg) | ![commit](https://img.shields.io/github/commit-activity/y/swaggo/swag.svg) | ![Release](https://img.shields.io/github/release/swaggo/swag.svg) | 2017-11-30 |
| Go | [fizz](https://github.com/wI2L/fizz) | 是 | 3 | ![stars](https://img.shields.io/github/stars/wI2L/fizz.svg) | ![Release](https://img.shields.io/github/release/wI2L/fizz.svg) | 2019-11-06 | | Go | [fizz](https://github.com/wI2L/fizz) | 是 | 3 | ![stars](https://img.shields.io/github/stars/wI2L/fizz.svg) | ![contributors](https://img.shields.io/github/contributors/wI2L/fizz.svg) | ![commit](https://img.shields.io/github/commit-activity/y/wI2L/fizz.svg) | ![Release](https://img.shields.io/github/release/wI2L/fizz.svg) | 2019-11-06 |
| Python | [Django REST framework](https://github.com/encode/django-rest-framework) | 是 | 3 | ![stars](https://img.shields.io/github/stars/encode/django-rest-framework.svg) | ![Release](https://img.shields.io/github/release/encode/django-rest-framework.svg) | 2011-02-22 | | Python | [Django REST framework](https://github.com/encode/django-rest-framework) | 是 | 3 | ![stars](https://img.shields.io/github/stars/encode/django-rest-framework.svg) | ![contributors](https://img.shields.io/github/contributors/encode/django-rest-framework.svg) | ![commit](https://img.shields.io/github/commit-activity/y/encode/django-rest-framework.svg) | ![Release](https://img.shields.io/github/release/encode/django-rest-framework.svg) | 2011-02-22 |
| Python | [FastAPI](https://github.com/tiangolo/fastapi) | 是 | 3 | ![stars](https://img.shields.io/github/stars/tiangolo/fastapi.svg) | ![Release](https://img.shields.io/github/release/tiangolo/fastapi.svg) | 2018-12-16 | | Python | [FastAPI](https://github.com/tiangolo/fastapi) | 是 | 3 | ![stars](https://img.shields.io/github/stars/tiangolo/fastapi.svg) | ![contributors](https://img.shields.io/github/contributors/tiangolo/fastapi.svg) | ![commit](https://img.shields.io/github/commit-activity/y/tiangolo/fastapi.svg) | ![Release](https://img.shields.io/github/release/tiangolo/fastapi.svg) | 2018-12-16 |
| Rust | [Poem](https://github.com/poem-web/poem) | 是 | 3 | ![stars](https://img.shields.io/github/stars/poem-web/poem.svg) | ![Release](https://img.shields.io/github/tag/poem-web/poem.svg) | 2021-10-14 | | Rust | [Poem](https://github.com/poem-web/poem) | 是 | 3 | ![stars](https://img.shields.io/github/stars/poem-web/poem.svg) | ![contributors](https://img.shields.io/github/contributors/poem-web/poem.svg) | ![commit](https://img.shields.io/github/commit-activity/y/poem-web/poem.svg) | ![Release](https://img.shields.io/github/tag/poem-web/poem.svg) | 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

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AnimalsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "animals"

View File

@ -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'],
},
),
]

View 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"]

View File

@ -0,0 +1,8 @@
from rest_framework import serializers
from .models import Student
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View 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()

View 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()

View 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"

View 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",
),
]

View 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()

View 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()

View 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

View 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
View 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"},
]

View 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"

View File

@ -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
View File

@ -0,0 +1 @@
/target

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
View 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
View 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
}