From 979a8dbb851e4c86553166f6461181ada139f945 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Wed, 16 Sep 2026 09:49:04 -0500 Subject: [PATCH 1/5] Enable lazy imports in the generator. --- tools/developer_tools/python-client/ClientApiConfig.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 } From 24dea7aed24046764b4918011483729279c9cc3d Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Wed, 16 Sep 2026 09:49:15 -0500 Subject: [PATCH 2/5] Update version of the generator. --- tools/developer_tools/python-client/generatePyClient.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From e5a5bad49c6fb1659f3923abc753a2ba473416e0 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Wed, 16 Sep 2026 09:49:30 -0500 Subject: [PATCH 3/5] Add lazy loading for belyApiFactory --- .../packages/api/BelyApiFactory.py | 117 ++++++++++++------ 1 file changed, 82 insertions(+), 35 deletions(-) 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) From ee43f5be31e72834ba3c714ab34cbdc0b0991e71 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Wed, 16 Sep 2026 09:49:54 -0500 Subject: [PATCH 4/5] Prepare Release --- tools/developer_tools/bely-cli/conda-recipe/meta.yaml | 2 +- tools/developer_tools/bely-cli/pyproject.toml | 4 ++-- .../developer_tools/python-client/conda-recipe/API/meta.yaml | 2 +- .../developer_tools/python-client/packages/api/pyproject.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) 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/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/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 = [ From 77a62de929eb2b14d7563df7a2dc56501a86ee85 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Wed, 16 Sep 2026 11:47:37 -0500 Subject: [PATCH 5/5] Release updated API --- tools/developer_tools/bely-cli/uv.lock | 4 ++-- tools/developer_tools/python-client/uv.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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/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" },