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