Fix config for ws devices
Cleanups
This commit is contained in:
parent
50b4dff145
commit
41e92fad57
2 changed files with 135 additions and 97 deletions
|
@ -3,55 +3,63 @@ import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Type, Tuple
|
from typing import Any, Type, Tuple
|
||||||
|
|
||||||
from pydantic_settings import (BaseSettings,
|
from pydantic_settings import (
|
||||||
PydanticBaseSettingsSource,
|
BaseSettings,
|
||||||
SettingsConfigDict)
|
PydanticBaseSettingsSource,
|
||||||
#from pydantic import ConfigDict
|
SettingsConfigDict,
|
||||||
|
)
|
||||||
|
|
||||||
|
# from pydantic import ConfigDict
|
||||||
from pydantic.v1.utils import deep_update
|
from pydantic.v1.utils import deep_update
|
||||||
from yaml import safe_load
|
from yaml import safe_load
|
||||||
|
|
||||||
from gisaf._version import __version__
|
from gisaf._version import __version__
|
||||||
#from sqlalchemy.ext.asyncio.engine import AsyncEngine
|
# from sqlalchemy.ext.asyncio.engine import AsyncEngine
|
||||||
#from sqlalchemy.orm.session import sessionmaker
|
# from sqlalchemy.orm.session import sessionmaker
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
ENV = environ.get('env', 'prod')
|
ENV = environ.get("env", "prod")
|
||||||
|
|
||||||
config_files = [
|
config_files = [
|
||||||
Path(Path.cwd().root) / 'etc' / 'gisaf' / ENV,
|
Path(Path.cwd().root) / "etc" / "gisaf" / ENV,
|
||||||
Path.home() / '.local' / 'gisaf' / ENV
|
Path.home() / ".local" / "gisaf" / ENV,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class DashboardHome(BaseSettings):
|
class DashboardHome(BaseSettings):
|
||||||
title: str = 'Gisaf - home/dashboards'
|
title: str = "Gisaf - home/dashboards"
|
||||||
content_file: str = '/etc/gisaf/dashboard_home_content.html'
|
content_file: str = "/etc/gisaf/dashboard_home_content.html"
|
||||||
footer_file: str = '/etc/gisaf/dashboard_home_footer.html'
|
footer_file: str = "/etc/gisaf/dashboard_home_footer.html"
|
||||||
|
|
||||||
|
|
||||||
class GisafConfig(BaseSettings):
|
class GisafConfig(BaseSettings):
|
||||||
title: str = 'Gisaf'
|
title: str = "Gisaf"
|
||||||
windowTitle: str = 'Gisaf'
|
windowTitle: str = "Gisaf"
|
||||||
debugLevel: str = 'INFO'
|
debugLevel: str = "INFO"
|
||||||
dashboard_home: DashboardHome = DashboardHome()
|
dashboard_home: DashboardHome = DashboardHome()
|
||||||
redirect: str = ''
|
redirect: str = ""
|
||||||
use_pretty_errors: bool = False
|
use_pretty_errors: bool = False
|
||||||
|
|
||||||
|
|
||||||
class SpatialSysRef(BaseSettings):
|
class SpatialSysRef(BaseSettings):
|
||||||
author: str = 'AVSM'
|
author: str = "AVSM"
|
||||||
ellps: str = 'WGS84'
|
ellps: str = "WGS84"
|
||||||
k: int = 1
|
k: int = 1
|
||||||
lat_0: float = 12.01605433
|
lat_0: float = 12.01605433
|
||||||
lon_0: float = 79.80998934
|
lon_0: float = 79.80998934
|
||||||
no_defs: bool = True
|
no_defs: bool = True
|
||||||
proj: str = 'tmerc'
|
proj: str = "tmerc"
|
||||||
towgs84: str = '0,0,0,0,0,0,0'
|
towgs84: str = "0,0,0,0,0,0,0"
|
||||||
units: str = 'm'
|
units: str = "m"
|
||||||
x_0: float = 370455.630
|
x_0: float = 370455.630
|
||||||
y_0: float = 1328608.994
|
y_0: float = 1328608.994
|
||||||
|
|
||||||
|
|
||||||
class RawSurvey(BaseSettings):
|
class RawSurvey(BaseSettings):
|
||||||
spatial_sys_ref: SpatialSysRef = SpatialSysRef()
|
spatial_sys_ref: SpatialSysRef = SpatialSysRef()
|
||||||
srid: int = 910001
|
srid: int = 910001
|
||||||
|
|
||||||
|
|
||||||
class Geo(BaseSettings):
|
class Geo(BaseSettings):
|
||||||
raw_survey: RawSurvey = RawSurvey()
|
raw_survey: RawSurvey = RawSurvey()
|
||||||
simplify_geom_factor: int = 10000000
|
simplify_geom_factor: int = 10000000
|
||||||
|
@ -59,43 +67,50 @@ class Geo(BaseSettings):
|
||||||
srid: int = 4326
|
srid: int = 4326
|
||||||
srid_for_proj: int = 32644
|
srid_for_proj: int = 32644
|
||||||
|
|
||||||
|
|
||||||
# class Flask(BaseSettings):
|
# class Flask(BaseSettings):
|
||||||
# secret_key: str
|
# secret_key: str
|
||||||
# debug: int
|
# debug: int
|
||||||
|
|
||||||
|
|
||||||
class MQTT(BaseSettings):
|
class MQTT(BaseSettings):
|
||||||
broker: str = 'localhost'
|
broker: str = "localhost"
|
||||||
port: int = 1883
|
port: int = 1883
|
||||||
|
|
||||||
|
|
||||||
class GisafLive(BaseSettings):
|
class GisafLive(BaseSettings):
|
||||||
hostname: str = 'localhost'
|
hostname: str = "localhost"
|
||||||
port: int = 80
|
port: int = 80
|
||||||
scheme: str = 'http'
|
scheme: str = "http"
|
||||||
redis: str = 'redis://localhost'
|
redis: str = "redis://localhost"
|
||||||
mqtt: MQTT = MQTT()
|
mqtt: MQTT = MQTT()
|
||||||
|
|
||||||
|
|
||||||
class DefaultSurvey(BaseSettings):
|
class DefaultSurvey(BaseSettings):
|
||||||
surveyor_id: int = 1
|
surveyor_id: int = 1
|
||||||
equipment_id: int = 1
|
equipment_id: int = 1
|
||||||
|
|
||||||
|
|
||||||
class Survey(BaseSettings):
|
class Survey(BaseSettings):
|
||||||
# model_config = ConfigDict(extra='ignore')
|
# model_config = ConfigDict(extra='ignore')
|
||||||
db_schema_raw: str = 'raw_survey'
|
db_schema_raw: str = "raw_survey"
|
||||||
db_schema: str = 'survey'
|
db_schema: str = "survey"
|
||||||
default: DefaultSurvey = DefaultSurvey()
|
default: DefaultSurvey = DefaultSurvey()
|
||||||
|
|
||||||
|
|
||||||
class Crypto(BaseSettings):
|
class Crypto(BaseSettings):
|
||||||
secret: str = 'Gisaf big secret'
|
secret: str = "Gisaf big secret"
|
||||||
algorithm: str = 'HS256'
|
algorithm: str = "HS256"
|
||||||
expire: float = 21600
|
expire: float = 21600
|
||||||
|
|
||||||
|
|
||||||
class DB(BaseSettings):
|
class DB(BaseSettings):
|
||||||
# uri: str
|
# uri: str
|
||||||
host: str = 'localhost'
|
host: str = "localhost"
|
||||||
port: int = 5432
|
port: int = 5432
|
||||||
user: str = 'gisaf'
|
user: str = "gisaf"
|
||||||
db: str = 'gisaf'
|
db: str = "gisaf"
|
||||||
password: str = 'secret'
|
password: str = "secret"
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
info: bool = True
|
info: bool = True
|
||||||
pool_size: int = 10
|
pool_size: int = 10
|
||||||
|
@ -103,41 +118,46 @@ class DB(BaseSettings):
|
||||||
echo: bool = False
|
echo: bool = False
|
||||||
|
|
||||||
def get_sqla_url(self):
|
def get_sqla_url(self):
|
||||||
return f'postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}'
|
return f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}"
|
||||||
|
|
||||||
def get_pg_url(self):
|
def get_pg_url(self):
|
||||||
return f'postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}'
|
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}"
|
||||||
|
|
||||||
|
|
||||||
class Log(BaseSettings):
|
class Log(BaseSettings):
|
||||||
level: str = 'WARNING'
|
level: str = "WARNING"
|
||||||
|
|
||||||
|
|
||||||
class OGCAPILicense(BaseSettings):
|
class OGCAPILicense(BaseSettings):
|
||||||
name: str = 'CC-BY 4.0 license'
|
name: str = "CC-BY 4.0 license"
|
||||||
url: str = 'https://creativecommons.org/licenses/by/4.0/'
|
url: str = "https://creativecommons.org/licenses/by/4.0/"
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIProvider(BaseSettings):
|
class OGCAPIProvider(BaseSettings):
|
||||||
name: str = 'Organization Name'
|
name: str = "Organization Name"
|
||||||
url: str = 'https://pygeoapi.io'
|
url: str = "https://pygeoapi.io"
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIServerContact(BaseSettings):
|
class OGCAPIServerContact(BaseSettings):
|
||||||
name: str = 'Lastname, Firstname'
|
name: str = "Lastname, Firstname"
|
||||||
position: str = 'Position Title'
|
position: str = "Position Title"
|
||||||
address: str = 'Mailing Address'
|
address: str = "Mailing Address"
|
||||||
city: str = 'City'
|
city: str = "City"
|
||||||
stateorprovince: str = 'Administrative Area'
|
stateorprovince: str = "Administrative Area"
|
||||||
postalcode: int = 0
|
postalcode: int = 0
|
||||||
country: str = 'Country'
|
country: str = "Country"
|
||||||
email: str = 'you@example.org'
|
email: str = "you@example.org"
|
||||||
url: str | None = None
|
url: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIIdentification(BaseSettings):
|
class OGCAPIIdentification(BaseSettings):
|
||||||
title: str = 'pygeoapi default instance'
|
title: str = "pygeoapi default instance"
|
||||||
description: str = 'pygeoapi provides an API to geospatial data'
|
description: str = "pygeoapi provides an API to geospatial data"
|
||||||
keywords: list[str] = ['geospatial', 'data', 'api']
|
keywords: list[str] = ["geospatial", "data", "api"]
|
||||||
keywords_type: str = 'theme'
|
keywords_type: str = "theme"
|
||||||
terms_of_service: str = 'https://creativecommons.org/licenses/by/4.0/'
|
terms_of_service: str = "https://creativecommons.org/licenses/by/4.0/"
|
||||||
url: str = 'http://example.org'
|
url: str = "http://example.org"
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIMetadata(BaseSettings):
|
class OGCAPIMetadata(BaseSettings):
|
||||||
identification: OGCAPIIdentification = OGCAPIIdentification()
|
identification: OGCAPIIdentification = OGCAPIIdentification()
|
||||||
|
@ -145,38 +165,44 @@ class OGCAPIMetadata(BaseSettings):
|
||||||
provider: OGCAPIProvider = OGCAPIProvider()
|
provider: OGCAPIProvider = OGCAPIProvider()
|
||||||
contact: OGCAPIServerContact = OGCAPIServerContact()
|
contact: OGCAPIServerContact = OGCAPIServerContact()
|
||||||
|
|
||||||
|
|
||||||
class ServerBind(BaseSettings):
|
class ServerBind(BaseSettings):
|
||||||
host: str = '0.0.0.0'
|
host: str = "0.0.0.0"
|
||||||
port: int = 5000
|
port: int = 5000
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIServerMap(BaseSettings):
|
class OGCAPIServerMap(BaseSettings):
|
||||||
url: str = 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png'
|
url: str = "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png"
|
||||||
attribution: str = '''<a href="https://wikimediafoundation.org/wiki/Maps_Terms_of_Use">Wikimedia maps</a> | Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap contributors</a>'''
|
attribution: str = """<a href="https://wikimediafoundation.org/wiki/Maps_Terms_of_Use">Wikimedia maps</a> | Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap contributors</a>"""
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIServer(BaseSettings):
|
class OGCAPIServer(BaseSettings):
|
||||||
bind: ServerBind = ServerBind()
|
bind: ServerBind = ServerBind()
|
||||||
url: str = 'https://example.org/ogcapi'
|
url: str = "https://example.org/ogcapi"
|
||||||
mimetype: str = 'application/json; charset=UTF-8'
|
mimetype: str = "application/json; charset=UTF-8"
|
||||||
encoding: str = 'utf-8'
|
encoding: str = "utf-8"
|
||||||
language: str = 'en-US'
|
language: str = "en-US"
|
||||||
pretty_print: bool = False
|
pretty_print: bool = False
|
||||||
limit: int = 1000
|
limit: int = 1000
|
||||||
map: OGCAPIServerMap = OGCAPIServerMap()
|
map: OGCAPIServerMap = OGCAPIServerMap()
|
||||||
|
|
||||||
|
|
||||||
class OGCAPI(BaseSettings):
|
class OGCAPI(BaseSettings):
|
||||||
base_url: str = 'http://example.org/ogcapi'
|
base_url: str = "http://example.org/ogcapi"
|
||||||
bbox: list[float] = [-180, -90, 180, 90]
|
bbox: list[float] = [-180, -90, 180, 90]
|
||||||
log: Log = Log()
|
log: Log = Log()
|
||||||
metadata: OGCAPIMetadata = OGCAPIMetadata()
|
metadata: OGCAPIMetadata = OGCAPIMetadata()
|
||||||
server: OGCAPIServer = OGCAPIServer()
|
server: OGCAPIServer = OGCAPIServer()
|
||||||
|
|
||||||
|
|
||||||
class TileServer(BaseSettings):
|
class TileServer(BaseSettings):
|
||||||
baseDir: str = '/path/to/mbtiles_files_dir'
|
baseDir: str = "/path/to/mbtiles_files_dir"
|
||||||
useRequestUrl: bool = False
|
useRequestUrl: bool = False
|
||||||
spriteBaseDir: str = '/path/to/mbtiles_sprites_dir'
|
spriteBaseDir: str = "/path/to/mbtiles_sprites_dir"
|
||||||
spriteUrl: str = '/tiles/sprite/sprite'
|
spriteUrl: str = "/tiles/sprite/sprite"
|
||||||
spriteBaseUrl: str = 'https://gisaf.example.org'
|
spriteBaseUrl: str = "https://gisaf.example.org"
|
||||||
openMapTilesKey: str | None = None
|
openMapTilesKey: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class Map(BaseSettings):
|
class Map(BaseSettings):
|
||||||
tileServer: TileServer = TileServer()
|
tileServer: TileServer = TileServer()
|
||||||
|
@ -185,46 +211,56 @@ class Map(BaseSettings):
|
||||||
lat: float = 12.0000
|
lat: float = 12.0000
|
||||||
lng: float = 79.8106
|
lng: float = 79.8106
|
||||||
bearing: float = 0
|
bearing: float = 0
|
||||||
style: str = 'OSM (vector)'
|
style: str = "OSM (vector)"
|
||||||
opacity: float = 1
|
opacity: float = 1
|
||||||
attribution: str = ''
|
attribution: str = ""
|
||||||
status: list[str] = ['E', 'F', 'D']
|
status: list[str] = ["E", "F", "D"]
|
||||||
defaultStatus: list[str] = ['E'] # FIXME: should be str
|
defaultStatus: list[str] = ["E"] # FIXME: should be str
|
||||||
tagKeys: list[str] = ['source']
|
tagKeys: list[str] = ["source"]
|
||||||
|
|
||||||
|
|
||||||
class Measures(BaseSettings):
|
class Measures(BaseSettings):
|
||||||
defaultStore: str | None = None
|
defaultStore: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class BasketDefault(BaseSettings):
|
class BasketDefault(BaseSettings):
|
||||||
surveyor: str = 'Default surveyor'
|
surveyor: str = "Default surveyor"
|
||||||
equipment: str = 'Default equipment'
|
equipment: str = "Default equipment"
|
||||||
project: str = 'Default project'
|
project: str = "Default project"
|
||||||
status: str = 'E'
|
status: str = "E"
|
||||||
store: str | None = None
|
store: str | None = None
|
||||||
|
|
||||||
|
|
||||||
# class BasketOldDef(BaseSettings):
|
# class BasketOldDef(BaseSettings):
|
||||||
# base_dir: str
|
# base_dir: str
|
||||||
|
|
||||||
|
|
||||||
class Basket(BaseSettings):
|
class Basket(BaseSettings):
|
||||||
base_dir: str = '/var/local/gisaf/baskets'
|
base_dir: str = "/var/local/gisaf/baskets"
|
||||||
default: BasketDefault = BasketDefault()
|
default: BasketDefault = BasketDefault()
|
||||||
|
|
||||||
|
|
||||||
class Plot(BaseSettings):
|
class Plot(BaseSettings):
|
||||||
maxDataSize: int = 10000
|
maxDataSize: int = 10000
|
||||||
|
|
||||||
|
|
||||||
class Dashboard(BaseSettings):
|
class Dashboard(BaseSettings):
|
||||||
base_source_url: str = 'http://url.to.jupyter/lab/tree/'
|
base_source_url: str = "http://url.to.jupyter/lab/tree/"
|
||||||
base_storage_dir: str = '/var/lib/share/gisaf/dashboard'
|
base_storage_dir: str = "/var/lib/share/gisaf/dashboard"
|
||||||
base_storage_url: str = '/dashboard-attachment/'
|
base_storage_url: str = "/dashboard-attachment/"
|
||||||
|
|
||||||
|
|
||||||
class Widgets(BaseSettings):
|
class Widgets(BaseSettings):
|
||||||
footer: str = """Generated by <span class='link' onclick="window.open('https://redmine.auroville.org.in/projects/gisaf/')">Gisaf</span>"""
|
footer: str = """Generated by <span class='link' onclick="window.open('https://redmine.auroville.org.in/projects/gisaf/')">Gisaf</span>"""
|
||||||
|
|
||||||
|
|
||||||
class Admin(BaseSettings):
|
class Admin(BaseSettings):
|
||||||
basket: Basket = Basket()
|
basket: Basket = Basket()
|
||||||
|
|
||||||
|
|
||||||
class Attachments(BaseSettings):
|
class Attachments(BaseSettings):
|
||||||
base_dir: str = '/var/local/gisaf/attachments'
|
base_dir: str = "/var/local/gisaf/attachments"
|
||||||
|
|
||||||
|
|
||||||
class Job(BaseSettings):
|
class Job(BaseSettings):
|
||||||
id: str
|
id: str
|
||||||
|
@ -233,20 +269,23 @@ class Job(BaseSettings):
|
||||||
minutes: int | None = 0
|
minutes: int | None = 0
|
||||||
seconds: int | None = 0
|
seconds: int | None = 0
|
||||||
|
|
||||||
|
|
||||||
class Crs(BaseSettings):
|
class Crs(BaseSettings):
|
||||||
'''
|
"""
|
||||||
Handy definitions for crs-es
|
Handy definitions for crs-es
|
||||||
'''
|
"""
|
||||||
|
|
||||||
db: str
|
db: str
|
||||||
geojson: str
|
geojson: str
|
||||||
for_proj: str
|
for_proj: str
|
||||||
survey: str
|
survey: str
|
||||||
web_mercator: str
|
web_mercator: str
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
class Config(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
#env_prefix='gisaf_',
|
# env_prefix='gisaf_',
|
||||||
env_nested_delimiter='__',
|
env_nested_delimiter="__",
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -258,7 +297,7 @@ class Config(BaseSettings):
|
||||||
dotenv_settings: PydanticBaseSettingsSource,
|
dotenv_settings: PydanticBaseSettingsSource,
|
||||||
file_secret_settings: PydanticBaseSettingsSource,
|
file_secret_settings: PydanticBaseSettingsSource,
|
||||||
) -> Tuple[PydanticBaseSettingsSource, ...]:
|
) -> Tuple[PydanticBaseSettingsSource, ...]:
|
||||||
return env_settings, init_settings, file_secret_settings, config_file_settings # type: ignore
|
return env_settings, init_settings, file_secret_settings, config_file_settings # type: ignore
|
||||||
|
|
||||||
# def __init__(self, **kwargs):
|
# def __init__(self, **kwargs):
|
||||||
# super().__init__(**kwargs)
|
# super().__init__(**kwargs)
|
||||||
|
@ -289,19 +328,20 @@ class Config(BaseSettings):
|
||||||
plugins: dict[str, dict[str, Any]] = {}
|
plugins: dict[str, dict[str, Any]] = {}
|
||||||
survey: Survey = Survey()
|
survey: Survey = Survey()
|
||||||
version: str = __version__
|
version: str = __version__
|
||||||
weather_station: dict[str, list[dict[str, Any]] | dict[str, Any]] = {}
|
weather_station: dict[str, dict[str, Any]] = {}
|
||||||
widgets: Widgets = Widgets()
|
widgets: Widgets = Widgets()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def crs(self) -> Crs:
|
def crs(self) -> Crs:
|
||||||
return Crs(
|
return Crs(
|
||||||
db=f'epsg:{self.geo.srid}',
|
db=f"epsg:{self.geo.srid}",
|
||||||
geojson=f'epsg:{self.geo.srid}',
|
geojson=f"epsg:{self.geo.srid}",
|
||||||
for_proj=f'epsg:{self.geo.srid_for_proj}',
|
for_proj=f"epsg:{self.geo.srid_for_proj}",
|
||||||
survey=f'epsg:{self.geo.raw_survey.srid}',
|
survey=f"epsg:{self.geo.raw_survey.srid}",
|
||||||
web_mercator='epsg:3857',
|
web_mercator="epsg:3857",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def config_file_settings() -> dict[str, Any]:
|
def config_file_settings() -> dict[str, Any]:
|
||||||
config: dict[str, Any] = {}
|
config: dict[str, Any] = {}
|
||||||
for p in config_files:
|
for p in config_files:
|
||||||
|
@ -322,9 +362,7 @@ def load_yaml(path: Path) -> dict[str, Any]:
|
||||||
with Path(path).open("r") as f:
|
with Path(path).open("r") as f:
|
||||||
config = safe_load(f)
|
config = safe_load(f)
|
||||||
if not isinstance(config, dict):
|
if not isinstance(config, dict):
|
||||||
raise TypeError(
|
raise TypeError(f"Config file has no top-level mapping: {path}")
|
||||||
f"Config file has no top-level mapping: {path}"
|
|
||||||
)
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -368,4 +406,4 @@ conf = Config()
|
||||||
|
|
||||||
|
|
||||||
# def get_cache_dir() -> Path:
|
# def get_cache_dir() -> Path:
|
||||||
# return Path(conf.storage['root_cache_path'])
|
# return Path(conf.storage['root_cache_path'])
|
||||||
|
|
|
@ -12,7 +12,7 @@ import asyncio
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from importlib.metadata import entry_points
|
from importlib.metadata import entry_points
|
||||||
from typing import Any, Mapping, List
|
from typing import Any, List
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue