Skip to content
askfiy edited this page Nov 13, 2023 · 13 revisions

Start

Using Razor is extremely simple, as mentioned in the README.

Building a basic Razor server is as simple as breathing:

from razor.server import Application
from razor.server import TextResponse

app = Application(__name__)

@app.route("/index/")
async def index():
    return TextResponse("hello world")

if __name__ == "__main__":
    app.run()

You can also use the command line of uvicorn to start the project:

$ uvicorn setup:app --host 127.0.0.1 --port 5200

Razor uses port 5200 by default.

Route

At its current stage, Razor uses http-route to build its routing system.

Below are common examples of routes. For more details, you can refer to the http-route documentation:

from razor.server import Application
from razor.server import TextResponse

app = Application(__name__)


@app.route("/index/")
async def index():
    return TextResponse("hello world")


@app.route("/book/{book_id:int}/")
async def book(book_id):
    return TextResponse(f"book_id : {book_id}")


@app.route(r"/page/{page:\d+}/", methods=["GET", "POST"])
async def page(page):
    return TextResponse(f"page : {page}")


if __name__ == "__main__":
    app.run()

Razor provides a straightforward way to handle routes for Class-Based-Views:

from razor.server import Application
from razor.server import TextResponse
from razor.server import View

app = Application(__name__)


@app.route("/example/{page}/")
class Example(View):
    async def get(self, page):
        # default page type is str
        return TextResponse(f"example get ! {page}")


if __name__ == "__main__":
    app.run()

Razor also offers a routing mapping approach similar to Django:

app.add_routes(
    ("/index/", index),
    ("/example/", Example.as_view())
)

Unfortunately, Razor does not currently support alias parameters such as name or alias.

Even switches with a single URL strict match are not supported, but I think this is acceptable at this stage.

Request

Razor's request is inspired by the design of flask

You only need to import it and then use it as shown below:

from razor.server import (
    request,
    Application,
    TextResponse
)

app = Application(__name__)


@app.route("/index/")
async def index():
    print(request.method)
    return TextResponse("index")

if __name__ == "__main__":
    app.run()

The request provides a variety of methods:

- attribute
    - content
    - headers
    - cookies
    - query

- async method
    - body
    - text
    - form
    - files
    - json
    - data

Additionally, you can also access scope information using the . or [] syntax:

{
    'type': 'http',
    'asgi': {
        'version': '3.0',
        'spec_version': '2.3'
    },
    'http_version': '1.1',
    'server': ('127.0.0.1', 5000),
    'client': ('127.0.0.1', 51604),
    'scheme': 'http',
    'method': 'GET',
    'root_path': '',
    'path': '/index/',
    'raw_path': b'/index/',
    'query_string': b'k1=v1&k2=v2',
    'headers': [
        (b'host', b'127.0.0.1:5000'),
        (b'user-agent', b'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0'),
        (b'accept', b'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'),
        (b'accept-language', b'en-US,en;q=0.5'),
        (b'accept-encoding', b'gzip, deflate, br'),
        (b'connection', b'keep-alive'),
        (b'upgrade-insecure-requests', b'1'),
        (b'sec-fetch-dest', b'document'),
        (b'sec-fetch-mode', b'navigate'),
        (b'sec-fetch-site', b'cross-site')
    ],
    'state': {}}

For example:

request.method
request.server
request.client
request.headers

File Handle

Here's an example of uploading a file and saving it to disk:

from razor.server import (
    request,
    Application,
    TextResponse
)

app = Application(__name__)


@app.route("/files/", methods=["POST"])
async def index():
    files = await request.files()

    file = files["avater"]
    await file.save(f"./avater/{file.name}")

    return TextResponse("save file success")


if __name__ == "__main__":
    app.run()

Razor temporarily stores files in memory and writes the file to disk when you call the save method.

If the specified directory for saving the file doesn't exist, Razor will automatically create it.

If you don't provide any path to the save method, the file will be saved in the current directory by default.

Feel free to ask if you have any more questions!

Response

Razor offers several packages for Response:

from razor.server import (
    TextResponse,
    HtmlResponse,
    JsonResponse,
    RedirectResponse,
    ErrorResponse
)

The following are examples of usage:

return TextResponse("hello world")
return HtmlResponse("<div>hello world</div>")
return JsonResponse({"code": 0})
return RedirectResponse("/doc/")
return ErrorResponse(500)

The handling of response headers and cookies is also very simple:

from razor.server import (
    Application,
    TextResponse
)

app = Application(__name__)


@app.route("/index/", methods=["POST"])
async def index():
    resp = TextResponse("hello world")
    resp.cookies["k1"] = "v1"
    resp.headers["k1"] = "v1"
    return resp


if __name__ == "__main__":
    app.run()

Event hooks

Razor provides rich event hook functions:

"startup",
"shutdown",
"after_request",
"before_request",
"exception"

They can be used with app.on_event:

from typing import Type, Optional, Union

from razor.server import (
    Application,
    Response
)


app = Application(__name__)


@app.on_event("startup")
async def startup() -> None:
    print("startup")


@app.on_event("shutdown")
async def shutdown() -> None:
    print("shutdown")


@app.on_event("before_request")
async def before_request() -> Optional[Type[Response]]:
    print("before request")


@app.on_event("after_request")
async def after_request(resp: Type[Response]) -> Type[Response]:
    print("after request")
    return resp


@app.on_event("exception")
async def exception_handle(exc: Type[Exception]) -> Union[Type[Exception], Type[Response]]:
    print("exception handle")
    return exc

The same can be achieved with the quick decorators provided by Razor:

- on_startup
- on_shutdown
- on_before_request
- on_after_request
- on_exception

Example:

@app.on_startup
async def startup() -> None:
    print("startup")

logger

Razor reuses uvicorn's logger by default, so you only need to pass in the logger configuration for the Run method of Application.

app.run(log_config=LOGGER_CONF)

log_config can be the path to a Dict or JSON file or yaml file.

Use example:

from razor.server import Application
from razor.server import logger as log

app = Application(__name__)

@app.on_startup
async def startup():
    log.info("startup")

if __name__ == "__main__":
    app.run()

Context

In addition to the request context, Razor also provides an application context.

from razor.server import current_application

It ensures that you can access the current application instance object at any time.

Clone this wiki locally