Skip to content

Commit

Permalink
Merge pull request #74 from pblottiere/add_cache_entrypoint
Browse files Browse the repository at this point in the history
Add basic cache entrypoints
  • Loading branch information
pblottiere authored Aug 21, 2024
2 parents c1f577b + 48826b7 commit 0cc4b5f
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 17 deletions.
25 changes: 25 additions & 0 deletions docs/src/qsa-api/endpoints/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,28 @@ $ curl "http://localhost:5000/api/projects/my_project/styles" \
}
}'
````

## Cache

| Method | URL | Description |
|---------|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
| GET | `/api/projects/{project}/cache` | Return metadata about the cache |
| POST | `/api/projects/{project}/cache/reset` | Clear cached data and reset cache configuration |

Example:

```` shell
$ curl "http://localhost:5000/api/projects/my_project/cache"
{
"valid":true,
"storage":"filesystem"
}
````

<div class="warning">
Reset cache

When a QGIS project is created manually without QSA, the cache is not
initialized. This method allows to create the MapProxy configuration file
accordingly.
</div>
36 changes: 36 additions & 0 deletions qsa-api/qsa_api/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,3 +436,39 @@ def project_del_layer(name, layer_name):
except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415


@projects.get("/<name>/cache")
def project_cache(name):
log_request()
try:
psql_schema = request.args.get("schema", default="public")
project = QSAProject(name, psql_schema)
if project.exists():
cache_infos, err = project.cache_metadata()
if err:
return {"error": err}, 415
return jsonify(cache_infos), 201
else:
return {"error": "Project does not exist"}, 415
except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415


@projects.post("/<name>/cache/reset")
def project_cache_reset(name):
log_request()
try:
psql_schema = request.args.get("schema", default="public")
project = QSAProject(name, psql_schema)
if project.exists():
rc, err = project.cache_reset()
if err:
return {"error": err}, 415
return jsonify(rc), 201
else:
return {"error": "Project does not exist"}, 415
except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415
22 changes: 21 additions & 1 deletion qsa-api/qsa_api/mapproxy/mapproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ def write(self) -> None:
with open(self._mapproxy_project, "w") as file:
yaml.safe_dump(self.cfg, file, sort_keys=False)

def read(self) -> bool:
def read(self) -> (bool, str):
# if a QGIS project is created manually without QSA, the MapProxy
# configuration file may not be created at this point.
if not self._mapproxy_project.exists():
self.create()

try:
with open(self._mapproxy_project, "r") as file:
self.cfg = yaml.safe_load(file)
Expand All @@ -48,6 +53,21 @@ def read(self) -> bool:

return True, ""

def metadata(self) -> dict:
md = {}

md["storage"] = ""
md["valid"] = False

if self._mapproxy_project.exists():
md["valid"] = True

md["storage"] = "filesystem"
if config().mapproxy_cache_s3_bucket:
md["storage"] = "s3"

return md

def clear_cache(self, layer_name: str) -> None:
if config().mapproxy_cache_s3_bucket:
bucket_name = config().mapproxy_cache_s3_bucket
Expand Down
86 changes: 70 additions & 16 deletions qsa-api/qsa_api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,44 @@ def metadata(self) -> dict:

if StorageBackend.type() == StorageBackend.POSTGRESQL:
m["schema"] = self.schema

m["cache"] = "disabled"
if self._mapproxy_enabled:
m["cache"] = "mapproxy"

return m

def cache_metadata(self) -> (dict, str):
if self._mapproxy_enabled:
return QSAMapProxy(self.name).metadata(), ""
return {}, "Cache is disabled"

def cache_reset(self) -> (bool, str):
if self._mapproxy_enabled:
mp = QSAMapProxy(self.name)
rc, err = mp.read()
if not rc:
return False, err

p = QgsProject()
p.read(self._qgis_project_uri)

for layer in p.mapLayers().values():
t = layer.type()
bbox = QSAProject._layer_bbox(layer)
epsg_code = QSAProject._layer_epsg_code(layer)

mp.remove_layer(layer.name())
mp.add_layer(
layer.name(), bbox, epsg_code, t == Qgis.LayerType.Raster, None
)

mp.write()

return True, ""

return False, "Cache is disabled"

def style_default(self, geometry: str) -> bool:
con = sqlite3.connect(self.sqlite_db.as_posix())
cur = con.cursor()
Expand Down Expand Up @@ -245,7 +281,7 @@ def layer_update_style(
def layer_exists(self, name: str) -> bool:
return bool(self.layer(name))

def remove_layer(self, name: str) -> None:
def remove_layer(self, name: str) -> bool:
# remove layer in qgis project
project = QgsProject()
project.read(self._qgis_project_uri, Qgis.ProjectReadFlag.DontResolveLayers)
Expand All @@ -260,7 +296,11 @@ def remove_layer(self, name: str) -> None:
# remove layer in mapproxy config
if self._mapproxy_enabled:
mp = QSAMapProxy(self.name)
mp.read()
rc, err = mp.read()
if not rc:
self.debug(err)
return False

mp.remove_layer(name)
mp.write()

Expand Down Expand Up @@ -319,8 +359,13 @@ def remove(self) -> None:
for layer in self.layers:
self.remove_layer(layer)

# remove mapproxy config file
if self._mapproxy_enabled:
mp = QSAMapProxy(self.name)
mp.remove()

# remove qsa projects dir
shutil.rmtree(self._qgis_project_dir)
shutil.rmtree(self._qgis_project_dir, ignore_errors=True)

# remove remove qgis prohect in db if necessary
if StorageBackend.type() == StorageBackend.POSTGRESQL:
Expand Down Expand Up @@ -412,20 +457,10 @@ def add_layer(
if self._mapproxy_enabled:
self.debug("Update MapProxy configuration file")

bbox = list(
map(
float,
lyr.extent()
.asWktCoordinates()
.replace(",", "")
.split(" "),
)
)

authid_items = lyr.crs().authid().split(":")
if len(authid_items) < 2:
bbox = QSAProject._layer_bbox(lyr)
epsg_code = QSAProject._layer_epsg_code(lyr)
if epsg_code < 0:
return False, f"Invalid CRS {lyr.crs().authid()}"
epsg_code = int(authid_items[1])

self.debug(f"EPSG code {epsg_code}")

Expand Down Expand Up @@ -704,6 +739,25 @@ def _layer_provider(layer_type: Qgis.LayerType, datasource: str) -> str:
provider = "gdal"
return provider

@staticmethod
def _layer_epsg_code(lyr) -> int:
authid_items = lyr.crs().authid().split(":")
if len(authid_items) < 2:
return -1
return int(authid_items[1])

@staticmethod
def _layer_bbox(lyr) -> list:
return list(
map(
float,
lyr.extent()
.asWktCoordinates()
.replace(",", "")
.split(" "),
)
)

@property
def _qgis_project_uri(self) -> str:
if StorageBackend.type() == StorageBackend.POSTGRESQL:
Expand Down

0 comments on commit 0cc4b5f

Please sign in to comment.