Config: cleanup, use '__' as ENV nested delimiter; adjust CI test
This commit is contained in:
parent
ed3812b0f0
commit
898197209a
2 changed files with 55 additions and 67 deletions
|
@ -31,4 +31,4 @@ jobs:
|
||||||
run: uv pip install --python=$UV_PROJECT_ENVIRONMENT --no-deps .
|
run: uv pip install --python=$UV_PROJECT_ENVIRONMENT --no-deps .
|
||||||
|
|
||||||
- name: Run tests (API call)
|
- name: Run tests (API call)
|
||||||
run: GISAF_DB_HOST=gisaf-database pytest -s tests/basic.py
|
run: GISAF__DB__HOST=gisaf-database pytest -s tests/basic.py
|
||||||
|
|
|
@ -3,27 +3,29 @@ import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Type, Tuple
|
from typing import Any, Type, Tuple
|
||||||
from yaml import safe_load
|
from yaml import safe_load
|
||||||
|
from importlib.metadata import version
|
||||||
|
|
||||||
from xdg import BaseDirectory
|
from xdg import BaseDirectory
|
||||||
|
from pydantic import BaseModel
|
||||||
from pydantic_settings import (
|
from pydantic_settings import (
|
||||||
BaseSettings,
|
BaseSettings,
|
||||||
PydanticBaseSettingsSource,
|
PydanticBaseSettingsSource,
|
||||||
SettingsConfigDict,
|
SettingsConfigDict,
|
||||||
|
YamlConfigSettingsSource,
|
||||||
)
|
)
|
||||||
from pydantic.v1.utils import deep_update
|
|
||||||
|
|
||||||
from importlib.metadata import version
|
|
||||||
|
|
||||||
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(BaseDirectory.xdg_config_home) / "gisaf" / f"{ENV}.yaml",
|
||||||
Path(BaseDirectory.xdg_config_home) / "gisaf" / ENV,
|
Path(BaseDirectory.xdg_config_home) / "gisaf" / f"{ENV}.yml",
|
||||||
|
Path(Path.cwd().root) / "etc" / "gisaf" / f"{ENV}.yaml",
|
||||||
|
Path(Path.cwd().root) / "etc" / "gisaf" / f"{ENV}.yml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class DashboardHome(BaseSettings):
|
class DashboardHome(BaseModel):
|
||||||
title: str = "Gisaf - home/dashboards"
|
title: str = "Gisaf - home/dashboards"
|
||||||
content_file: Path = (
|
content_file: Path = (
|
||||||
Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_content.html"
|
Path(Path.cwd().root) / "etc" / "gisaf" / "dashboard_home_content.html"
|
||||||
|
@ -33,7 +35,7 @@ class DashboardHome(BaseSettings):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GisafConfig(BaseSettings):
|
class GisafConfig(BaseModel):
|
||||||
title: str = "Gisaf"
|
title: str = "Gisaf"
|
||||||
windowTitle: str = "Gisaf"
|
windowTitle: str = "Gisaf"
|
||||||
debugLevel: str = "INFO"
|
debugLevel: str = "INFO"
|
||||||
|
@ -42,7 +44,7 @@ class GisafConfig(BaseSettings):
|
||||||
use_pretty_errors: bool = False
|
use_pretty_errors: bool = False
|
||||||
|
|
||||||
|
|
||||||
class SpatialSysRef(BaseSettings):
|
class SpatialSysRef(BaseModel):
|
||||||
author: str = "AVSM"
|
author: str = "AVSM"
|
||||||
ellps: str = "WGS84"
|
ellps: str = "WGS84"
|
||||||
k: int = 1
|
k: int = 1
|
||||||
|
@ -56,12 +58,12 @@ class SpatialSysRef(BaseSettings):
|
||||||
y_0: float = 1328608.994
|
y_0: float = 1328608.994
|
||||||
|
|
||||||
|
|
||||||
class RawSurvey(BaseSettings):
|
class RawSurvey(BaseModel):
|
||||||
spatial_sys_ref: SpatialSysRef = SpatialSysRef()
|
spatial_sys_ref: SpatialSysRef = SpatialSysRef()
|
||||||
srid: int = 910001
|
srid: int = 910001
|
||||||
|
|
||||||
|
|
||||||
class Geo(BaseSettings):
|
class Geo(BaseModel):
|
||||||
raw_survey: RawSurvey = RawSurvey()
|
raw_survey: RawSurvey = RawSurvey()
|
||||||
simplify_geom_factor: int = 10000000
|
simplify_geom_factor: int = 10000000
|
||||||
simplify_preserve_topology: bool = False
|
simplify_preserve_topology: bool = False
|
||||||
|
@ -69,17 +71,17 @@ class Geo(BaseSettings):
|
||||||
srid_for_proj: int = 32644
|
srid_for_proj: int = 32644
|
||||||
|
|
||||||
|
|
||||||
# class Flask(BaseSettings):
|
# class Flask(BaseModel):
|
||||||
# secret_key: str
|
# secret_key: str
|
||||||
# debug: int
|
# debug: int
|
||||||
|
|
||||||
|
|
||||||
class MQTT(BaseSettings):
|
class MQTT(BaseModel):
|
||||||
broker: str = "localhost"
|
broker: str = "localhost"
|
||||||
port: int = 1883
|
port: int = 1883
|
||||||
|
|
||||||
|
|
||||||
class GisafLive(BaseSettings):
|
class GisafLive(BaseModel):
|
||||||
hostname: str = "localhost"
|
hostname: str = "localhost"
|
||||||
port: int = 80
|
port: int = 80
|
||||||
scheme: str = "http"
|
scheme: str = "http"
|
||||||
|
@ -87,24 +89,24 @@ class GisafLive(BaseSettings):
|
||||||
mqtt: MQTT = MQTT()
|
mqtt: MQTT = MQTT()
|
||||||
|
|
||||||
|
|
||||||
class DefaultSurvey(BaseSettings):
|
class DefaultSurvey(BaseModel):
|
||||||
surveyor_id: int = 1
|
surveyor_id: int = 1
|
||||||
equipment_id: int = 1
|
equipment_id: int = 1
|
||||||
|
|
||||||
|
|
||||||
class Survey(BaseSettings):
|
class Survey(BaseModel):
|
||||||
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(BaseModel):
|
||||||
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(BaseModel):
|
||||||
# uri: str
|
# uri: str
|
||||||
host: str = "localhost"
|
host: str = "localhost"
|
||||||
port: int = 5432
|
port: int = 5432
|
||||||
|
@ -124,21 +126,21 @@ class DB(BaseSettings):
|
||||||
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(BaseModel):
|
||||||
level: str = "WARNING"
|
level: str = "WARNING"
|
||||||
|
|
||||||
|
|
||||||
class OGCAPILicense(BaseSettings):
|
class OGCAPILicense(BaseModel):
|
||||||
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(BaseModel):
|
||||||
name: str = "Organization Name"
|
name: str = "Organization Name"
|
||||||
url: str = "https://pygeoapi.io"
|
url: str = "https://pygeoapi.io"
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIServerContact(BaseSettings):
|
class OGCAPIServerContact(BaseModel):
|
||||||
name: str = "Lastname, Firstname"
|
name: str = "Lastname, Firstname"
|
||||||
position: str = "Position Title"
|
position: str = "Position Title"
|
||||||
address: str = "Mailing Address"
|
address: str = "Mailing Address"
|
||||||
|
@ -150,7 +152,7 @@ class OGCAPIServerContact(BaseSettings):
|
||||||
url: str | None = None
|
url: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIIdentification(BaseSettings):
|
class OGCAPIIdentification(BaseModel):
|
||||||
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"]
|
||||||
|
@ -159,26 +161,26 @@ class OGCAPIIdentification(BaseSettings):
|
||||||
url: str = "http://example.org"
|
url: str = "http://example.org"
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIMetadata(BaseSettings):
|
class OGCAPIMetadata(BaseModel):
|
||||||
identification: OGCAPIIdentification = OGCAPIIdentification()
|
identification: OGCAPIIdentification = OGCAPIIdentification()
|
||||||
license: OGCAPILicense = OGCAPILicense()
|
license: OGCAPILicense = OGCAPILicense()
|
||||||
provider: OGCAPIProvider = OGCAPIProvider()
|
provider: OGCAPIProvider = OGCAPIProvider()
|
||||||
contact: OGCAPIServerContact = OGCAPIServerContact()
|
contact: OGCAPIServerContact = OGCAPIServerContact()
|
||||||
|
|
||||||
|
|
||||||
class ServerBind(BaseSettings):
|
class ServerBind(BaseModel):
|
||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
port: int = 5000
|
port: int = 5000
|
||||||
|
|
||||||
|
|
||||||
class OGCAPIServerMap(BaseSettings):
|
class OGCAPIServerMap(BaseModel):
|
||||||
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 = (
|
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>"""
|
"""<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(BaseModel):
|
||||||
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"
|
||||||
|
@ -189,7 +191,7 @@ class OGCAPIServer(BaseSettings):
|
||||||
map: OGCAPIServerMap = OGCAPIServerMap()
|
map: OGCAPIServerMap = OGCAPIServerMap()
|
||||||
|
|
||||||
|
|
||||||
class OGCAPI(BaseSettings):
|
class OGCAPI(BaseModel):
|
||||||
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()
|
||||||
|
@ -197,7 +199,7 @@ class OGCAPI(BaseSettings):
|
||||||
server: OGCAPIServer = OGCAPIServer()
|
server: OGCAPIServer = OGCAPIServer()
|
||||||
|
|
||||||
|
|
||||||
class TileServer(BaseSettings):
|
class TileServer(BaseModel):
|
||||||
baseDir: Path = Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_files_dir"
|
baseDir: Path = Path(BaseDirectory.xdg_data_home) / "gisaf" / "mbtiles_files_dir"
|
||||||
useRequestUrl: bool = False
|
useRequestUrl: bool = False
|
||||||
spriteBaseDir: Path = (
|
spriteBaseDir: Path = (
|
||||||
|
@ -213,7 +215,7 @@ class TileServer(BaseSettings):
|
||||||
self.spriteBaseDir.mkdir(parents=True, exist_ok=True)
|
self.spriteBaseDir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
class Map(BaseSettings):
|
class Map(BaseModel):
|
||||||
tileServer: TileServer = TileServer()
|
tileServer: TileServer = TileServer()
|
||||||
zoom: int = 14
|
zoom: int = 14
|
||||||
pitch: int = 45
|
pitch: int = 45
|
||||||
|
@ -228,11 +230,11 @@ class Map(BaseSettings):
|
||||||
tagKeys: list[str] = ["source"]
|
tagKeys: list[str] = ["source"]
|
||||||
|
|
||||||
|
|
||||||
class Measures(BaseSettings):
|
class Measures(BaseModel):
|
||||||
defaultStore: str | None = None
|
defaultStore: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class BasketDefault(BaseSettings):
|
class BasketDefault(BaseModel):
|
||||||
surveyor: str = "Default surveyor"
|
surveyor: str = "Default surveyor"
|
||||||
equipment: str = "Default equipment"
|
equipment: str = "Default equipment"
|
||||||
project: str = "Default project"
|
project: str = "Default project"
|
||||||
|
@ -240,40 +242,40 @@ class BasketDefault(BaseSettings):
|
||||||
store: str | None = None
|
store: str | None = None
|
||||||
|
|
||||||
|
|
||||||
# class BasketOldDef(BaseSettings):
|
# class BasketOldDef(BaseModel):
|
||||||
# base_dir: str
|
# base_dir: str
|
||||||
|
|
||||||
|
|
||||||
class Basket(BaseSettings):
|
class Basket(BaseModel):
|
||||||
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(BaseModel):
|
||||||
maxDataSize: int = 10000
|
maxDataSize: int = 10000
|
||||||
|
|
||||||
|
|
||||||
class Dashboard(BaseSettings):
|
class Dashboard(BaseModel):
|
||||||
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(BaseModel):
|
||||||
footer: str = (
|
footer: str = (
|
||||||
"""Generated by <span class='link' onclick="window.open('https://redmine.auroville.org.in/projects/gisaf/')">Gisaf</span>"""
|
"""Generated by <span class='link' onclick="window.open('https://redmine.auroville.org.in/projects/gisaf/')">Gisaf</span>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Admin(BaseSettings):
|
class Admin(BaseModel):
|
||||||
basket: Basket = Basket()
|
basket: Basket = Basket()
|
||||||
|
|
||||||
|
|
||||||
class Attachments(BaseSettings):
|
class Attachments(BaseModel):
|
||||||
base_dir: str = "/var/local/gisaf/attachments"
|
base_dir: str = "/var/local/gisaf/attachments"
|
||||||
|
|
||||||
|
|
||||||
class Job(BaseSettings):
|
class Job(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
func: str
|
func: str
|
||||||
trigger: str
|
trigger: str
|
||||||
|
@ -281,7 +283,7 @@ class Job(BaseSettings):
|
||||||
seconds: int | None = 0
|
seconds: int | None = 0
|
||||||
|
|
||||||
|
|
||||||
class Crs(BaseSettings):
|
class Crs(BaseModel):
|
||||||
"""
|
"""
|
||||||
Handy definitions for crs-es
|
Handy definitions for crs-es
|
||||||
"""
|
"""
|
||||||
|
@ -295,7 +297,8 @@ class Crs(BaseSettings):
|
||||||
|
|
||||||
class Config(BaseSettings):
|
class Config(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
# env_prefix='gisaf_',
|
env_prefix="GISAF__",
|
||||||
|
nested_model_default_partial_update=True,
|
||||||
env_nested_delimiter="__",
|
env_nested_delimiter="__",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -308,7 +311,15 @@ 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
|
configs = [
|
||||||
|
YamlConfigSettingsSource(settings_cls, yaml_file=cf) for cf in config_files
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
env_settings,
|
||||||
|
init_settings,
|
||||||
|
file_secret_settings,
|
||||||
|
*configs,
|
||||||
|
)
|
||||||
|
|
||||||
# def __init__(self, **kwargs):
|
# def __init__(self, **kwargs):
|
||||||
# super().__init__(**kwargs)
|
# super().__init__(**kwargs)
|
||||||
|
@ -353,31 +364,8 @@ class Config(BaseSettings):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def config_file_settings() -> dict[str, Any]:
|
|
||||||
config: dict[str, Any] = {}
|
|
||||||
for p in config_files:
|
|
||||||
for suffix in {".yaml", ".yml"}:
|
|
||||||
path = p.with_suffix(suffix)
|
|
||||||
if not path.is_file():
|
|
||||||
logger.info(f"No file found at `{path.resolve()}`")
|
|
||||||
continue
|
|
||||||
logger.debug(f"Reading config file `{path.resolve()}`")
|
|
||||||
if path.suffix in {".yaml", ".yml"}:
|
|
||||||
config = deep_update(config, load_yaml(path))
|
|
||||||
else:
|
|
||||||
logger.info(f"Unknown config file extension `{path.suffix}`")
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def load_yaml(path: Path) -> dict[str, Any]:
|
|
||||||
with Path(path).open("r") as f:
|
|
||||||
config = safe_load(f)
|
|
||||||
if not isinstance(config, dict):
|
|
||||||
raise TypeError(f"Config file has no top-level mapping: {path}")
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
conf = Config()
|
conf = Config()
|
||||||
|
breakpoint()
|
||||||
|
|
||||||
# def set_app_config(app) -> None:
|
# def set_app_config(app) -> None:
|
||||||
# raw_configs = []
|
# raw_configs = []
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue