Skip to content

Commit

Permalink
alpha v2.7, add proxy and service registry for Operations system, han…
Browse files Browse the repository at this point in the history
…dle fastapi operations integrate before route, fix API response missing in openapi generate, handle client base_url with query, compat django 3.0, optimize cmd
  • Loading branch information
voidZXL committed Dec 5, 2024
1 parent e3b4521 commit 124a954
Show file tree
Hide file tree
Showing 79 changed files with 1,628 additions and 836 deletions.
11 changes: 9 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
python-version: "3.8"
django-version: "4.0"
name: "3.8 (django 4.0)"
- os: ubuntu-latest
python-version: "3.8"
django-version: "3.0"
name: "Django compat earliest (3.0)"

name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
Expand All @@ -66,8 +70,11 @@ jobs:
pip install utype
pip install databases[aiosqlite] redis aioredis
pip install django==${{ matrix.django-version }}
pip install flask fastapi sanic[ext] tornado aiohttp uvicorn httpx requests python-multipart
pip install django-ninja djangorestframework drf-spectacular apiflask
pip install flask apiflask fastapi sanic[ext] tornado aiohttp uvicorn httpx requests python-multipart
- name: Install conditional dependencies
if: ${{ matrix.django-version >= '4.0'}}
run: |
pip install django-ninja djangorestframework drf-spectacular
- name: Run lint
run: |
flake8 utilmeta --count --select=E9,F63,F7,F82 --show-source --statistics
Expand Down
65 changes: 57 additions & 8 deletions docs/zh/guide/ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,10 @@ UtilMeta 框架的运维管理系统除了可以连接 UtilMeta 框架的服务
在连接任何 Python 项目之前,请先初始化 UtilMeta 设置,方式很简单,就是进入你的项目文件夹中,输入以下命令

```
meta init --app=ref.of.your.app
meta init
```

注意命令中的 `--app` 参数需要指定你的 **Python WSGI/ASGI 应用的引用路径**,比如对于下面的 Django 项目

执行后将会提示你指定当前 Python 项目的 **WSGI/ASGI 应用的引用路径**,比如对于下面的 Django 项目
```
/django_project
/django_settings
Expand All @@ -282,11 +281,7 @@ meta init --app=ref.of.your.app
manage.py
```

Django 的 WSGI 应用一般位于 `wsgi.py` 中的 `application` ,当我们在 /django_project 文件夹初始化 UtilMeta 项目时,就可以执行

```
meta init --app=django_settings.wsgi.app
```
Django 的 WSGI 应用一般位于 `wsgi.py` 中的 `application` ,当我们在 /django_project 文件夹初始化 UtilMeta 项目时,对应的引用路径就可以输入 `django_settings.wsgi.app`

对于 Flask / FastAPI / Sanic 项目,只需要找到对应的 `Flask()`, `FastAPI()`, `Sanic()` 应用的引用即可,用法和上面一样

Expand Down Expand Up @@ -344,6 +339,50 @@ meta connect

如果你的服务提供了网络访问,请进入 [UtilMeta 管理平台](https://ops.utilmeta.com),创建项目团队并按照其中的提示操作

#### 同步 Django Ninja
如果你正在使用 Django Ninja 框架,由于 Django Ninja 注入 django 应用的方式是通过 `urlpatterns`,UtilMeta 无法直接获取到 Django Ninja 的 `NinjaAPI` 应用来生成 OpenAPI 文档,所以你需要手动在 Operations 配置中指定,例如对于如下的 NinjaAPI

```python
# urls.py --------
from ninja import NinjaAPI

ninja_api = NinjaAPI()

@ninja_api.get("/add")
def add(request, a: int, b: int):
return {"result": a + b}

urlpatterns = [
path("api-ninja/", ninja_api.urls),
]
```

我们需要在 wsgi.py 的 Operations 配置中加入解析 NinjaAPI 文档的代码

```python hl_lines="14-16"
import os
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

from utilmeta.ops import Operations
from .urls import ninja_api

Operations(
route='ops',
database=Operations.Database(
name='operations_db',
engine='sqlite3'
),
openapi=Operations.get_django_ninja_openapi({
"api-ninja/": ninja_api
}),
base_url='https://<YOUR DOMAIN>/api',
# base_url='http://127.0.0.1:<YOUR_PORT>', # 本地项目
).integrate(application, __name__)
```

Operations 配置的 `openapi` 参数用于指定额外的 API 文档,我们这里直接调用 Operations 配置的 `get_django_ninja_openapi` 方法,其中的传入一个字典,字典的键是 NinjaAPI 挂载到 urlpatterns 的路径,比如上面例子中的 `api-ninja/`,字典的值就是对应的 `NinjaAPI()` 实例,由于 Django Ninja 可以创建多个 `NinjaAPI()` 实例,你都可以按照这个规则传入到函数中

### 连接 Flask

对于 Flask 项目,我们只需要将 Operations 配置接入 Flask app 即可,如
Expand All @@ -353,6 +392,9 @@ from flask import Flask

app = Flask(__name__)

# @app.route(...)
# 请将以下代码插入到 Flask 路由的后面,否则会影响 API 文档生成

from utilmeta.ops import Operations
Operations(
route='ops',
Expand Down Expand Up @@ -389,6 +431,10 @@ from fastapi import FastAPI

app = FastAPI()

# @app.route(...)
# app.include_router(...)
# 请将以下代码插入到 FastAPI 路由的后面,否则会影响 API 文档生成

from utilmeta.ops import Operations
Operations(
route='ops',
Expand Down Expand Up @@ -426,6 +472,9 @@ from sanic import Sanic

app = Sanic('mysite')

# @app.route(...)
# 请将以下代码插入到 Sanic 路由的后面,否则会影响 API 文档生成

from utilmeta.ops import Operations
Operations(
route='ops',
Expand Down
81 changes: 0 additions & 81 deletions docs/zh/plugins/cors.md

This file was deleted.

Empty file removed docs/zh/plugins/retry.md
Empty file.
24 changes: 0 additions & 24 deletions docs/zh/plugins/write-plugin.md

This file was deleted.

6 changes: 4 additions & 2 deletions examples/mini_blog/server.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from utilmeta import UtilMeta
from utilmeta.core import api
from config import configure
import fastapi
import django

service = UtilMeta(
__name__,
name='blog',
backend=fastapi,
backend=django,
asynchronous=True,
port=8080
)
Expand All @@ -21,5 +21,7 @@ class RootAPI(api.API):
article: ArticleAPI


app = service.application()

if __name__ == '__main__':
service.run()
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def __init__(
# self.asgi: Optional[Server] = None
self.connections_override = connections_override
self.server_class = WSGIServer if single_thread else ThreadedWSGIServer
self.host = self.service.host
self.host = self.service.host or '127.0.0.1'
self.port = self.service.port
super().__init__()

Expand Down Expand Up @@ -245,7 +245,7 @@ def run_service():
cnt = 0
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
while True:
if s.connect_ex((service.host, service.port)) == 0:
if s.connect_ex(('127.0.0.1', service.port)) == 0:
break
time.sleep(CONNECT_INTERVAL)
cnt += 1
Expand Down Expand Up @@ -354,7 +354,7 @@ def service_process(service: UtilMeta):
cnt = 0
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
while True:
if s.connect_ex((service.host, service.port)) == 0:
if s.connect_ex(('127.0.0.1', service.port)) == 0:
break
time.sleep(CONNECT_INTERVAL)
cnt += 1
Expand Down
5 changes: 5 additions & 0 deletions tests/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class ServiceEnvironment(Env):
from utilmeta.core.orm import DatabaseConnections, Database
from utilmeta.core.cache import CacheConnections, Cache
from utilmeta.ops.config import Operations
from utilmeta.conf import Preference

service.use(DjangoSettings(
apps=['app']
Expand All @@ -78,6 +79,10 @@ class ServiceEnvironment(Env):
engine='locmem'
)
}))
service.use(Preference(
default_aborted_response_status=500,
default_timeout_response_status=500
))

# ------ SET BACKEND
backend = None
Expand Down
7 changes: 5 additions & 2 deletions tests/test_1_orm/test_schema_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,8 @@ class UserSchema(orm.Schema[User]):
username='new user 1',
followings=[1, 2]
)
user1.save(with_relations=True)
user1.save()
# test default with_relations=True
user1_inst: User = user1.get_instance(fresh=True)
assert user1_inst.username == 'new user 1'
assert set(Follow.objects.filter(user_id=user1.pk).values_list('target_id', flat=True)) == {1, 2}
Expand Down Expand Up @@ -536,7 +537,9 @@ class UserSchema(orm.Schema[User]):
username='async new user 1',
followings=[1, 2]
)
await user1.asave(with_relations=True)
await user1.asave()
# test default with_relations=True

user1_inst: User = await user1.aget_instance(fresh=True)
assert user1_inst.username == 'async new user 1'
assert {v async for v in Follow.objects.filter(user_id=user1.pk).values_list('target_id', flat=True)} == {1, 2}
Expand Down
6 changes: 6 additions & 0 deletions tests/test_3_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ def post(self):
def get(self):
pass

with pytest.raises(Exception):
class _API(API): # noqa
@api.get('/route/{path}')
def get(self):
pass

with pytest.raises(Exception):
class _API(API): # noqa
def post(self):
Expand Down
4 changes: 4 additions & 0 deletions tests/test_3_api/test_api_django.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import django

from tests.conftest import setup_service, make_live_process, make_server_thread
from .params import do_live_api_tests

Expand All @@ -15,6 +17,8 @@


def test_django_api(service, django_server_process):
if django.VERSION < (3, 1) and service.asynchronous:
return
do_live_api_tests(service)
service._application = None
service.adaptor.app = None
Expand Down
10 changes: 10 additions & 0 deletions tests/test_4_client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ def test_build_url(self):
query={'k2': 'v2'}
) == 'https://test2.com/sub/?k1=v1&k2=v2'

client2 = Client(
base_url='https://test.com/path?q1=v1',
base_query={'q0': 'v0'},
append_slash=True
)
assert client2._build_url(
path='sub?q2=v2',
query={'q3': 'v3'}
) == 'https://test.com/path/sub/?q0=v0&q1=v1&q2=v2&q3=v3'

def test_live_server(self, server_thread, sync_request_backend):
with TestClient(
base_url='http://127.0.0.1:8666/api/test',
Expand Down
13 changes: 12 additions & 1 deletion tests/test_7_ops/django_site/django_demo/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,20 @@ class UserViewSet(viewsets.ModelViewSet):
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

from ninja import NinjaAPI

ninja_api = NinjaAPI()


@ninja_api.get("/add")
def add(request, a: int, b: int):
return {"result": a + b}


# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path("api-ninja/", ninja_api.urls),
]
Loading

0 comments on commit 124a954

Please sign in to comment.