diff --git a/pyproject.toml b/pyproject.toml index bc721a3..f8ae259 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "websockets>=12.0", "pyxdg>=0.28", "typer-slim>=0.15.1", + "httpx>=0.28.1", ] requires-python = ">=3.12" readme = "README.md" @@ -62,5 +63,4 @@ dev-dependencies = [ "types-python-jose>=3.3.4.20240106", "types-passlib>=1.7.7.20240311", "pytest>=8.3.4", - "httpx>=0.28.1", ] diff --git a/src/gisaf/api/map.py b/src/gisaf/api/map.py index a2dd8df..0c89e46 100644 --- a/src/gisaf/api/map.py +++ b/src/gisaf/api/map.py @@ -1,15 +1,21 @@ from collections import OrderedDict, defaultdict import logging -from json import dumps +from json import dumps, loads -from fastapi import (APIRouter, Request, HTTPException, - Response, status) +from httpx import AsyncClient +from fastapi import APIRouter, Request, HTTPException, Response, status from sqlalchemy.orm import selectinload from sqlalchemy.exc import NoResultFound from sqlmodel import select -from gisaf.models.map_bases import (BaseMap, BaseMapLayer, BaseMapWithStores, - BaseStyle, MapInitData, MaplibreStyle) +from gisaf.models.map_bases import ( + BaseMap, + BaseMapLayer, + BaseMapWithStores, + BaseStyle, + MapInitData, + MaplibreStyle, +) from gisaf.models.misc import Qml from gisaf.registry import registry from gisaf.database import db_session, fastapi_db_session @@ -24,25 +30,31 @@ api = APIRouter( # responses={404: {"description": "Not found"}}, ) + async def get_base_styles(): async with db_session() as session: - query = select(BaseStyle.name)\ - .where(BaseStyle.enabled==True)\ - .order_by(BaseStyle.id) # type: ignore # noqa: E712 + query = ( + select(BaseStyle.name) + .where(BaseStyle.enabled == True) + .order_by(BaseStyle.id) + ) # type: ignore # noqa: E712 data = await session.exec(query) base_styles = data.all() ## TODO: tiles_registry - logger.warning('TODO: tiles_registry') + logger.warning("TODO: tiles_registry") # base_styles.extend(tiles_registry.mbtiles.values()) - return [BaseStyle(name=bs) for bs in base_styles] # type: ignore + return [BaseStyle(name=bs) for bs in base_styles] # type: ignore + async def get_base_maps() -> list[BaseMapWithStores]: async with db_session() as session: - query1 = select(BaseMap) #.options(selectinload(BaseMap.layers)) # type: ignore + query1 = select( + BaseMap + ) # .options(selectinload(BaseMap.layers)) # type: ignore data1 = await session.exec(query1) base_maps = data1.all() base_map_dict = {bm.id: bm.name for bm in base_maps} - query2 = select(BaseMapLayer).options(selectinload(BaseMapLayer.base_map)) # type: ignore + query2 = select(BaseMapLayer).options(selectinload(BaseMapLayer.base_map)) # type: ignore data2 = await session.exec(query2) base_map_layers = data2.all() bms: dict[str, list] = defaultdict(list) @@ -54,23 +66,28 @@ async def get_base_maps() -> list[BaseMapWithStores]: for bm, bmls in OrderedDict(sorted(bms.items())).items() ] -@api.get('/init-data') + +@api.get("/init-data") async def get_init_data() -> MapInitData: await registry.update_stores_counts() - df = registry.stores.reset_index().\ - drop(columns=['model', 'raw_model', 'base_gis_type']) + df = registry.stores.reset_index().drop( + columns=["model", "raw_model", "base_gis_type"] + ) return MapInitData( baseStyles=await get_base_styles(), baseMaps=await get_base_maps(), groups=registry.primary_groups, - stores=df.to_dict(orient='records') # type: ignore + stores=df.to_dict(orient="records"), # type: ignore ) -@api.get('/base_style/{name}') -async def get_base_style(request: Request, name: str, - db_session: fastapi_db_session, - ) -> BaseStyle: - data = await db_session.exec(select(BaseStyle).where(BaseStyle.name==name)) + +@api.get("/base_style/{name}") +async def get_base_style( + request: Request, + name: str, + db_session: fastapi_db_session, +) -> BaseStyle: + data = await db_session.exec(select(BaseStyle).where(BaseStyle.name == name)) try: base_style = data.one() except NoResultFound: @@ -79,19 +96,26 @@ async def get_base_style(request: Request, name: str, # name=name, # style=dumps({}) # ) + if base_style.static_url is not None: + ## Externally managed style, no need to get anything from tiles registry + async with AsyncClient() as client: + resp = await client.get(base_style.static_url) + base_style.style = loads(resp.content) if name in tiles_registry.mbtiles: ## Try to get base_style from tiles_registry - tiles = tiles_registry.mbtiles['name'] - style = dumps(await tiles.get_style(style_record=base_style, - request=request)) + tiles = tiles_registry.mbtiles["name"] + style = dumps(await tiles.get_style(style_record=base_style, request=request)) else: - style = base_style.style # type: ignore - return BaseStyle(name=name, style=style) # type: ignore + style = base_style.style # type: ignore + return BaseStyle(name=name, style=style) # type: ignore -@api.get('/layer_style/{store}') -async def get_layer_style(request: Request, store: str, - response: Response, - ) -> MaplibreStyle | None: + +@api.get("/layer_style/{store}") +async def get_layer_style( + request: Request, + store: str, + response: Response, +) -> MaplibreStyle | None: try: store_record = registry.stores.loc[store] except KeyError: @@ -101,18 +125,18 @@ async def get_layer_style(request: Request, store: str, ## Get layer_defs from live redis and give symbol return await redis_store.get_maplibre_style(store) ## Set the etag based on the last modification of the model's style. - #if store in info.schema.app['registry'].geom_auto: + # if store in info.schema.app['registry'].geom_auto: if store_record.custom: ## The style is in Qml - ttag_channel = 'gisaf_map.qml' + ttag_channel = "gisaf_map.qml" else: ## The style is in Category - ttag_channel = 'gisaf_survey.category' + ttag_channel = "gisaf_survey.category" ## Check if the request was etagged: ttag = await redis_store.get_ttag(ttag_channel) - if ttag and request.headers.get('If-None-Match') == ttag: + if ttag and request.headers.get("If-None-Match") == ttag: raise HTTPException(status_code=status.HTTP_304_NOT_MODIFIED) # request.not_modified = True # return MaplibreStyle() - response.headers['ETag'] = ttag + response.headers["ETag"] = ttag return await store_record.model.get_maplibre_style() diff --git a/src/gisaf/models/store.py b/src/gisaf/models/store.py index 95d9bdf..a96a3de 100644 --- a/src/gisaf/models/store.py +++ b/src/gisaf/models/store.py @@ -12,30 +12,31 @@ class Store(StoreNameOnly): count: int | None = None custom: bool description: str - #extra: dict[str, Any] | None + # extra: dict[str, Any] | None gis_type: str group: str - #icon: str + # icon: str in_menu: bool is_db: bool is_line_work: bool is_live: bool long_name: str | None - #mapbox_layout: dict[str, Any] | None - #mapbox_paint: dict[str, Any] | None + # mapbox_layout: dict[str, Any] | None + # mapbox_paint: dict[str, Any] | None type: str # mapbox_type_custom: str | None - #mapbox_type_default: str + # mapbox_type_default: str minor_group_1: str | None minor_group_2: str | None - #model: GeoModel - #name_letter: str - #name_number: int - #raw_model: GeoPointSurveyModel - #raw_model_store_name: str + # model: GeoModel + # name_letter: str + # name_number: int + # raw_model: GeoPointSurveyModel + # raw_model_store_name: str status: str style: str | None symbol: str | None title: str - viewable_role: str | None - z_index: int \ No newline at end of file + viewable_role: str | None = None + z_index: int = 500 + diff --git a/uv.lock b/uv.lock index bffe4d5..436ba4c 100644 --- a/uv.lock +++ b/uv.lock @@ -538,7 +538,7 @@ wheels = [ [[package]] name = "gisaf-backend" -version = "0.6.0a0" +version = "0.0.0" source = { editable = "." } dependencies = [ { name = "aiopath" }, @@ -548,6 +548,7 @@ dependencies = [ { name = "fastapi" }, { name = "geoalchemy2" }, { name = "geopandas" }, + { name = "httpx" }, { name = "itsdangerous" }, { name = "matplotlib" }, { name = "orjson" }, @@ -586,7 +587,6 @@ mqtt = [ [package.dev-dependencies] dev = [ { name = "asyncpg-stubs" }, - { name = "httpx" }, { name = "ipdb" }, { name = "pandas-stubs" }, { name = "pretty-errors" }, @@ -610,6 +610,7 @@ requires-dist = [ { name = "geopandas", specifier = ">=1.0.1" }, { name = "gisaf-backend", extras = ["contextily"], marker = "extra == 'all'" }, { name = "gisaf-backend", extras = ["mqtt"], marker = "extra == 'all'" }, + { name = "httpx", specifier = ">=0.28.1" }, { name = "itsdangerous", specifier = ">=2.1.2" }, { name = "matplotlib", specifier = ">=3.8.3" }, { name = "orjson", specifier = ">=3.9.10" }, @@ -636,7 +637,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "asyncpg-stubs", specifier = ">=0.29.1" }, - { name = "httpx", specifier = ">=0.28.1" }, { name = "ipdb", specifier = ">=0.13.13" }, { name = "pandas-stubs", specifier = ">=2.1.4.231218" }, { name = "pretty-errors", specifier = ">=1.2.25" },