diff --git a/tools/developer_tools/bely-cli/conda-recipe/meta.yaml b/tools/developer_tools/bely-cli/conda-recipe/meta.yaml index b41bba122..b6ec5c561 100644 --- a/tools/developer_tools/bely-cli/conda-recipe/meta.yaml +++ b/tools/developer_tools/bely-cli/conda-recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "bely-cli" %} -{% set version = "2026.3.0" %} +{% set version = "2026.3.1" %} package: name: "{{ name|lower }}" diff --git a/tools/developer_tools/bely-cli/pyproject.toml b/tools/developer_tools/bely-cli/pyproject.toml index 57242c6c6..4047d7ae4 100644 --- a/tools/developer_tools/bely-cli/pyproject.toml +++ b/tools/developer_tools/bely-cli/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bely-cli" -version = "2026.3.0" +version = "2026.3.1" description = "Command-line interface for the BELY electronic logbook" readme = "README.md" requires-python = ">=3.10" @@ -13,7 +13,7 @@ maintainers = [{ name = "Dariusz Jarosz", email = "djarosz@aps.anl.gov" }] dependencies = [ "click>=8.1.0", "PyYAML>=6.0.0", - "bely-api==2026.3.0", + "bely-api==2026.3.1", "textual>=0.86.0", "rich>=13.7.0", ] diff --git a/tools/developer_tools/bely-cli/uv.lock b/tools/developer_tools/bely-cli/uv.lock index bee40f1d9..29a2a19d2 100644 --- a/tools/developer_tools/bely-cli/uv.lock +++ b/tools/developer_tools/bely-cli/uv.lock @@ -17,7 +17,7 @@ wheels = [ [[package]] name = "bely-api" -version = "2026.3.0" +version = "2026.3.1" source = { directory = "../python-client/packages/api" } dependencies = [ { name = "certifi" }, @@ -36,7 +36,7 @@ requires-dist = [ [[package]] name = "bely-cli" -version = "2026.3.0" +version = "2026.3.1" source = { editable = "." } dependencies = [ { name = "bely-api" }, diff --git a/tools/developer_tools/python-client/ClientApiConfig.yml b/tools/developer_tools/python-client/ClientApiConfig.yml index 5cef507df..cf28e5d1c 100644 --- a/tools/developer_tools/python-client/ClientApiConfig.yml +++ b/tools/developer_tools/python-client/ClientApiConfig.yml @@ -1,3 +1,4 @@ { - "packageName": "belyApi" + "packageName": "belyApi", + "lazyImports": true } diff --git a/tools/developer_tools/python-client/conda-recipe/API/meta.yaml b/tools/developer_tools/python-client/conda-recipe/API/meta.yaml index 3587ac99d..b87b84fc3 100644 --- a/tools/developer_tools/python-client/conda-recipe/API/meta.yaml +++ b/tools/developer_tools/python-client/conda-recipe/API/meta.yaml @@ -1,5 +1,5 @@ {% set name = "bely-api" %} -{% set version = "2026.3.0" %} +{% set version = "2026.3.1" %} package: name: "{{ name|lower }}" diff --git a/tools/developer_tools/python-client/generatePyClient.sh b/tools/developer_tools/python-client/generatePyClient.sh index 31f85af61..5a9ee0778 100755 --- a/tools/developer_tools/python-client/generatePyClient.sh +++ b/tools/developer_tools/python-client/generatePyClient.sh @@ -14,7 +14,7 @@ MY_DIR=`dirname $0` && cd $MY_DIR && MY_DIR=`pwd` ROOT_DIR=$MY_DIR -OPEN_API_VERSION="7.20.0" +OPEN_API_VERSION="7.25.0" OPEN_API_GENERATOR_JAR="openapi-generator-cli-$OPEN_API_VERSION.jar" OPEN_API_GENERATOR_JAR_URL="https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/$OPEN_API_VERSION/$OPEN_API_GENERATOR_JAR" diff --git a/tools/developer_tools/python-client/packages/api/BelyApiFactory.py b/tools/developer_tools/python-client/packages/api/BelyApiFactory.py index 906b3d3b5..fc76289ff 100755 --- a/tools/developer_tools/python-client/packages/api/BelyApiFactory.py +++ b/tools/developer_tools/python-client/packages/api/BelyApiFactory.py @@ -2,23 +2,28 @@ # Copyright (c) UChicago Argonne, LLC. All rights reserved. # See LICENSE file. + +# Annotations are stored as strings so that the API return types below can be +# named without importing them at module load time. +from __future__ import annotations + import base64 import os import warnings - -from belyApi import Configuration, ApiClient -from belyApi import ( - DomainApi, - DownloadsApi, - UsersApi, - PropertyValueApi, - SystemLogApi, - SearchApi, - AuthenticationApi, - LogbookApi, - NotificationConfigurationApi, -) -from belyApi import ApiExceptionMessage +import typing + +if typing.TYPE_CHECKING: + from belyApi import ( + AuthenticationApi, + DomainApi, + DownloadsApi, + LogbookApi, + NotificationConfigurationApi, + PropertyValueApi, + SearchApi, + SystemLogApi, + UsersApi, + ) class BelyApiFactory: @@ -28,34 +33,74 @@ class BelyApiFactory: LOGBOOK_DOMAIN_ID = 1 + # attribute name -> generated api class. Each api is built on first access by + # __getattr__ so that importing this module does not drag in every belyApi + # model; see the "Known startup cost" section of bely-cli/CLAUDE.md. The + # generator's lazyImports option only pays off while this stays lazy. + _API_CLASSES = { + "auth_api": "AuthenticationApi", + "domain_api": "DomainApi", + "downloads_api": "DownloadsApi", + "logbook_api": "LogbookApi", + "notification_configuration_api": "NotificationConfigurationApi", + "property_value_api": "PropertyValueApi", + "search_api": "SearchApi", + "systemlog_api": "SystemLogApi", + "users_api": "UsersApi", + } + + # Deprecated camelCase aliases -> canonical attribute name + _API_ALIASES = { + "domainApi": "domain_api", + "downloadsApi": "downloads_api", + "propertyValueApi": "property_value_api", + "searchApi": "search_api", + "systemlogApi": "systemlog_api", + "usersApi": "users_api", + } + def __init__(self, bely_url): + from belyApi import ApiClient, Configuration + self.bely_url = bely_url self.config = Configuration(host=self.bely_url) self.api_client = ApiClient(configuration=self.config) - self.logbook_api = LogbookApi(api_client=self.api_client) - self.notification_configuration_api = NotificationConfigurationApi( - api_client=self.api_client + def __getattr__(self, name): + """Build api instances on first access. + + Only called when normal attribute lookup fails, so each api is created + once and then cached in __dict__. + """ + canonical = self._API_ALIASES.get(name, name) + class_name = self._API_CLASSES.get(canonical) + if class_name is None: + raise AttributeError( + f"{type(self).__name__!r} object has no attribute {name!r}" + ) + + # __init__ must have run; guard against access on a half-built instance + # (e.g. during unpickling) recursing back into __getattr__. + if "api_client" not in self.__dict__: + raise AttributeError( + f"cannot build {canonical!r} before BelyApiFactory.__init__ has run" + ) + + import belyApi + + api = getattr(belyApi, class_name)(api_client=self.api_client) + setattr(self, canonical, api) + if name != canonical: + setattr(self, name, api) + return api + + def __dir__(self): + return sorted( + set(super().__dir__()) + | set(self._API_CLASSES) + | set(self._API_ALIASES) ) - self.downloads_api = DownloadsApi(api_client=self.api_client) - self.property_value_api = PropertyValueApi(api_client=self.api_client) - self.users_api = UsersApi(api_client=self.api_client) - self.domain_api = DomainApi(api_client=self.api_client) - - self.systemlog_api = SystemLogApi(api_client=self.api_client) - self.search_api = SearchApi(api_client=self.api_client) - - # Deprecated camelCase aliases - self.downloadsApi = self.downloads_api - self.propertyValueApi = self.property_value_api - self.usersApi = self.users_api - self.domainApi = self.domain_api - self.systemlogApi = self.systemlog_api - self.searchApi = self.search_api - - self.auth_api = AuthenticationApi(api_client=self.api_client) - def _deprecated(self, old_name, new_name): warnings.warn( f"{old_name} is deprecated, use {new_name} instead", @@ -110,6 +155,8 @@ def logout_user(self): self.auth_api.log_out() def parse_api_exception(self, open_api_exception): + from belyApi import ApiExceptionMessage + response_type = ApiExceptionMessage.__name__ open_api_exception.data = open_api_exception.body ex_obj = self.api_client.deserialize(open_api_exception, response_type) diff --git a/tools/developer_tools/python-client/packages/api/pyproject.toml b/tools/developer_tools/python-client/packages/api/pyproject.toml index 685076793..ad0c2207e 100644 --- a/tools/developer_tools/python-client/packages/api/pyproject.toml +++ b/tools/developer_tools/python-client/packages/api/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "bely-api" -version = "2026.3.0" +version = "2026.3.1" description = "Python client API library used to communicate with BELY API." license = { text = "Copyright (c) UChicago Argonne, LLC. All rights reserved." } maintainers = [ diff --git a/tools/developer_tools/python-client/uv.lock b/tools/developer_tools/python-client/uv.lock index c00db8278..ae86bf72b 100644 --- a/tools/developer_tools/python-client/uv.lock +++ b/tools/developer_tools/python-client/uv.lock @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "bely-api" -version = "2026.3.0" +version = "2026.3.1" source = { editable = "packages/api" } dependencies = [ { name = "certifi" },