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

Handle VersionConflictError at _req level, and improve doc

parent 89136881
import logging import logging
import inspect
from base64 import b64encode from base64 import b64encode
from os.path import join from os.path import join
from typing import Optional from typing import Optional
...@@ -13,13 +14,31 @@ from .settings import Settings ...@@ -13,13 +14,31 @@ from .settings import Settings
MAX_RETRIES = 5 # max retries number MAX_RETRIES = 5 # max retries number
WAIT_BETWEEN_RETRIES_IN_MS = 200 # time between each retry WAIT_BETWEEN_RETRIES_IN_MS = 200 # time between each retry
class VersionConflictError(Exception): class VersionConflictError(Exception):
"""Raised when there is a version conflict when updating ES doc""" """Raised when there is a version conflict when updating ES doc"""
pass pass
class BuildingGeneric: 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: def __init__(self, uid: str = None, **kwargs) -> None:
...@@ -30,15 +49,15 @@ class BuildingGeneric: ...@@ -30,15 +49,15 @@ class BuildingGeneric:
self._uid = uid self._uid = uid
self._building = None self._building = None
self.settings = Settings(staging=staging, registry=registry) self.settings = Settings(staging=staging, registry=registry, **kwargs)
if self._uid: if self._uid:
self.fetch() self.fetch(timeout=kwargs.get("timeout", None))
def __getitem__(self, item): def __getitem__(self, item):
return self._building.get(item, None) return self._building.get(item, None)
def __setitem__ (self, key, item): def __setitem__(self, key, item):
self._building[key] = item self._building[key] = item
# ----------------- Building level --------------------- # ----------------- Building level ---------------------
...@@ -57,9 +76,11 @@ class BuildingGeneric: ...@@ -57,9 +76,11 @@ class BuildingGeneric:
buildings = self._req_with_retries("get", self.settings.base_url, **kwargs) buildings = self._req_with_retries("get", self.settings.base_url, **kwargs)
return buildings return buildings
def fetch(self) -> None: def fetch(self, **kwargs) -> None:
"""Fetch the building document""" """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: def update_building(self, payload) -> None:
""" """
...@@ -68,14 +89,15 @@ class BuildingGeneric: ...@@ -68,14 +89,15 @@ class BuildingGeneric:
""" """
self._req("put", "/".join([self.settings.base_url, self._uid]), json=payload) 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. Update the history.
Note: the retries for conflict error are managed within BKC API Note: the retries for conflict error are managed within BKC API
""" """
payload = {"new_entry": status} payload = {"new_entry": status}
url = "/".join([self.settings.base_url, self._uid, "history"]) 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): def register_error_for_building(self, error_code: str):
""" """
...@@ -91,9 +113,7 @@ class BuildingGeneric: ...@@ -91,9 +113,7 @@ class BuildingGeneric:
"""Upload the facade metadata to Hbase""" """Upload the facade metadata to Hbase"""
body = {"metadata": payload} body = {"metadata": payload}
self._req_with_retries( self._req_with_retries(
"post", "post", "/".join([self.settings.base_url, self._uid, "metadata", name]), json=body,
"/".join([self.settings.base_url, self._uid, "metadata", name]),
json=body,
) )
# ----------------- Roof level --------------------- # ----------------- Roof level ---------------------
...@@ -140,26 +160,29 @@ class BuildingGeneric: ...@@ -140,26 +160,29 @@ class BuildingGeneric:
image = self._req_with_retries( image = self._req_with_retries(
"get", "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"] return image["image"]
def get_segmentation_vectors(self, facade_id: str, item: str): def get_segmentation_vectors(self, facade_id: str, item: str):
"""Get segmentation vectors for the specified item (eg: door) from Hbase""" """Get segmentation vectors for the specified item (eg: door) from Hbase"""
vectors = self._req_with_retries( 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 return vectors
def upload_facade_image_by_name( def upload_facade_image_by_name(self, facade_id: str, image_name: str, image: bytes):
self, facade_id: str, image_name: str, image: bytes
):
"""Upload the facade image to Hbase""" """Upload the facade image to Hbase"""
body = {"image": b64encode(image).decode()} body = {"image": b64encode(image).decode()}
self._req_with_retries( self._req_with_retries(
"post", "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, json=body,
) )
...@@ -187,9 +210,7 @@ class BuildingGeneric: ...@@ -187,9 +210,7 @@ class BuildingGeneric:
for v_index, visible_facade in enumerate(facade["visible_facades"]): for v_index, visible_facade in enumerate(facade["visible_facades"]):
if visible_facade["id"] == visible_facade_id: if visible_facade["id"] == visible_facade_id:
for k, v in kwargs.items(): for k, v in kwargs.items():
self._building["facade"][f_index]["visible_facades"][v_index][ self._building["facade"][f_index]["visible_facades"][v_index][k] = v
k
] = v
def filter_value(value): def filter_value(value):
""" """
...@@ -221,11 +242,7 @@ class BuildingGeneric: ...@@ -221,11 +242,7 @@ class BuildingGeneric:
return value return value
facade = [ 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"] for f in self._building["facade"]
] ]
...@@ -261,8 +278,21 @@ class BuildingGeneric: ...@@ -261,8 +278,21 @@ class BuildingGeneric:
headers = {} headers = {}
headers = {**headers, "Api-Key": self.settings.api_key} 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: try:
return res.json() return res.json()
except JSONDecodeError: except JSONDecodeError:
...@@ -272,6 +302,7 @@ class BuildingGeneric: ...@@ -272,6 +302,7 @@ class BuildingGeneric:
def _force_update_with_version(self, payload: dict, url: str) -> None: def _force_update_with_version(self, payload: dict, url: str) -> None:
""" """
Update doc using current doc version to prevent version conflict errors Update doc using current doc version to prevent version conflict errors
This will trigger a VersionConflictError on version conflict
Retry if VersionConflictError Retry if VersionConflictError
""" """
self.fetch() self.fetch()
...@@ -283,13 +314,6 @@ class BuildingGeneric: ...@@ -283,13 +314,6 @@ class BuildingGeneric:
Update ES doc using the version of the doc that was fetched Update ES doc using the version of the doc that was fetched
Use this to prevent version conflict when uploading data Use this to prevent version conflict when uploading data
""" """
try:
res = self._req( res = self._req("put", url, json={**payload, "version": str(self._building["version"])})
"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
...@@ -5,7 +5,7 @@ from MaxIcsRegistry import MaxIcsRegistry ...@@ -5,7 +5,7 @@ from MaxIcsRegistry import MaxIcsRegistry
class Settings: class Settings:
"""Settings for BKC communication""" """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: if not registry:
registry = MaxIcsRegistry.from_env(staging=staging) registry = MaxIcsRegistry.from_env(staging=staging)
...@@ -13,6 +13,13 @@ class Settings: ...@@ -13,6 +13,13 @@ class Settings:
building_center_info = registry.get_info("building-knowledge-information-center") building_center_info = registry.get_info("building-knowledge-information-center")
self.api_key = next(iter(building_center_info["api_in"].values()))["key"] 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