Migrate from current project¶
UtilMeta is a Progressive meta-framework, which means that it can be integrated incrementally from existing Python projects, and can also integrate other Python framework's API. This document will introduce the corresponding usage.
Integrate UtilMeta to current project¶
All API in the UtilMeta project are declared in API class ( utilmeta.core.api.API
). The API class has a method for converting the UtilMeta API to the routing function of other frameworks. This method is as follows
API.__as__(backend, route: str, asynchornous: bool = None)
The parameters are
backend
: You can pass in thedjango
,tornado
package or the core application offlask
,starlette
,fastapi
,sanic
route
: The routing path corresponding to the API, such as/v2
asynchornous
: Whether to provide an asynchronous API. If True, the UtilMeta API class will be converted to an asynchronous function. Otherwise, it will be converted to a synchronous function. The default is None, determined by thebackend
This is the only method required for the UtilMeta API to progressively integrate to existing projects, and the following is an example of integrate to different frameworks
Django¶
To integrate a Django project, simply use the return result of API.__as__
method as an element in urlpatterns
, as shown in
import django
from django.urls import re_path
from django.http.response import HttpResponse
from utilmeta.core import api, response
class CalcAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'msg'
@api.get
def add(self, a: int, b: int) -> int:
return a + b
def django_test(request, route: str):
return HttpResponse(route)
urlpatterns = [
re_path('test/(.*)', django_test),
CalcAPI.__as__(django, route='/calc'),
]
We mount the CalcAPI
on the route /calc
, and when we request GET /calc/add?a=1&b=2
, we'll get the following JSON response
{"data": 3, "msg": ""}
Flask¶
Flask use Flask(__name__)
to initialize an application, and you just need to pass the application to API.__as__
as the first parameter of the method.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
from utilmeta.core import api, response
class CalcAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'msg'
@api.get
def add(self, a: int, b: int) -> int:
return a + b
CalcAPI.__as__(app, route='/calc')
if __name__ == '__main__':
app.run()
When you startup the project and request http://127.0.0.1:5000/, you can see that the Hello, World!
in the browser, this is the API from Flask, and if request http://127.0.0.1:5000/calc/add?a=-1&b=2, you can see the return of the UtilMeta API.
{"data": 1, "msg": ""}
Starlette (FastAPI)¶
Similar to Flask, when accessing a Fast API (Starlette) application, you only need to pass the application into API.__as__
the method, as shown in
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
from utilmeta.core import api, response
class CalcAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'msg'
@api.get
def add(self, a: int, b: int) -> int:
return a + b
CalcAPI.__as__(app, route='/calc')
if __name__ == '__main__':
import uvicorn
uvicorn.run(app)
When you request http://127.0.0.1:8000/items/1, you will access the FastAPI's endpoint and get {"item_id":"1"}
When you request http://127.0.0.1:8000/calc/add?a=1.5&b=2.1, you will get the results of UtilMeta API
{"data": 3, "msg": ""}
Tip
This result is because the a
and b
param is converted to int
before the calculation
Sanic¶
Usage is similar to Flask or FastAPI
from sanic import Sanic
from sanic.response import text
app = Sanic("MyHelloWorldApp")
@app.get("/")
async def hello_world(request):
return text("Hello, world.")
from utilmeta.core import api, response
class CalcAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'msg'
@api.get
def add(self, a: int, b: int) -> int:
return a + b
CalcAPI.__as__(app, route='/calc')
if __name__ == '__main__':
app.run()
Request http://127.0.0.1:8000/ will see Sanic returned hello, world
, and request http://127.0.0.1:8000/calc/add?a=1&b=x will see UtilMeta's parameters procession.
{"data": null, "msg": "BadRequest: parse item: ['b'] failed: invalid number: 'x'"}
Tip
Because the query param b=x
cannot converted to int
type
Tornado¶
The way to integrate the UtilMeta API like Tornado is as follows
import asyncio
import tornado
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
from utilmeta.core import api, response
class CalcAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'msg'
@api.get
def add(self, a: int, b: int) -> int:
return a + b
def make_app():
return tornado.web.Application([
CalcAPI.__as__(tornado, route='/calc'),
(r"/", MainHandler),
])
async def main():
app = make_app()
app.listen(8888)
await asyncio.Event().wait()
if __name__ == "__main__":
asyncio.run(main())
Just take the result of API.__as__
as a route of tornado.web.Application
Integration rules¶
When integrate UtilMeta to other existing projects, you should only integrate One API classes. If you develop other API classes, you can use API mounting as a subroute of the integrated API classes.
Because the service is not controlled by UtilMeta when the UtilMeta API integrate other projects, so API.__as__
will create a hidden UtilMeta service for control, in order to avoid service conflicts, you can only call the API.__as__
function once.
Integrate other frameworks¶
Your UtilMeta project can also integrate other framework's APIs
Django¶
You can mount the Django view URL directly into the UtilMeta project in two ways.
Use django
as backend
If your UtilMeta service is used django
as backend
a, it has a file structure similar to the following
/blog
/app
models.py
urls.py
service.py
Django view urls is defined in urls.py
, so you just need to pass a reference to this file into the root_urlconf
param of DjangoSettings
from utilmeta import UtilMeta
import django
service = UtilMeta(
__name__,
name='blog',
backend=django
)
from utilmeta.core.server.backends.django import DjangoSettings
from utilmeta.core.orm import DatabaseConnections, Database
service.use(DjangoSettings(
apps=['app'],
root_urlconf='urls'
))
service.use(DatabaseConnections({
'default': Database(
name='db',
engine='sqlite3',
)
}))
from utilmeta.core import api
class RootAPI(api.API):
@api.get
def hello(self):
return 'world'
service.mount(RootAPI, route='/api')
app = service.application()
if __name__ == '__main__':
service.run()
from app.models import Article
from django.urls import path
import json
from django.http.response import HttpResponse
def get_article(request):
return HttpResponse(
json.dumps(list(Article.objects.filter(id=request.GET.get('id')).values()))
)
urlpatterns = [
path('article', get_article)
]
When you request GET /article
, it will hit Django’s route view function.
Use starlette
(fastapi) as backend
You can also use starlette
as backend
mount Django view functions. In the configuration of your Django project settings.py
, there should be a application
property
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
You just need to import this application
and mount it using the mount()
method of UtilMeta service, such as
import starlette
from utilmeta import UtilMeta
service = UtilMeta(__name__, backend=starlette, name='demo')
from settings import application as django_wsgi
service.mount(django_wsgi, '/v1')
When /v1/xxx
is requested, it will be directed to Django’s view functions.
Django Ninja¶
to integrate Django Ninja, you must use django
as service backend, the integration is as simple as follows
import django
from utilmeta import UtilMeta
service = UtilMeta(__name__, backend=django, name='demo')
from utilmeta.core.server.backends.django import DjangoSettings
service.use(DjangoSettings())
service.setup()
from ninja import NinjaAPI
ninja_api = NinjaAPI()
@ninja_api.get("/add")
def add(request, a: int, b: int):
return {"result": a + b}
service.mount(ninja_api, '/v1')
app = service.application()
Warning
ninja
should import after the django settings setup, otherwise a django.core.exceptions.ImproperlyConfigured
exception will be raised
DRF¶
to Integrate Django REST framework, you must use django
as service backend, the integration is as simple as follows
from utilmeta import UtilMeta
import django
service = UtilMeta(
__name__,
name='demo',
backend=django
)
from utilmeta.core.server.backends.django import DjangoSettings
from utilmeta.core.orm import DatabaseConnections, Database
service.use(DjangoSettings(
apps=['app', 'rest_framework', 'django.contrib.auth'],
extra=dict(
REST_FRAMEWORK={
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
]
}
)
))
service.setup()
from app.models import User
from rest_framework import routers, serializers, viewsets
# Serializers define the API representation.
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['username', 'signup_time', 'admin']
# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# Routers provide an easy way of automatically determining the URL conf.
drf_router = routers.DefaultRouter()
drf_router.register(r'users', UserViewSet)
service.mount(drf_router, route='/v1')
app = service.application()
Tip
You can configure DRF in the extra
param of DjangoSettings, and remember to include 'rest_framework'
in your apps
param
Flask¶
Use flask
as backend
If you are using flask
as the UtilMeta service backend
, simply pass the Flask application as backend
of the UtilMeta service.
from flask import Flask
from utilmeta import UtilMeta
from utilmeta.core import api, response
flask_app = Flask(__name__)
class RootAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'error'
@api.get
def hello(self):
return 'Hello, UtilMeta!'
service = UtilMeta(
__name__,
name='demo',
backend=flask_app,
api=RootAPI,
route='/api'
)
@flask_app.route("/v1/hello")
def hello_flask():
return "<p>Hello, flask!</p>"
if __name__ == '__main__':
service.run()
In this way, when you request http://127.0.0.1:5000/v1/hello, the hello_flask
routing function will be called and return Hello, flask!
, and when you request http://127.0.0.1:5000/api/hello, it will be processed and responded by UtilMeta API.
{"data": "Hello, UtilMeta!", "error": ""}
Use starlette
(fastapi) as backend
You can also use starlette
as backend
and mount the flask application, just mount the Flask(__name__)
application using mount
.
import starlette
from flask import Flask
flask_app = Flask(__name__)
@flask_app.route("/hello")
def hello_flask():
return "<p>Hello, flask!</p>"
from utilmeta import UtilMeta
service = UtilMeta(__name__, backend=starlette, name='demo')
service.mount(flask_app, '/v1')
So when you request GET /v1/hello
, it will still be processed by hello_flask
routing function of flask.
Starlette (FastAPI)¶
Similar to Flask, integrate Starlette (FastAPI) only needs to mount the core application using mount
.
from fastapi import FastAPI
fastapi_app = FastAPI()
@fastapi_app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
from utilmeta import UtilMeta
from utilmeta.core import api, response
class RootAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'error'
@api.get
def hello(self):
return 'Hello, UtilMeta!'
service = UtilMeta(
__name__,
name='demo',
backend=fastapi_app,
api=RootAPI
)
if __name__ == '__main__':
service.run()
When you request http://127.0.0.1:8000/items/1, it will call the read_item
function of FastAPI application and return {"item_id":"1"}
, and request http://127.0.0.1:8000/hello will be processed and responded by UtilMeta API.
{"data": "Hello, UtilMeta!", "error": ""}
Tip
When integrating Starlette (FastAPI) application, your backend
of UtilMeta service must be starlette
or fastapi
Sanic¶
Integrate Sanic is similar to Flask, but you can only use sanic
as the backend
of UtilMeta service
from sanic import Sanic, text
sanic_app = Sanic('demo')
@sanic_app.get("/v1/hello")
def hello(request):
return text("Hello, sanic!")
from utilmeta import UtilMeta
from utilmeta.core import api, response
class RootAPI(api.API):
class response(response.Response):
result_key = 'data'
message_key = 'error'
@api.get
def hello(self):
return 'Hello, UtilMeta!'
service = UtilMeta(
__name__,
name='demo',
backend=sanic_app,
api=RootAPI,
route='/api'
)
app = service.application()
if __name__ == '__main__':
service.run()
When you run service and request http://127.0.0.1:8000/v1/hello, it will call sanic’s routing function and return Hello, sanic!
, and when you request http://127.0.0.1:8000/api/hello, it will be processed and responded by UtilMeta API.
{"data": "Hello, UtilMeta!", "error": ""}
Support more frameworks¶
If the Python framework you are using is not yet supported, please mention it in the Issues of UtilMeta framework.