Commit 3d684c94 authored by unknown's avatar unknown
Browse files

Handle VersionConflictError at _req level, and improve doc

parent 89136881
import logging
import inspect
from base64 import b64encode
from os.path import join
from typing import Optional
......@@ -13,13 +14,31 @@ from .settings import Settings
MAX_RETRIES = 5 # max retries number
WAIT_BETWEEN_RETRIES_IN_MS = 200 # time between each retry
class VersionConflictError(Exception):
"""Raised when there is a version conflict when updating ES doc"""
pass
class BuildingGeneric:
"""Stores the methods used to receive or update the building info from ES doc"""
"""
Stores the methods used to receive or update the building info from ES doc
There are diferent levels for requesting:
=> req_with_retry : request again on error. To be used for GET essentially
=> update_with_version: pass the version of the ES doc to the payload.
To be used:
=> for operations related to parts of doc that risk to be erased concurrently
=> whenever there is a risk that the doc gets updated by the time the request get caught by the BKC API
(since a fetch operation is done within the BKC API)
Eg: to be used for facade update
Note: for all put methods, the version is included in the payload at the BKC API level, if not provided at the worker level (ie here)
This way, a VersionConflictError pops whenever a conflicts of versions happens
"""
def __init__(self, uid: str = None, **kwargs) -> None:
......@@ -30,15 +49,15 @@ class BuildingGeneric:
self._uid = uid
self._building = None
self.settings = Settings(staging=staging, registry=registry)
self.settings = Settings(staging=staging, registry=registry, **kwargs)
if self._uid:
self.fetch()
self.fetch(timeout=kwargs.get("timeout", None))
def __getitem__(self, item):
return self._building.get(item, None)
def __setitem__ (self, key, item):
def __setitem__(self, key, item):
self._building[key] = item
# ----------------- Building level ---------------------
......@@ -57,9 +76,11 @@ class BuildingGeneric:
buildings = self._req_with_retries("get", self.settings.base_url, **kwargs)
return buildings
def fetch(self) -> None:
def fetch(self, **kwargs) -> None:
"""Fetch the building document"""
self._building = self._req_with_retries("get", "/".join([self.settings.base_url, self._uid]))
self._building = self._req_with_retries(
"get", "/".join([self.settings.base_url, self._uid]), **kwargs
)
def update_building(self, payload) -> None:
"""
......@@ -68,14 +89,15 @@ class BuildingGeneric:
"""
self._req("put", "/".join([self.settings.base_url, self._uid]), json=payload)
def update_status_history(self, status: str) -> None:
def update_status_history(self, status: str, **kwargs) -> None:
"""
Update the history.
Note: the retries for conflict error are managed within BKC API
"""
payload = {"new_entry": status}
url = "/".join([self.settings.base_url, self._uid, "history"])
return self._req(method="put", url=url, json=payload)
return self._req(method="put", url=url, json=payload, **kwargs)
def register_error_for_building(self, error_code: str):
"""
......@@ -91,9 +113,7 @@ class BuildingGeneric:
"""Upload the facade metadata to Hbase"""
body = {"metadata": payload}
self._req_with_retries(
"post",
"/".join([self.settings.base_url, self._uid, "metadata", name]),
json=body,
"post", "/".join([self.settings.base_url, self._uid, "metadata", name]), json=body,
)
# ----------------- Roof level ---------------------
......@@ -140,26 +160,29 @@ class BuildingGeneric:
image = self._req_with_retries(
"get",
"/".join([self.settings.base_url, self._uid, "facade", facade_id, "image", image_name]),
"/".join(
[self.settings.base_url, self._uid, "facade", facade_id, "image", image_name]
),
)
return image["image"]
def get_segmentation_vectors(self, facade_id: str, item: str):
"""Get segmentation vectors for the specified item (eg: door) from Hbase"""
vectors = self._req_with_retries(
"get", "/".join([self.settings.base_url, self._uid, "facade", facade_id, item, "vectors"])
"get",
"/".join([self.settings.base_url, self._uid, "facade", facade_id, item, "vectors"]),
)
return vectors
def upload_facade_image_by_name(
self, facade_id: str, image_name: str, image: bytes
):
def upload_facade_image_by_name(self, facade_id: str, image_name: str, image: bytes):
"""Upload the facade image to Hbase"""
body = {"image": b64encode(image).decode()}
self._req_with_retries(
"post",
"/".join([self.settings.base_url, self._uid, "facade", facade_id, "image", image_name]),
"/".join(
[self.settings.base_url, self._uid, "facade", facade_id, "image", image_name]
),
json=body,
)
......@@ -187,9 +210,7 @@ class BuildingGeneric:
for v_index, visible_facade in enumerate(facade["visible_facades"]):
if visible_facade["id"] == visible_facade_id:
for k, v in kwargs.items():
self._building["facade"][f_index]["visible_facades"][v_index][
k
] = v
self._building["facade"][f_index]["visible_facades"][v_index][k] = v
def filter_value(value):
"""
......@@ -221,11 +242,7 @@ class BuildingGeneric:
return value
facade = [
{
k: filter_value(v)
for k, v in f.items()
if filter_value(v) or filter_value(v) == 0
}
{k: filter_value(v) for k, v in f.items() if filter_value(v) or filter_value(v) == 0}
for f in self._building["facade"]
]
......@@ -261,8 +278,21 @@ class BuildingGeneric:
headers = {}
headers = {**headers, "Api-Key": self.settings.api_key}
res = requests.request(method, url, json=json, headers=headers, timeout=timeout, **kwargs)
res.raise_for_status()
try:
res = requests.request(
method, url, json=json, headers=headers, timeout=timeout, **kwargs
)
res.raise_for_status()
except HTTPError as e:
if (
e.response.status_code == 500
and "version conflict error" in e.response.text.lower()
):
raise VersionConflictError
else:
raise
try:
return res.json()
except JSONDecodeError:
......@@ -272,6 +302,7 @@ class BuildingGeneric:
def _force_update_with_version(self, payload: dict, url: str) -> None:
"""
Update doc using current doc version to prevent version conflict errors
This will trigger a VersionConflictError on version conflict
Retry if VersionConflictError
"""
self.fetch()
......@@ -283,13 +314,6 @@ class BuildingGeneric:
Update ES doc using the version of the doc that was fetched
Use this to prevent version conflict when uploading data
"""
try:
res = self._req(
"put", url, json={**payload, "version": str(self._building["version"])}
)
except HTTPError as e:
if e.response.status_code == 500 and "version conflict error" in e.response.text.lower():
raise VersionConflictError
else:
raise
return res
res = self._req("put", url, json={**payload, "version": str(self._building["version"])})
......@@ -5,7 +5,7 @@ from MaxIcsRegistry import MaxIcsRegistry
class Settings:
"""Settings for BKC communication"""
def __init__(self, staging: bool = False, registry=None) -> None:
def __init__(self, staging: bool = False, registry=None, **kwargs) -> None:
if not registry:
registry = MaxIcsRegistry.from_env(staging=staging)
......@@ -13,6 +13,13 @@ class Settings:
building_center_info = registry.get_info("building-knowledge-information-center")
self.api_key = next(iter(building_center_info["api_in"].values()))["key"]
self.base_url = "http://{}:{}/api/buildings".format(
building_center_info["service_addr"], building_center_info["service_port"]
)
if "base_url" in kwargs.keys():
self.base_url = kwargs["base_url"]
else:
self.base_url = "http://{}:{}/api/buildings".format(
building_center_info["service_addr"], building_center_info["service_port"]
)
def __str__(self):
return "Settings __dict__: %s" % str(self.__dict__)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment