diff --git a/.bumpversion.cfg b/.bumpversion.cfg index dfecc1cc..ae0a4085 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 5.18.0 +current_version = 5.19.0 commit = True tag = True diff --git a/caveclient/__init__.py b/caveclient/__init__.py index f50517f8..bc3940cf 100644 --- a/caveclient/__init__.py +++ b/caveclient/__init__.py @@ -1,4 +1,4 @@ -__version__ = "5.18.0" +__version__ = "5.19.0" from .frameworkclient import CAVEclient diff --git a/caveclient/base.py b/caveclient/base.py index 054a1d13..5e21d613 100644 --- a/caveclient/base.py +++ b/caveclient/base.py @@ -215,6 +215,7 @@ def __init__( self._api_version = api_version self._endpoints = endpoints self._fc = over_client + self._server_version = self._get_version() @property def fc(self): @@ -234,7 +235,11 @@ def api_version(self): def _get_version(self) -> Optional[Version]: endpoint_mapping = self.default_url_mapping - url = self._endpoints.get("get_version", None).format_map(endpoint_mapping) + endpoint = self._endpoints.get("get_version", None) + if endpoint is None: + return None + + url = endpoint.format_map(endpoint_mapping) response = self.session.get(url) if response.status_code == 404: # server doesn't have this endpoint yet return None @@ -349,8 +354,11 @@ def _version_fails_constraint(version: Version, constraint: str = None): if constraint is None: return False else: - specifier = SpecifierSet(constraint) - return version not in specifier + if version is None: + return True + else: + specifier = SpecifierSet(constraint) + return version not in specifier @parametrized @@ -399,17 +407,17 @@ def wrapper(*args, **kwargs): ) raise ServerIncompatibilityError(msg) - - for kwarg, kwarg_constraint in kwarg_use_constraints.items(): - if _version_fails_constraint(self.server_version, kwarg_constraint): - msg = ( - f"Use of keyword argument `{kwarg}` in `{method.__name__}` " - "is only permitted " - f"for server version {kwarg_constraint}, your server " - f"version is {self.server_version}. Contact your system " - "administrator to update the server version." - ) - raise ServerIncompatibilityError(msg) + if kwarg_use_constraints is not None: + for kwarg, kwarg_constraint in kwarg_use_constraints.items(): + if _version_fails_constraint(self.server_version, kwarg_constraint): + msg = ( + f"Use of keyword argument `{kwarg}` in `{method.__name__}` " + "is only permitted " + f"for server version {kwarg_constraint}, your server " + f"version is {self.server_version}. Contact your system " + "administrator to update the server version." + ) + raise ServerIncompatibilityError(msg) out = method(*args, **kwargs) return out diff --git a/caveclient/chunkedgraph.py b/caveclient/chunkedgraph.py index 8fa7c8bf..a9d8d052 100644 --- a/caveclient/chunkedgraph.py +++ b/caveclient/chunkedgraph.py @@ -189,7 +189,6 @@ def __init__( self._default_timestamp = timestamp self._table_name = table_name self._segmentation_info = None - self._server_version = self._get_version() @property def default_url_mapping(self): diff --git a/caveclient/endpoints.py b/caveclient/endpoints.py index 39d98b69..b871883f 100644 --- a/caveclient/endpoints.py +++ b/caveclient/endpoints.py @@ -219,6 +219,11 @@ "upload_state_w_id": json_v1 + "/post/{state_id}", "get_state": json_v1 + "/{state_id}", "get_state_raw": json_v1 + "/raw/{state_id}", + "get_properties": json_v1 + "/property/{state_id}/info", + "upload_properties": json_v1 + "/property/post", + "get_properties_raw": json_v1 + "/property/raw/{state_id}", + "upload_properties_w_id": json_v1 + "/property/post/{state_id}", + "get_version": json_v1 + "/version", } json_legacy = "{json_server_address}/nglstate" diff --git a/caveclient/jsonservice.py b/caveclient/jsonservice.py index 6f904be3..b9b38410 100644 --- a/caveclient/jsonservice.py +++ b/caveclient/jsonservice.py @@ -9,6 +9,7 @@ from .base import ( ClientBase, _api_endpoints, + _check_version_compatibility, handle_response, ) from .endpoints import ( @@ -186,6 +187,27 @@ def get_state_json(self, state_id): handle_response(response, as_json=False) return json.loads(response.content) + @_check_version_compatibility(method_constraint=">=0.4.0") + def get_property_json(self, state_id): + """Download a Neuroglancer JSON state + + Parameters + ---------- + state_id : int + ID of a JSON state uploaded to the state service. + + Returns + ------- + dict + JSON specifying a Neuroglancer state. + """ + url_mapping = self.default_url_mapping + url_mapping["state_id"] = state_id + url = self._endpoints["get_property"].format_map(url_mapping) + response = self.session.get(url) + handle_response(response, as_json=False) + return json.loads(response.content) + def upload_state_json(self, json_state, state_id=None, timestamp=None): """Upload a Neuroglancer JSON state @@ -224,6 +246,45 @@ def upload_state_json(self, json_state, state_id=None, timestamp=None): response_re = re.search(".*\/(\d+)", str(response.content)) return int(response_re.groups()[0]) + @_check_version_compatibility(">=0.4.0") + def upload_property_json(self, property_json, state_id=None, timestamp=None): + """Upload a Neuroglancer JSON state + + Parameters + ---------- + propery_json : dict + Dict representation of a neuroglancer segment properties json + state_id : int + ID of a JSON state uploaded to the state service. + Using a state_id is an admin feature. + timestamp: time.time + Timestamp for json state date. Requires state_id. + + Returns + ------- + int + state_id of the uploaded JSON state + """ + url_mapping = self.default_url_mapping + + if state_id is None: + url = self._endpoints["upload_properties"].format_map(url_mapping) + else: + url_mapping = self.default_url_mapping + url_mapping["state_id"] = state_id + url = self._endpoints["upload_properties_w_id"].format_map(url_mapping) + + response = self.session.post( + url, + data=json.dumps( + property_json, + default=neuroglancer_json_encoder, + ), + ) + handle_response(response, as_json=False) + response_re = re.search(".*\/(\d+)", str(response.content)) + return int(response_re.groups()[0]) + def save_state_json_local(self, json_state, filename, overwrite=False): """Save a Neuroglancer JSON state to a JSON file locally. @@ -251,6 +312,7 @@ def build_neuroglancer_url( ngl_url=None, target_site=None, static_url=False, + format_propeties=False, ): """Build a URL for a Neuroglancer deployment that will automatically retrieve specified state. If the datastack is specified, this is prepopulated from the info file field "viewer_site". @@ -269,7 +331,8 @@ def build_neuroglancer_url( Default is None. static_url : bool If True, treats "state_id" as a static URL directly to the JSON and does not use the state service. - + format_propeties : bool + If True, formats the url as a segment_properties info file Returns ------- str @@ -304,6 +367,14 @@ def build_neuroglancer_url( target_site_error = "A specified target_site must be one of 'seunglab', 'cave-explorer' or 'mainline'" raise ValueError(target_site_error) + if format_propeties: + url_mapping = self.default_url_mapping + url_mapping["state_id"] = state_id + get_state_url = self._endpoints["get_properties"][:-5].format_map( + url_mapping + ) + url = "precomputed://" + auth_text + get_state_url + return url if static_url: url = ngl_url + parameter_text + state_id else: