1
0
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:
checktheroads
2020-01-20 10:17:52 -07:00
parent abcb87de46
commit 65ccce5aff
5 changed files with 92 additions and 26 deletions

View File

@ -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__()})"

View File

@ -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):

View File

@ -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,

View File

@ -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()

View File

@ -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")