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 &copy; <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 &copy; <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