Path:
strictdoc/server/app.py
Lines:
149
Non-empty lines:
118
Non-empty lines covered with requirements:
118 / 118 (100.0%)
Functions:
6
Functions covered by requirements:
6 / 6 (100.0%)
1
"""2
@relation(SDOC-SRS-126, scope=file)3
"""4
5
import logging
6
import os
7
import sys
8
import time
9
from typing import Awaitable, Callable, Generator
10
11
from fastapi import FastAPI
12
from fastapi.middleware.cors import CORSMiddleware
13
from starlette.requests import Request
14
from starlette.responses import Response
15
16
from strictdoc import __version__
17
from strictdoc.core.project_config import ProjectConfig
18
from strictdoc.helpers.coverage import register_code_coverage_hook
19
from strictdoc.helpers.pickle import pickle_load
20
from strictdoc.server.config import SDocServerEnvVariable
21
from strictdoc.server.helpers.hierarchical_rw_lock_manager import (
22
HierarchicalRWLockManager,
23
)24
from strictdoc.server.routers.main_router import create_main_router
25
from strictdoc.server.routers.other_router import create_other_router
26
27
# Define O_TEMPORARY for Windows only28
if sys.platform == "win32":
29
O_TEMPORARY = os.O_TEMPORARY # pragma: no cover
30
else:
31
O_TEMPORARY = 0
32
33
34
LOGGER = logging.getLogger("uvicorn.error")
35
36
37
def print_welcome_message(project_config: ProjectConfig) -> None:
38
strictdoc_version = f"StrictDoc Web Server v{__version__}"
39
40
host = (
41
project_config.server_host
42
if project_config.server_host.startswith("http")
43
else f"http://{project_config.server_host}"
44
)45
46
url = f"{host}:{project_config.server_port}"
47
48
width = 72
49
border = "═" * width
50
51
lines = [
52
f" {strictdoc_version.center(width - 2)} ",
53
"",
54
f" Server URL: {url}",
55
"",
56
" Documentation: https://strictdoc.readthedocs.io/",
57
"",
58
" Share feedback or report issues:",
59
" https://github.com/strictdoc-project/strictdoc/issues",
60
]61
62
banner = (
63
"\n\n"
64
f"╔{border}╗\n"
65
+ "\n".join(f"║{line.ljust(width)}║" for line in lines)
66
+ f"\n╚{border}╝\n"
67
)68
69
LOGGER.info(banner)
70
71
72
def create_app(*, project_config: ProjectConfig) -> FastAPI:
73
def lifespan(_: FastAPI) -> Generator[None, None, None]:
74
print_welcome_message(project_config)
75
yield76
77
app = FastAPI(lifespan=lifespan)
78
79
origins = [
80
"http://localhost",
81
"http://localhost:8081",
82
"http://localhost:3000",
83
]84
85
# Uncomment this to enable performance measurements.86
@app.middleware("http")
87
async def add_process_time_header( # pylint: disable=unused-variable
88
request: Request, call_next: Callable[[Request], Awaitable[Response]]
89
) -> Response:
90
start_time = time.time()
91
response: Response = await call_next(request)
92
time_passed = round(time.time() - start_time, 3)
93
94
request_path = request.url.path
95
if len(request.url.query) > 0:
96
request_path += f"?{request.url.query}"
97
98
print( # noqa: T201
99
f"PERF: {request.method} {request_path} {time_passed}s"
100
)101
return response
102
103
app.add_middleware(
104
CORSMiddleware,
105
allow_origins=origins,
106
allow_credentials=True,
107
allow_methods=["*"],
108
allow_headers=["*"],
109
)110
111
lock_manager = HierarchicalRWLockManager()
112
113
app.include_router(
114
create_other_router(
115
project_config=project_config,
116
lock_manager=lock_manager,
117
)118
)119
app.include_router(
120
create_main_router(
121
project_config=project_config,
122
app=app,
123
lock_manager=lock_manager,
124
)125
)126
127
return app
128
129
130
def strictdoc_production_app() -> FastAPI:
131
register_code_coverage_hook()
132
133
# This is a work-around to allow opening a file created with134
# NamedTemporaryFile on Windows.135
# See https://stackoverflow.com/a/15235559136
def temp_opener(name: str, flag: int, mode: int = 0o777) -> int:
137
flag |= O_TEMPORARY
138
return os.open(name, flag, mode)
139
140
path_to_tmp_config = os.environ[SDocServerEnvVariable.PATH_TO_CONFIG]
141
with open(path_to_tmp_config, "rb", opener=temp_opener) as tmp_config_file:
142
tmp_config_bytes = tmp_config_file.read()
143
144
project_config = pickle_load(tmp_config_bytes)
145
assert isinstance(project_config, ProjectConfig), project_config
146
147
return create_app(
148
project_config=project_config,
149
)