mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
add fastapi docs config model
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
import re
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import BaseSettings
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def clean_name(_name):
|
||||
@ -25,7 +25,7 @@ def clean_name(_name):
|
||||
return _scrubbed.lower()
|
||||
|
||||
|
||||
class HyperglassModel(BaseSettings):
|
||||
class HyperglassModel(BaseModel):
|
||||
"""Base model for all hyperglass configuration models."""
|
||||
|
||||
pass
|
||||
@ -51,3 +51,29 @@ class HyperglassModelExtra(HyperglassModel):
|
||||
"""Default pydantic configuration."""
|
||||
|
||||
extra = "allow"
|
||||
|
||||
|
||||
class AnyUri(str):
|
||||
"""Custom field type for HTTP URI, e.g. /example."""
|
||||
|
||||
@classmethod
|
||||
def __get_validators__(cls):
|
||||
"""Pydantic custim field method."""
|
||||
yield cls.validate
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value):
|
||||
"""Ensure URI string contains a leading forward-slash."""
|
||||
uri_regex = re.compile(r"^(\/.*)$")
|
||||
if not isinstance(value, str):
|
||||
raise TypeError("AnyUri type must be a string")
|
||||
match = uri_regex.fullmatch(value)
|
||||
if not match:
|
||||
raise ValueError(
|
||||
"Invalid format. A URI must begin with a forward slash, e.g. '/example'"
|
||||
)
|
||||
return cls(match.group())
|
||||
|
||||
def __repr__(self):
|
||||
"""Stringify custom field representation."""
|
||||
return f"AnyUri({super().__repr__()})"
|
||||
|
@ -18,6 +18,7 @@ from pydantic import validator
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
from hyperglass.configuration.models.docs import Docs
|
||||
from hyperglass.configuration.models.opengraph import OpenGraph
|
||||
|
||||
|
||||
@ -46,6 +47,7 @@ class General(HyperglassModel):
|
||||
"isp",
|
||||
]
|
||||
opengraph: OpenGraph = OpenGraph()
|
||||
docs: Docs = Docs()
|
||||
google_analytics: StrictStr = ""
|
||||
redis_host: StrictStr = "localhost"
|
||||
redis_port: StrictInt = 6379
|
||||
@ -54,6 +56,7 @@ class General(HyperglassModel):
|
||||
listen_address: Optional[Union[IPvAnyAddress, StrictStr]]
|
||||
listen_port: StrictInt = 8001
|
||||
log_file: Optional[FilePath]
|
||||
cors_origins: List[StrictStr] = []
|
||||
|
||||
@validator("listen_address", pre=True, always=True)
|
||||
def validate_listen_address(cls, value, values):
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Hyperglass Front End."""
|
||||
|
||||
# Standard Library Imports
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
@ -17,12 +16,14 @@ from prometheus_client import Counter
|
||||
from prometheus_client import generate_latest
|
||||
from prometheus_client import multiprocess
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import PlainTextResponse
|
||||
from starlette.responses import UJSONResponse
|
||||
from starlette.staticfiles import StaticFiles
|
||||
|
||||
# Project Imports
|
||||
from hyperglass import __version__
|
||||
from hyperglass.configuration import frontend_params
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.exceptions import AuthError
|
||||
@ -37,6 +38,7 @@ from hyperglass.execution.execute import Execute
|
||||
from hyperglass.models.query import Query
|
||||
from hyperglass.util import check_python
|
||||
from hyperglass.util import log
|
||||
from hyperglass.util import write_env
|
||||
|
||||
# Verify Python version meets minimum requirement
|
||||
try:
|
||||
@ -57,14 +59,41 @@ IMAGES_DIR = STATIC_DIR / "images"
|
||||
NEXT_DIR = UI_DIR / "_next"
|
||||
log.debug(f"Static Files: {STATIC_DIR}")
|
||||
|
||||
docs_mode_map = {"swagger": "docs_url", "redoc": "redoc_url"}
|
||||
|
||||
docs_config = {"docs_url": None, "redoc_url": None}
|
||||
|
||||
if params.general.docs.enable:
|
||||
if params.general.docs.mode == "swagger":
|
||||
docs_config["docs_url"] = params.general.docs.uri
|
||||
docs_config["redoc_url"] = None
|
||||
elif params.general.docs.mode == "redoc":
|
||||
docs_config["docs_url"] = None
|
||||
docs_config["redoc_url"] = params.general.docs.uri
|
||||
|
||||
|
||||
# Main App Definition
|
||||
app = FastAPI()
|
||||
app = FastAPI(
|
||||
debug=params.general.debug,
|
||||
title=params.general.site_title,
|
||||
description=params.general.site_description,
|
||||
version=__version__,
|
||||
**docs_config,
|
||||
)
|
||||
app.mount("/ui", StaticFiles(directory=UI_DIR), name="ui")
|
||||
app.mount("/_next", StaticFiles(directory=NEXT_DIR), name="_next")
|
||||
app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images")
|
||||
app.mount("/ui/images", StaticFiles(directory=IMAGES_DIR), name="ui/images")
|
||||
|
||||
APP_PARAMS = {
|
||||
# CORS Configuration
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=params.general.cors_origins,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
ASGI_PARAMS = {
|
||||
"host": str(params.general.listen_address),
|
||||
"port": params.general.listen_port,
|
||||
"debug": params.general.debug,
|
||||
@ -72,7 +101,7 @@ APP_PARAMS = {
|
||||
|
||||
# Redis Config
|
||||
redis_config = {
|
||||
"host": params.general.redis_host,
|
||||
"host": str(params.general.redis_host),
|
||||
"port": params.general.redis_port,
|
||||
"decode_responses": True,
|
||||
}
|
||||
@ -80,6 +109,7 @@ redis_config = {
|
||||
r_cache = aredis.StrictRedis(db=params.features.cache.redis_id, **redis_config)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def check_redis():
|
||||
"""Ensure Redis is running before starting server.
|
||||
|
||||
@ -89,19 +119,29 @@ async def check_redis():
|
||||
Returns:
|
||||
{bool} -- True if Redis is running.
|
||||
"""
|
||||
redis_host = redis_config["host"]
|
||||
redis_port = redis_config["port"]
|
||||
try:
|
||||
await r_cache.echo("hyperglass test")
|
||||
# await r_limiter.echo("hyperglass test")
|
||||
except Exception:
|
||||
raise HyperglassError(
|
||||
f"Redis isn't running at: {redis_config['host']}:{redis_config['port']}",
|
||||
alert="danger",
|
||||
f"Redis isn't running at: {redis_host}:{redis_port}", alert="danger"
|
||||
) from None
|
||||
log.debug(f"Redis is running at: {redis_host}:{redis_port}")
|
||||
return True
|
||||
|
||||
|
||||
# Verify Redis is running
|
||||
asyncio.run(check_redis())
|
||||
@app.on_event("startup")
|
||||
async def write_env_variables():
|
||||
"""Write environment varibles for Next.js/Node.
|
||||
|
||||
Returns:
|
||||
{bool} -- True if successful
|
||||
"""
|
||||
result = await write_env({"NODE_ENV": "production", "_HYPERGLASS_URL_": "/"})
|
||||
if result:
|
||||
log.debug(result)
|
||||
return True
|
||||
|
||||
|
||||
# Prometheus Config
|
||||
@ -141,20 +181,16 @@ async def metrics(request):
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def http_exception_handler(request, exc):
|
||||
"""Handle application errors."""
|
||||
"""Handle web server errors."""
|
||||
return UJSONResponse(
|
||||
{
|
||||
"output": exc.detail.get("message", params.messages.general),
|
||||
"alert": exc.detail.get("alert", "error"),
|
||||
"keywords": exc.detail.get("keywords", []),
|
||||
},
|
||||
{"output": exc.detail, "alert": "danger", "keywords": []},
|
||||
status_code=exc.status_code,
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(HyperglassError)
|
||||
async def http_exception_handler(request, exc):
|
||||
"""Handle request validation errors."""
|
||||
"""Handle application errors."""
|
||||
return UJSONResponse(
|
||||
{"output": exc.message, "alert": exc.alert, "keywords": exc.keywords},
|
||||
status_code=400,
|
||||
|
@ -4,9 +4,9 @@
|
||||
def start():
|
||||
"""Start the web server with Uvicorn ASGI."""
|
||||
import uvicorn
|
||||
from hyperglass.hyperglass import app, APP_PARAMS
|
||||
from hyperglass.hyperglass import app, ASGI_PARAMS
|
||||
|
||||
uvicorn.run(app, **APP_PARAMS)
|
||||
uvicorn.run(app, **ASGI_PARAMS)
|
||||
|
||||
|
||||
app = start()
|
||||
|
13
manage.py
13
manage.py
@ -611,21 +611,22 @@ def build_ui():
|
||||
def dev_server(host, port, build):
|
||||
"""Renders theme and web build, then starts dev web server"""
|
||||
try:
|
||||
from hyperglass.hyperglass import app, APP_PARAMS
|
||||
from hyperglass.hyperglass import app, ASGI_PARAMS
|
||||
except ImportError as import_error:
|
||||
raise click.ClickException(
|
||||
click.style("✗ Error importing hyperglass: ", fg="red", bold=True)
|
||||
+ click.style(import_error, fg="blue")
|
||||
)
|
||||
asgi_params = ASGI_PARAMS.copy()
|
||||
if host is not None:
|
||||
APP_PARAMS["host"] = host
|
||||
asgi_params["host"] = host
|
||||
if port is not None:
|
||||
APP_PARAMS["port"] = port
|
||||
asgi_params["port"] = port
|
||||
|
||||
write_env_variables(
|
||||
{
|
||||
"NODE_ENV": "development",
|
||||
"_HYPERGLASS_URL_": f'http://{APP_PARAMS["host"]}:{APP_PARAMS["port"]}/',
|
||||
"_HYPERGLASS_URL_": f'http://{asgi_params["host"]}:{asgi_params["port"]}/',
|
||||
}
|
||||
)
|
||||
if build:
|
||||
@ -637,9 +638,9 @@ def dev_server(host, port, build):
|
||||
+ click.style(e, fg="white")
|
||||
) from None
|
||||
if build_complete:
|
||||
start_dev_server(app, APP_PARAMS)
|
||||
start_dev_server(app, asgi_params)
|
||||
if not build:
|
||||
start_dev_server(app, APP_PARAMS)
|
||||
start_dev_server(app, asgi_params)
|
||||
|
||||
|
||||
@hg.command("migrate-configs", help="Copy YAML examples to usable config files")
|
||||
|
Reference in New Issue
Block a user