From 6272431d26a143b06d7c5b4c953b1ac10482cd97 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 16:42:55 -0800 Subject: [PATCH 01/19] update: move to src/py --- .../py/exabyte_api_client}/__init__.py | 0 .../py/exabyte_api_client}/client.py | 6 +- .../exabyte_api_client}/endpoints/__init__.py | 0 .../endpoints/bank_entity.py | 0 .../endpoints/bank_materials.py | 0 .../endpoints/bank_workflows.py | 0 .../exabyte_api_client}/endpoints/charges.py | 0 .../exabyte_api_client}/endpoints/entity.py | 0 .../py/exabyte_api_client}/endpoints/enums.py | 0 .../py/exabyte_api_client}/endpoints/jobs.py | 0 .../py/exabyte_api_client}/endpoints/login.py | 0 .../exabyte_api_client}/endpoints/logout.py | 0 .../endpoints/materials.py | 0 .../endpoints/metaproperties.py | 0 .../endpoints/mixins/__init__.py | 0 .../endpoints/mixins/default.py | 0 .../endpoints/mixins/set.py | 0 .../exabyte_api_client}/endpoints/projects.py | 0 .../endpoints/properties.py | 0 .../endpoints/workflows.py | 0 src/py/exabyte_api_client/helpers.py | 215 ++++++++++++++++++ .../py/exabyte_api_client}/utils/__init__.py | 0 .../py/exabyte_api_client}/utils/http.py | 0 .../py/exabyte_api_client}/utils/materials.py | 0 24 files changed, 217 insertions(+), 4 deletions(-) rename {exabyte_api_client => src/py/exabyte_api_client}/__init__.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/client.py (98%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/__init__.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/bank_entity.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/bank_materials.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/bank_workflows.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/charges.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/entity.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/enums.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/jobs.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/login.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/logout.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/materials.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/metaproperties.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/mixins/__init__.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/mixins/default.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/mixins/set.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/projects.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/properties.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/endpoints/workflows.py (100%) create mode 100644 src/py/exabyte_api_client/helpers.py rename {exabyte_api_client => src/py/exabyte_api_client}/utils/__init__.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/utils/http.py (100%) rename {exabyte_api_client => src/py/exabyte_api_client}/utils/materials.py (100%) diff --git a/exabyte_api_client/__init__.py b/src/py/exabyte_api_client/__init__.py similarity index 100% rename from exabyte_api_client/__init__.py rename to src/py/exabyte_api_client/__init__.py diff --git a/exabyte_api_client/client.py b/src/py/exabyte_api_client/client.py similarity index 98% rename from exabyte_api_client/client.py rename to src/py/exabyte_api_client/client.py index 3d176a8..39ff9de 100644 --- a/exabyte_api_client/client.py +++ b/src/py/exabyte_api_client/client.py @@ -74,13 +74,11 @@ def from_env(cls) -> "AuthEnv": class Account(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True) + client: Any = Field(exclude=True, repr=False) id_cache: Optional[str] = None - class Config: - arbitrary_types_allowed = True - validate_assignment = True - @property def id(self) -> str: if self.id_cache: diff --git a/exabyte_api_client/endpoints/__init__.py b/src/py/exabyte_api_client/endpoints/__init__.py similarity index 100% rename from exabyte_api_client/endpoints/__init__.py rename to src/py/exabyte_api_client/endpoints/__init__.py diff --git a/exabyte_api_client/endpoints/bank_entity.py b/src/py/exabyte_api_client/endpoints/bank_entity.py similarity index 100% rename from exabyte_api_client/endpoints/bank_entity.py rename to src/py/exabyte_api_client/endpoints/bank_entity.py diff --git a/exabyte_api_client/endpoints/bank_materials.py b/src/py/exabyte_api_client/endpoints/bank_materials.py similarity index 100% rename from exabyte_api_client/endpoints/bank_materials.py rename to src/py/exabyte_api_client/endpoints/bank_materials.py diff --git a/exabyte_api_client/endpoints/bank_workflows.py b/src/py/exabyte_api_client/endpoints/bank_workflows.py similarity index 100% rename from exabyte_api_client/endpoints/bank_workflows.py rename to src/py/exabyte_api_client/endpoints/bank_workflows.py diff --git a/exabyte_api_client/endpoints/charges.py b/src/py/exabyte_api_client/endpoints/charges.py similarity index 100% rename from exabyte_api_client/endpoints/charges.py rename to src/py/exabyte_api_client/endpoints/charges.py diff --git a/exabyte_api_client/endpoints/entity.py b/src/py/exabyte_api_client/endpoints/entity.py similarity index 100% rename from exabyte_api_client/endpoints/entity.py rename to src/py/exabyte_api_client/endpoints/entity.py diff --git a/exabyte_api_client/endpoints/enums.py b/src/py/exabyte_api_client/endpoints/enums.py similarity index 100% rename from exabyte_api_client/endpoints/enums.py rename to src/py/exabyte_api_client/endpoints/enums.py diff --git a/exabyte_api_client/endpoints/jobs.py b/src/py/exabyte_api_client/endpoints/jobs.py similarity index 100% rename from exabyte_api_client/endpoints/jobs.py rename to src/py/exabyte_api_client/endpoints/jobs.py diff --git a/exabyte_api_client/endpoints/login.py b/src/py/exabyte_api_client/endpoints/login.py similarity index 100% rename from exabyte_api_client/endpoints/login.py rename to src/py/exabyte_api_client/endpoints/login.py diff --git a/exabyte_api_client/endpoints/logout.py b/src/py/exabyte_api_client/endpoints/logout.py similarity index 100% rename from exabyte_api_client/endpoints/logout.py rename to src/py/exabyte_api_client/endpoints/logout.py diff --git a/exabyte_api_client/endpoints/materials.py b/src/py/exabyte_api_client/endpoints/materials.py similarity index 100% rename from exabyte_api_client/endpoints/materials.py rename to src/py/exabyte_api_client/endpoints/materials.py diff --git a/exabyte_api_client/endpoints/metaproperties.py b/src/py/exabyte_api_client/endpoints/metaproperties.py similarity index 100% rename from exabyte_api_client/endpoints/metaproperties.py rename to src/py/exabyte_api_client/endpoints/metaproperties.py diff --git a/exabyte_api_client/endpoints/mixins/__init__.py b/src/py/exabyte_api_client/endpoints/mixins/__init__.py similarity index 100% rename from exabyte_api_client/endpoints/mixins/__init__.py rename to src/py/exabyte_api_client/endpoints/mixins/__init__.py diff --git a/exabyte_api_client/endpoints/mixins/default.py b/src/py/exabyte_api_client/endpoints/mixins/default.py similarity index 100% rename from exabyte_api_client/endpoints/mixins/default.py rename to src/py/exabyte_api_client/endpoints/mixins/default.py diff --git a/exabyte_api_client/endpoints/mixins/set.py b/src/py/exabyte_api_client/endpoints/mixins/set.py similarity index 100% rename from exabyte_api_client/endpoints/mixins/set.py rename to src/py/exabyte_api_client/endpoints/mixins/set.py diff --git a/exabyte_api_client/endpoints/projects.py b/src/py/exabyte_api_client/endpoints/projects.py similarity index 100% rename from exabyte_api_client/endpoints/projects.py rename to src/py/exabyte_api_client/endpoints/projects.py diff --git a/exabyte_api_client/endpoints/properties.py b/src/py/exabyte_api_client/endpoints/properties.py similarity index 100% rename from exabyte_api_client/endpoints/properties.py rename to src/py/exabyte_api_client/endpoints/properties.py diff --git a/exabyte_api_client/endpoints/workflows.py b/src/py/exabyte_api_client/endpoints/workflows.py similarity index 100% rename from exabyte_api_client/endpoints/workflows.py rename to src/py/exabyte_api_client/endpoints/workflows.py diff --git a/src/py/exabyte_api_client/helpers.py b/src/py/exabyte_api_client/helpers.py new file mode 100644 index 0000000..f28e1b4 --- /dev/null +++ b/src/py/exabyte_api_client/helpers.py @@ -0,0 +1,215 @@ +""" +Helper functions for simplified API interactions. + +This module provides convenient wrappers around the endpoint classes +with automatic OIDC authentication support. +""" + +import os +from typing import Any, Dict, List, Optional + +import requests + +from .endpoints.jobs import JobEndpoints +from .endpoints.materials import MaterialEndpoints +from .endpoints.projects import ProjectEndpoints +from .endpoints.workflows import WorkflowEndpoints + +# Configuration - can be overridden by environment variables +ACCESS_TOKEN_ENV_VAR = os.getenv("ACCESS_TOKEN_ENV_VAR", "OIDC_ACCESS_TOKEN") +ACCOUNT_ID_ENV_VAR = os.getenv("ACCOUNT_ID_ENV_VAR", "ACCOUNT_ID") + + +class Endpoints: + def __init__(self, material, workflow, job, project): + self.material = material + self.workflow = workflow + self.job = job + self.project = project + + +def _create_endpoints_with_auth( + host: str, + port: int, + account_id: str, + auth_token: str, + version: str = "2018-10-01", + secure: bool = True, +) -> Endpoints: + """ + Create endpoint instances with automatic OIDC authentication if available. + + Args: + host: API host + port: API port + account_id: Account ID (can be placeholder if using OIDC) + auth_token: Legacy auth token (can be placeholder if using OIDC) + version: API version + secure: Whether to use HTTPS + + Returns: + Endpoints instance with all endpoint objects + """ + endpoint_args = [host, port, account_id, auth_token, version, secure] + + material_endpoints = MaterialEndpoints(*endpoint_args) + workflow_endpoints = WorkflowEndpoints(*endpoint_args) + job_endpoints = JobEndpoints(*endpoint_args) + project_endpoints = ProjectEndpoints(*endpoint_args) + + # Check if OIDC token is available and use it instead of legacy auth + access_token = os.environ.get(ACCESS_TOKEN_ENV_VAR) + if access_token: + auth_header = {"Authorization": f"Bearer {access_token}"} + material_endpoints.headers.update(auth_header) + workflow_endpoints.headers.update(auth_header) + job_endpoints.headers.update(auth_header) + project_endpoints.headers.update(auth_header) + + return Endpoints(material_endpoints, workflow_endpoints, job_endpoints, project_endpoints) + + +def get_owner_id( + endpoints: Endpoints, + host: str, + port: int, + secure: bool = True, + fallback_account_id: Optional[str] = None, +) -> str: + """ + Get the owner/account ID, fetching from API if OIDC token is available. + + Args: + endpoints: Endpoints instance + host: API host + port: API port + secure: Whether to use HTTPS + fallback_account_id: Fallback account ID if OIDC not available + + Returns: + Account ID string + """ + access_token = os.environ.get(ACCESS_TOKEN_ENV_VAR) + if access_token: + protocol = "https" if secure else "http" + port_str = f":{port}" if port not in [80, 443] else "" + url = f"{protocol}://{host}{port_str}/api/v1/users/me" + + response = requests.get(url, headers={"Authorization": f"Bearer {access_token}"}, timeout=30) + response.raise_for_status() + account_id = response.json()["data"]["user"]["entity"]["defaultAccountId"] + + # Cache it for future use + os.environ[ACCOUNT_ID_ENV_VAR] = account_id + return account_id + + # Fallback to environment or provided account ID + account_id = os.environ.get(ACCOUNT_ID_ENV_VAR) or fallback_account_id + if not account_id or account_id == "ACCOUNT_ID": + raise ValueError( + f"{ACCOUNT_ID_ENV_VAR} is not set. Please authenticate first or provide account_id" + ) + return account_id + + +def get_material(endpoints: Endpoints, material_id: str) -> Dict[str, Any]: + return endpoints.material.get(material_id) + + +def list_materials( + endpoints: Endpoints, + query: Optional[Dict[str, Any]] = None, + owner_id: Optional[str] = None, +) -> List[Dict[str, Any]]: + query_params = query or {} + if owner_id and "owner._id" not in query_params: + query_params["owner._id"] = owner_id + return endpoints.material.list(query_params) + + +def create_material( + endpoints: Endpoints, + material: Any, + owner_id: str, +) -> Dict[str, Any]: + raw_config = material.to_dict() + fields = ["name", "lattice", "basis"] + config = {key: raw_config[key] for key in fields} + return endpoints.material.create(config, owner_id) + + +def get_workflow(endpoints: Endpoints, workflow_id: str) -> Dict[str, Any]: + return endpoints.workflow.get(workflow_id) + + +def list_workflows( + endpoints: Endpoints, + query: Optional[Dict[str, Any]] = None, + owner_id: Optional[str] = None, +) -> List[Dict[str, Any]]: + query_params = query or {} + if owner_id and "owner._id" not in query_params: + query_params["owner._id"] = owner_id + return endpoints.workflow.list(query_params) + + +def create_workflow( + endpoints: Endpoints, + workflow: Any, + owner_id: str, +) -> Dict[str, Any]: + config = workflow.to_dict() + return endpoints.workflow.create(config, owner_id) + + +def get_job(endpoints: Endpoints, job_id: str) -> Dict[str, Any]: + return endpoints.job.get(job_id) + + +def list_jobs( + endpoints: Endpoints, + query: Optional[Dict[str, Any]] = None, + owner_id: Optional[str] = None, +) -> List[Dict[str, Any]]: + query_params = query or {} + if owner_id and "owner._id" not in query_params: + query_params["owner._id"] = owner_id + return endpoints.job.list(query_params) + + +def create_job( + endpoints: Endpoints, + materials: List[str], + workflow_id: str, + project_id: str, + name: str, + compute: Dict[str, Any], + owner_id: str, +) -> Dict[str, Any]: + return endpoints.job.create_by_ids( + materials=materials, + workflow_id=workflow_id, + project_id=project_id, + owner_id=owner_id, + prefix=name, + compute=compute, + ) + + +def get_compute_config(endpoints: Endpoints, cluster: str = "cluster-001") -> Dict[str, Any]: + return endpoints.job.get_compute(cluster=cluster) + + +def submit_job(endpoints: Endpoints, job_id: str) -> Dict[str, Any]: + return endpoints.job.submit(job_id) + +def get_default_project( + endpoints: Endpoints, + owner_id: str, +) -> str: + projects = endpoints.project.list({"isDefault": True, "owner._id": owner_id}) + + if not projects: + raise Exception(f"No default project found for owner {owner_id}") + + return projects[0]["_id"] diff --git a/exabyte_api_client/utils/__init__.py b/src/py/exabyte_api_client/utils/__init__.py similarity index 100% rename from exabyte_api_client/utils/__init__.py rename to src/py/exabyte_api_client/utils/__init__.py diff --git a/exabyte_api_client/utils/http.py b/src/py/exabyte_api_client/utils/http.py similarity index 100% rename from exabyte_api_client/utils/http.py rename to src/py/exabyte_api_client/utils/http.py diff --git a/exabyte_api_client/utils/materials.py b/src/py/exabyte_api_client/utils/materials.py similarity index 100% rename from exabyte_api_client/utils/materials.py rename to src/py/exabyte_api_client/utils/materials.py From 59a285fcf7a74068475186d9115694a42496d11b Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 16:43:17 -0800 Subject: [PATCH 02/19] update: add pyproj toml --- README.md | 49 +++++++++++++++++--- pyproject.toml | 103 +++++++++++++++++++++++++++++++++++++++++-- requirements-dev.txt | 9 ---- 3 files changed, 141 insertions(+), 20 deletions(-) delete mode 100644 requirements-dev.txt diff --git a/README.md b/README.md index c60cceb..b5e1018 100644 --- a/README.md +++ b/README.md @@ -32,19 +32,54 @@ pip install -e . [exabyte-api-examples](https://github.com/Exabyte-io/exabyte-api-examples) repository contains examples for performing most-common tasks in the Exabyte.io platform through its RESTful API in Jupyter Notebook format. # Testing -A Virtualenv environment can be created and the tests run with the included `run-tests.sh` script. -To run the unit tests in Python 3, you can: + +The package uses pytest for testing. Tests are organized into unit and integration tests. + +## Running Tests + +### Unit Tests (No environment setup required) + +Run all unit tests: +```bash +pytest tests/py/unit ``` -./run-tests -t=unit + +Or using the test script: +```bash +./run-tests.sh -t=unit ``` -To run the integration tests in Python 2, you can: +### Integration Tests (Requires API credentials) + +Integration tests require the following environment variables to be set: + +- `TEST_HOST` - API host (e.g., `platform.mat3ra.com`) +- `TEST_PORT` - API port (e.g., `443`) +- `TEST_ACCOUNT_ID` - Your account ID +- `TEST_AUTH_TOKEN` - Your authentication token +- `TEST_SECURE` - Use HTTPS (optional, default: `False`) +- `TEST_VERSION` - API version (optional, default: `2018-10-01`) + +To run integration tests: +```bash +export TEST_HOST=platform.mat3ra.com +export TEST_PORT=443 +export TEST_ACCOUNT_ID=your-account-id +export TEST_AUTH_TOKEN=your-auth-token +export TEST_SECURE=true + +pytest tests/py/integration +# Or: ./run-tests.sh -t=integration ``` -./run-tests -p=python2 -t=integration + +### Run All Tests + +```bash +pytest tests/py +# Or: ./run-tests.sh -t=all ``` -(assuming you have a `python2` binary in your PATH environment). -Note that the integration tests require a REST API service against which the live tests will run. See `tests/integration/__init__.py` for the environment variable details. +**Note:** Integration tests will be automatically skipped if required environment variables are not set. © 2020 Exabyte Inc. diff --git a/pyproject.toml b/pyproject.toml index d88ca30..93187fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,105 @@ +[project] +name = "exabyte-api-client" +dynamic = ["version"] +description = "Exabyte Python Client for RESTful API" +readme = "README.md" +requires-python = ">=3.10" +license = {file = "LICENSE.md"} +authors = [ + { name = "Exabyte Inc.", email = "info@mat3ra.com" } +] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Topic :: Software Development", + "License :: OSI Approved :: Apache Software License", +] +dependencies = [ + "requests>=2.26.0", + "pydantic>=2,<3", +] + +[project.optional-dependencies] +dev = [ + "pre-commit", + "black", + "ruff", + "isort", + "mypy", + "pip-tools", +] +tests = [ + "coverage[toml]>=5.3", + "pytest", + "pytest-cov", + "mock>=4.0.3", +] +all = [ + "exabyte-api-client[tests]", + "exabyte-api-client[dev]", +] + [build-system] -requires = ["setuptools>=45", "wheel", "setuptools_scm>=6.2"] +requires = [ + "setuptools>=42", + "setuptools-scm[toml]>=3.4" +] build-backend = "setuptools.build_meta" [tool.setuptools_scm] -write_to = "exabyte_api_client/_version.py" +git_describe_command = "git describe --tags --long" +write_to = "src/py/exabyte_api_client/_version.py" + +[tool.setuptools.packages.find] +where = ["src/py"] + +[tool.black] +line-length = 120 +target-version = ['py310'] +extend-exclude = ''' +( + tests\/fixtures* +) +''' + +[tool.ruff] +extend-exclude = [ + "tests/fixtures" +] +line-length = 120 +target-version = "py310" + +[tool.ruff.per-file-ignores] +"__init__.py" = ["F401"] + +[tool.isort] +profile = "black" +multi_line_output = 3 +include_trailing_comma = true + +[tool.pytest.ini_options] +pythonpath = [ + "src/py", +] +testpaths = [ + "tests/py" +] [tool.coverage.run] -source = ['.'] -omit = ['env*/*', 'venv*/*', 'tests/*'] +source = ["src/py"] +omit = ["env*/*", "venv*/*", "tests/*", "src/py/exabyte_api_client/_version.py"] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", + "if __name__ == .__main__.:", +] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 408212c..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,9 +0,0 @@ -certifi==2020.12.5 -chardet==3.0.4 -coverage==5.5 -idna==2.7 -mock==4.0.3 -requests>=2.26.0 -pydantic>=2,<3 -toml==0.10.2 -urllib3==1.26.5 From 70994c01a0b2e0f0235b5c91f0d7e916f4345d16 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 16:43:33 -0800 Subject: [PATCH 03/19] update: pytest tests --- tests/__init__.py | 23 ------ tests/conftest.py | 27 ++++++ tests/integration/__init__.py | 20 ----- tests/integration/entity.py | 80 ------------------ tests/{ => py}/data/login.json | 0 tests/{ => py}/data/logout.json | 0 tests/{ => py}/data/material.json | 0 tests/py/integration/__init__.py | 45 ++++++++++ tests/py/integration/entity.py | 86 ++++++++++++++++++++ tests/{ => py}/integration/test_jobs.py | 47 +++++++---- tests/{ => py}/integration/test_materials.py | 9 +- tests/py/unit/__init__.py | 32 ++++++++ tests/py/unit/entity.py | 44 ++++++++++ tests/{ => py}/unit/test_bank_materials.py | 7 +- tests/{ => py}/unit/test_bank_workflows.py | 7 +- tests/{ => py}/unit/test_client.py | 2 +- tests/py/unit/test_httpBase.py | 42 ++++++++++ tests/{ => py}/unit/test_jobs.py | 6 +- tests/{ => py}/unit/test_login.py | 23 ++++-- tests/{ => py}/unit/test_logout.py | 12 ++- tests/{ => py}/unit/test_materials.py | 6 +- tests/{ => py}/unit/test_properties.py | 7 +- tests/{ => py}/unit/test_workflows.py | 6 +- tests/unit/__init__.py | 24 ------ tests/unit/entity.py | 37 --------- tests/unit/test_httpBase.py | 32 -------- 26 files changed, 363 insertions(+), 261 deletions(-) create mode 100644 tests/conftest.py delete mode 100644 tests/integration/__init__.py delete mode 100644 tests/integration/entity.py rename tests/{ => py}/data/login.json (100%) rename tests/{ => py}/data/logout.json (100%) rename tests/{ => py}/data/material.json (100%) create mode 100644 tests/py/integration/__init__.py create mode 100644 tests/py/integration/entity.py rename tests/{ => py}/integration/test_jobs.py (59%) rename tests/{ => py}/integration/test_materials.py (82%) create mode 100644 tests/py/unit/__init__.py create mode 100644 tests/py/unit/entity.py rename tests/{ => py}/unit/test_bank_materials.py (83%) rename tests/{ => py}/unit/test_bank_workflows.py (83%) rename tests/{ => py}/unit/test_client.py (98%) create mode 100644 tests/py/unit/test_httpBase.py rename tests/{ => py}/unit/test_jobs.py (87%) rename tests/{ => py}/unit/test_login.py (62%) rename tests/{ => py}/unit/test_logout.py (73%) rename tests/{ => py}/unit/test_materials.py (87%) rename tests/{ => py}/unit/test_properties.py (84%) rename tests/{ => py}/unit/test_workflows.py (87%) delete mode 100644 tests/unit/__init__.py delete mode 100644 tests/unit/entity.py delete mode 100644 tests/unit/test_httpBase.py diff --git a/tests/__init__.py b/tests/__init__.py index c756939..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,23 +0,0 @@ -import json -import os -import unittest - - -class EndpointBaseTest(unittest.TestCase): - """ - Base class for testing endpoints. - """ - - def __init__(self, *args, **kwargs): - super(EndpointBaseTest, self).__init__(*args, **kwargs) - - def get_file_path(self, filename): - return os.path.join(os.path.dirname(__file__), "data", filename) - - def get_content(self, filename): - with open(self.get_file_path(filename)) as f: - return f.read() - - def get_content_in_json(self, filename): - with open(self.get_file_path(filename)) as f: - return json.loads(f.read()) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b81fe30 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,27 @@ +""" +Shared test fixtures and base classes for pytest. +""" +import json +import os +import unittest + + +class EndpointBaseTest(unittest.TestCase): + """ + Base class for testing endpoints. + """ + + def __init__(self, *args, **kwargs): + super(EndpointBaseTest, self).__init__(*args, **kwargs) + + def get_file_path(self, filename): + return os.path.join(os.path.dirname(__file__), "py", "data", filename) + + def get_content(self, filename): + with open(self.get_file_path(filename)) as f: + return f.read() + + def get_content_in_json(self, filename): + with open(self.get_file_path(filename)) as f: + return json.loads(f.read()) + diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index 9a99cfa..0000000 --- a/tests/integration/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -import os - -from tests import EndpointBaseTest - - -class BaseIntegrationTest(EndpointBaseTest): - """ - Base class for endpoints integration tests. - """ - - def __init__(self, *args, **kwargs): - super(BaseIntegrationTest, self).__init__(*args, **kwargs) - self.endpoint_kwargs = { - "host": os.environ["TEST_HOST"], - "port": os.environ["TEST_PORT"], - "account_id": os.environ["TEST_ACCOUNT_ID"], - "auth_token": os.environ["TEST_AUTH_TOKEN"], - "secure": os.environ.get("TEST_SECURE", "False").lower() == "true", - "version": os.environ.get("TEST_VERSION", "2018-10-01"), - } diff --git a/tests/integration/entity.py b/tests/integration/entity.py deleted file mode 100644 index 5d8381a..0000000 --- a/tests/integration/entity.py +++ /dev/null @@ -1,80 +0,0 @@ -import time - -from requests.exceptions import HTTPError - -from tests.integration import BaseIntegrationTest - - -class EntityIntegrationTest(BaseIntegrationTest): - """ - Base entity integration tests class. - """ - - def __init__(self, *args, **kwargs): - super(EntityIntegrationTest, self).__init__(*args, **kwargs) - self.endpoints = None - self.entity_id: str = "" - - def entities_selector(self): - """ - Returns a selector to access entities created in tests. - Override upon inheritance as necessary. - """ - return {"tags": "INTEGRATION-TEST"} - - def tearDown(self): - """Delete only the current test entity if it still exists after test. - - Warn if the filtering fails, failsafe attempt to delete the entity anyways. - """ - tagged_test_entity_id_list = [e["_id"] for e in self.endpoints.list(query=self.entities_selector())] - try: - if self.entity_id not in tagged_test_entity_id_list: - print( - f"WARNING: Entity with ID {self.entity_id} not found in the list of tagged entities:" - f" {tagged_test_entity_id_list}" - ) - self.endpoints.delete(self.entity_id) - - except HTTPError as e: - print(f"WARNING: Failed to delete entity with ID {self.entity_id}: {e}") - - def get_default_config(self): - """ - Returns the default entity config. - Override upon inheritance. - """ - raise NotImplementedError - - def create_entity(self, kwargs=None): - entity = self.get_default_config() - entity.update(kwargs or {}) - entity.setdefault("tags", []).append("INTEGRATION-TEST") - created_entity = self.endpoints.create(entity) - self.entity_id = created_entity["_id"] - return created_entity - - def list_entities_test(self): - entity = self.create_entity() - self.assertIn(entity["_id"], [e["_id"] for e in self.endpoints.list(query=self.entities_selector())]) - - def get_entity_by_id_test(self): - entity = self.create_entity() - self.assertEqual(self.endpoints.get(entity["_id"])["_id"], entity["_id"]) - - def create_entity_test(self): - name = "test-{}".format(time.time()) - entity = self.create_entity({"name": name}) - self.assertEqual(entity["name"], name) - self.assertIsNotNone(entity["_id"]) - self.assertIn("INTEGRATION-TEST", entity["tags"]) - - def delete_entity_test(self): - entity = self.create_entity() - self.endpoints.delete(entity["_id"]) - self.assertNotIn(entity["_id"], [e["_id"] for e in self.endpoints.list(query=self.entities_selector())]) - - def update_entity_test(self): - entity = self.create_entity() - updated_entity = self.endpoints.update(entity["_id"], {"name": "UPDATED"}) - self.assertEqual(updated_entity["name"], "UPDATED") diff --git a/tests/data/login.json b/tests/py/data/login.json similarity index 100% rename from tests/data/login.json rename to tests/py/data/login.json diff --git a/tests/data/logout.json b/tests/py/data/logout.json similarity index 100% rename from tests/data/logout.json rename to tests/py/data/logout.json diff --git a/tests/data/material.json b/tests/py/data/material.json similarity index 100% rename from tests/data/material.json rename to tests/py/data/material.json diff --git a/tests/py/integration/__init__.py b/tests/py/integration/__init__.py new file mode 100644 index 0000000..e4c6352 --- /dev/null +++ b/tests/py/integration/__init__.py @@ -0,0 +1,45 @@ +import os + +import pytest + +from tests.conftest import EndpointBaseTest + +ENV_TEST_HOST = "TEST_HOST" +ENV_TEST_PORT = "TEST_PORT" +ENV_TEST_ACCOUNT_ID = "TEST_ACCOUNT_ID" +ENV_TEST_AUTH_TOKEN = "TEST_AUTH_TOKEN" +ENV_TEST_SECURE = "TEST_SECURE" +ENV_TEST_VERSION = "TEST_VERSION" +DEFAULT_TEST_SECURE = "False" +DEFAULT_TEST_VERSION = "2018-10-01" + +REQUIRED_ENV_VARS = [ENV_TEST_HOST, ENV_TEST_PORT, ENV_TEST_ACCOUNT_ID, ENV_TEST_AUTH_TOKEN] +MISSING_ENV_VARS_MESSAGE = ( + "Integration tests require environment variables: TEST_HOST, TEST_PORT, " + "TEST_ACCOUNT_ID, TEST_AUTH_TOKEN. Set them to run integration tests." +) + + +def _check_integration_env(): + """Check if required integration test environment variables are set.""" + missing = [var for var in REQUIRED_ENV_VARS if var not in os.environ] + if missing: + pytest.skip(f"{MISSING_ENV_VARS_MESSAGE} Missing: {', '.join(missing)}") + + +class BaseIntegrationTest(EndpointBaseTest): + """ + Base class for endpoints integration tests. + """ + + def __init__(self, *args, **kwargs): + super(BaseIntegrationTest, self).__init__(*args, **kwargs) + _check_integration_env() + self.endpoint_kwargs = { + "host": os.environ[ENV_TEST_HOST], + "port": os.environ[ENV_TEST_PORT], + "account_id": os.environ[ENV_TEST_ACCOUNT_ID], + "auth_token": os.environ[ENV_TEST_AUTH_TOKEN], + "secure": os.environ.get(ENV_TEST_SECURE, DEFAULT_TEST_SECURE).lower() == "true", + "version": os.environ.get(ENV_TEST_VERSION, DEFAULT_TEST_VERSION), + } diff --git a/tests/py/integration/entity.py b/tests/py/integration/entity.py new file mode 100644 index 0000000..f67aa3a --- /dev/null +++ b/tests/py/integration/entity.py @@ -0,0 +1,86 @@ +import time + +from requests.exceptions import HTTPError + +from tests.py.integration import BaseIntegrationTest + +INTEGRATION_TEST_TAG = "INTEGRATION-TEST" +ENTITY_ID_KEY = "_id" +ENTITY_NAME_KEY = "name" +ENTITY_TAGS_KEY = "tags" +TEST_NAME_PREFIX = "test-" +UPDATED_NAME = "UPDATED" +WARNING_ENTITY_NOT_FOUND = "WARNING: Entity with ID {} not found in the list of tagged entities: {}" +WARNING_DELETE_FAILED = "WARNING: Failed to delete entity with ID {}: {}" + + +class EntityIntegrationTest(BaseIntegrationTest): + """ + Base entity integration tests class. + """ + + def __init__(self, *args, **kwargs): + super(EntityIntegrationTest, self).__init__(*args, **kwargs) + self.endpoints = None + self.entity_id: str = "" + + def entities_selector(self): + """ + Returns a selector to access entities created in tests. + Override upon inheritance as necessary. + """ + return {ENTITY_TAGS_KEY: INTEGRATION_TEST_TAG} + + def tearDown(self): + """Delete only the current test entity if it still exists after test. + + Warn if the filtering fails, failsafe attempt to delete the entity anyways. + """ + tagged_test_entity_id_list = [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())] + try: + if self.entity_id not in tagged_test_entity_id_list: + print(WARNING_ENTITY_NOT_FOUND.format(self.entity_id, tagged_test_entity_id_list)) + self.endpoints.delete(self.entity_id) + + except HTTPError as e: + print(WARNING_DELETE_FAILED.format(self.entity_id, e)) + + def get_default_config(self): + """ + Returns the default entity config. + Override upon inheritance. + """ + raise NotImplementedError + + def create_entity(self, kwargs=None): + entity = self.get_default_config() + entity.update(kwargs or {}) + entity.setdefault(ENTITY_TAGS_KEY, []).append(INTEGRATION_TEST_TAG) + created_entity = self.endpoints.create(entity) + self.entity_id = created_entity[ENTITY_ID_KEY] + return created_entity + + def list_entities_test(self): + entity = self.create_entity() + self.assertIn(entity[ENTITY_ID_KEY], [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())]) + + def get_entity_by_id_test(self): + entity = self.create_entity() + self.assertEqual(self.endpoints.get(entity[ENTITY_ID_KEY])[ENTITY_ID_KEY], entity[ENTITY_ID_KEY]) + + def create_entity_test(self): + name = f"{TEST_NAME_PREFIX}{time.time()}" + entity = self.create_entity({ENTITY_NAME_KEY: name}) + self.assertEqual(entity[ENTITY_NAME_KEY], name) + self.assertIsNotNone(entity[ENTITY_ID_KEY]) + self.assertIn(INTEGRATION_TEST_TAG, entity[ENTITY_TAGS_KEY]) + + def delete_entity_test(self): + entity = self.create_entity() + self.endpoints.delete(entity[ENTITY_ID_KEY]) + self.assertNotIn(entity[ENTITY_ID_KEY], [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())]) + + def update_entity_test(self): + entity = self.create_entity() + updated_entity = self.endpoints.update(entity[ENTITY_ID_KEY], {ENTITY_NAME_KEY: UPDATED_NAME}) + self.assertEqual(updated_entity[ENTITY_NAME_KEY], UPDATED_NAME) diff --git a/tests/integration/test_jobs.py b/tests/py/integration/test_jobs.py similarity index 59% rename from tests/integration/test_jobs.py rename to tests/py/integration/test_jobs.py index e3136a6..eb1c598 100644 --- a/tests/integration/test_jobs.py +++ b/tests/py/integration/test_jobs.py @@ -2,7 +2,23 @@ import time from exabyte_api_client.endpoints.jobs import JobEndpoints -from tests.integration.entity import EntityIntegrationTest +from tests.py.integration.entity import EntityIntegrationTest + +KNOWN_COMPLETED_JOB_ID = "9gyhfncWDhnSyzALv" +JOB_NAME_PREFIX = "API-CLIENT TEST JOB" +DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" +DEFAULT_NODES = 1 +DEFAULT_NOTIFY = "n" +DEFAULT_PPN = 1 +DEFAULT_QUEUE = "D" +DEFAULT_TIME_LIMIT = "00:05:00" +TEST_TIME_LIMIT = "00:10:00" +TEST_NOTIFY = "abe" +JOB_STATUS_SUBMITTED = "submitted" +JOB_STATUS_FINISHED = "finished" +JOB_WAIT_TIMEOUT = 900 +JOB_WAIT_SLEEP_INTERVAL = 5 +JOB_TIMEOUT_ERROR = "job with ID {} did not finish within {} seconds" class JobEndpointsIntegrationTest(EntityIntegrationTest): @@ -10,8 +26,6 @@ class JobEndpointsIntegrationTest(EntityIntegrationTest): Job endpoints integration tests. """ - KNOWN_COMPLETED_JOB_ID = "9gyhfncWDhnSyzALv" - def __init__(self, *args, **kwargs): super(JobEndpointsIntegrationTest, self).__init__(*args, **kwargs) self.endpoints = JobEndpoints(**self.endpoint_kwargs) @@ -21,10 +35,10 @@ def get_default_config(self): Returns the default entity config. Override upon inheritance. """ - now_time = datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S") - return {"name": "API-CLIENT TEST JOB {}".format(now_time)} + now_time = datetime.datetime.today().strftime(DATETIME_FORMAT) + return {"name": f"{JOB_NAME_PREFIX} {now_time}"} - def get_compute_params(self, nodes=1, notify="n", ppn=1, queue="D", time_limit="00:05:00"): + def get_compute_params(self, nodes=DEFAULT_NODES, notify=DEFAULT_NOTIFY, ppn=DEFAULT_PPN, queue=DEFAULT_QUEUE, time_limit=DEFAULT_TIME_LIMIT): return {"nodes": nodes, "notify": notify, "ppn": ppn, "queue": queue, "timeLimit": time_limit} def test_list_jobs(self): @@ -42,34 +56,33 @@ def test_delete_job(self): def test_update_job(self): self.update_entity_test() - def _wait_for_job_to_finish(self, id_, timeout=900): + def _wait_for_job_to_finish(self, id_, timeout=JOB_WAIT_TIMEOUT): end = time.time() + timeout while time.time() < end: job = self.endpoints.get(id_) - if job["status"] == "finished": + if job["status"] == JOB_STATUS_FINISHED: return - time.sleep(5) - raise BaseException("job with ID {} did not finish within {} seconds".format(id_, timeout)) + time.sleep(JOB_WAIT_SLEEP_INTERVAL) + raise BaseException(JOB_TIMEOUT_ERROR.format(id_, timeout)) def test_submit_job_and_wait_to_finish(self): job = self.create_entity() self.endpoints.submit(job["_id"]) - self.assertEqual("submitted", self.endpoints.get(job["_id"])["status"]) + self.assertEqual(JOB_STATUS_SUBMITTED, self.endpoints.get(job["_id"])["status"]) self._wait_for_job_to_finish(job["_id"]) def test_create_job_timeLimit(self): - time_limit = "00:10:00" - job = self.create_entity({"compute": self.get_compute_params(time_limit=time_limit)}) + job = self.create_entity({"compute": self.get_compute_params(time_limit=TEST_TIME_LIMIT)}) self.assertEqual(self.endpoints.get(job["_id"])["_id"], job["_id"]) - self.assertEqual(self.endpoints.get(job["_id"])["compute"]["timeLimit"], time_limit) + self.assertEqual(self.endpoints.get(job["_id"])["compute"]["timeLimit"], TEST_TIME_LIMIT) def test_create_job_notify(self): - job = self.create_entity({"compute": self.get_compute_params(notify="abe")}) + job = self.create_entity({"compute": self.get_compute_params(notify=TEST_NOTIFY)}) self.assertEqual(self.endpoints.get(job["_id"])["_id"], job["_id"]) - self.assertEqual(self.endpoints.get(job["_id"])["compute"]["notify"], "abe") + self.assertEqual(self.endpoints.get(job["_id"])["compute"]["notify"], TEST_NOTIFY) def test_list_files(self): - http_response_data = self.endpoints.list_files(self.KNOWN_COMPLETED_JOB_ID) + http_response_data = self.endpoints.list_files(KNOWN_COMPLETED_JOB_ID) self.assertIsInstance(http_response_data, list) self.assertGreater(len(http_response_data), 0) self.assertIsInstance(http_response_data[0], dict) diff --git a/tests/integration/test_materials.py b/tests/py/integration/test_materials.py similarity index 82% rename from tests/integration/test_materials.py rename to tests/py/integration/test_materials.py index 2ba28aa..de397ae 100644 --- a/tests/integration/test_materials.py +++ b/tests/py/integration/test_materials.py @@ -1,5 +1,8 @@ from exabyte_api_client.endpoints.materials import MaterialEndpoints -from tests.integration.entity import EntityIntegrationTest +from tests.py.integration.entity import EntityIntegrationTest + +MATERIAL_DATA_FILE = "material.json" +TEST_FORMULA = "Si" class MaterialEndpointsIntegrationTest(EntityIntegrationTest): @@ -12,7 +15,7 @@ def __init__(self, *args, **kwargs): self.endpoints = MaterialEndpoints(**self.endpoint_kwargs) def get_default_config(self): - return self.get_content_in_json("material.json") + return self.get_content_in_json(MATERIAL_DATA_FILE) def test_list_materials(self): self.list_entities_test() @@ -31,5 +34,5 @@ def test_update_material(self): def test_get_material_by_formula(self): material = self.create_entity() - materials = self.endpoints.list(query={"_id": material["_id"], "formula": "Si"}) + materials = self.endpoints.list(query={"_id": material["_id"], "formula": TEST_FORMULA}) self.assertIn(material["_id"], [m["_id"] for m in materials]) diff --git a/tests/py/unit/__init__.py b/tests/py/unit/__init__.py new file mode 100644 index 0000000..9f6f7f9 --- /dev/null +++ b/tests/py/unit/__init__.py @@ -0,0 +1,32 @@ +from requests import Response + +from tests.conftest import EndpointBaseTest + +DEFAULT_TEST_PORT = 4000 +DEFAULT_TEST_VERSION = "2018-10-01" +DEFAULT_TEST_HOST = "platform.mat3ra.com" +DEFAULT_TEST_ACCOUNT_ID = "ubxMkAyx37Rjn8qK9" +DEFAULT_TEST_AUTH_TOKEN = "XihOnUA8EqytSui1icz6fYhsJ2tUsJGGTlV03upYPSF" +HTTP_STATUS_OK = 200 +HTTP_REASON_OK = "OK" + + +class EndpointBaseUnitTest(EndpointBaseTest): + """ + Base class for endpoints unit tests. + """ + + def __init__(self, *args, **kwargs): + super(EndpointBaseUnitTest, self).__init__(*args, **kwargs) + self.port = DEFAULT_TEST_PORT + self.version = DEFAULT_TEST_VERSION + self.host = DEFAULT_TEST_HOST + self.account_id = DEFAULT_TEST_ACCOUNT_ID + self.auth_token = DEFAULT_TEST_AUTH_TOKEN + + def mock_response(self, content, status_code=HTTP_STATUS_OK, reason=HTTP_REASON_OK): + response = Response() + response._content = content.encode() + response.status_code = status_code + response.reason = reason + return response diff --git a/tests/py/unit/entity.py b/tests/py/unit/entity.py new file mode 100644 index 0000000..56f8390 --- /dev/null +++ b/tests/py/unit/entity.py @@ -0,0 +1,44 @@ +from tests.py.unit import EndpointBaseUnitTest + +TEST_ENTITY_ID = "28FMvD5knJZZx452H" +MOCK_SUCCESS_RESPONSE_LIST = '{"status": "success", "data": []}' +MOCK_SUCCESS_RESPONSE_OBJECT = '{"status": "success", "data": {}}' +HTTP_METHOD_GET = "get" +HTTP_METHOD_DELETE = "delete" +CONTENT_TYPE_JSON = "application/json" + + +class EntityEndpointsUnitTest(EndpointBaseUnitTest): + """ + Base class for testing entity endpoints. + """ + + def __init__(self, *args, **kwargs): + super(EntityEndpointsUnitTest, self).__init__(*args, **kwargs) + self.endpoints = None + self.endpoint_name = None + + @property + def base_url(self): + return f"https://{self.host}:{self.port}/api/{self.version}/{self.endpoint_name}" + + def list(self, mock_request): + mock_request.return_value = self.mock_response(MOCK_SUCCESS_RESPONSE_LIST) + self.assertEqual(self.endpoints.list(), []) + self.assertEqual(mock_request.call_args[1]["method"], HTTP_METHOD_GET) + + def get(self, mock_request): + mock_request.return_value = self.mock_response(MOCK_SUCCESS_RESPONSE_OBJECT) + self.assertEqual(self.endpoints.get(TEST_ENTITY_ID), {}) + expected_url = f"{self.base_url}/{TEST_ENTITY_ID}" + self.assertEqual(mock_request.call_args[1]["url"], expected_url) + + def create(self, mock_request): + mock_request.return_value = self.mock_response(MOCK_SUCCESS_RESPONSE_OBJECT) + self.endpoints.create({}) + self.assertEqual(mock_request.call_args[1]["headers"]["Content-Type"], CONTENT_TYPE_JSON) + + def delete(self, mock_request): + mock_request.return_value = self.mock_response(MOCK_SUCCESS_RESPONSE_OBJECT) + self.assertEqual(self.endpoints.delete(TEST_ENTITY_ID), {}) + self.assertEqual(mock_request.call_args[1]["method"], HTTP_METHOD_DELETE) diff --git a/tests/unit/test_bank_materials.py b/tests/py/unit/test_bank_materials.py similarity index 83% rename from tests/unit/test_bank_materials.py rename to tests/py/unit/test_bank_materials.py index 4372ede..90080ac 100644 --- a/tests/unit/test_bank_materials.py +++ b/tests/py/unit/test_bank_materials.py @@ -1,6 +1,9 @@ from unittest import mock + from exabyte_api_client.endpoints.bank_materials import BankMaterialEndpoints -from tests.unit.entity import EntityEndpointsUnitTest +from tests.py.unit.entity import EntityEndpointsUnitTest + +ENDPOINT_NAME = "bank-materials" class EndpointMaterialsBankUnitTest(EntityEndpointsUnitTest): @@ -10,7 +13,7 @@ class EndpointMaterialsBankUnitTest(EntityEndpointsUnitTest): def __init__(self, *args, **kwargs): super(EndpointMaterialsBankUnitTest, self).__init__(*args, **kwargs) - self.endpoint_name = "bank-materials" + self.endpoint_name = ENDPOINT_NAME self.endpoints = BankMaterialEndpoints(self.host, self.port, self.account_id, self.auth_token) @mock.patch("requests.sessions.Session.request") diff --git a/tests/unit/test_bank_workflows.py b/tests/py/unit/test_bank_workflows.py similarity index 83% rename from tests/unit/test_bank_workflows.py rename to tests/py/unit/test_bank_workflows.py index 0c2cab6..423acfe 100644 --- a/tests/unit/test_bank_workflows.py +++ b/tests/py/unit/test_bank_workflows.py @@ -1,6 +1,9 @@ from unittest import mock + from exabyte_api_client.endpoints.bank_workflows import BankWorkflowEndpoints -from tests.unit.entity import EntityEndpointsUnitTest +from tests.py.unit.entity import EntityEndpointsUnitTest + +ENDPOINT_NAME = "bank-workflows" class EndpointWorkflowsBankUnitTest(EntityEndpointsUnitTest): @@ -10,7 +13,7 @@ class EndpointWorkflowsBankUnitTest(EntityEndpointsUnitTest): def __init__(self, *args, **kwargs): super(EndpointWorkflowsBankUnitTest, self).__init__(*args, **kwargs) - self.endpoint_name = "bank-workflows" + self.endpoint_name = ENDPOINT_NAME self.endpoints = BankWorkflowEndpoints(self.host, self.port, self.account_id, self.auth_token) @mock.patch("requests.sessions.Session.request") diff --git a/tests/unit/test_client.py b/tests/py/unit/test_client.py similarity index 98% rename from tests/unit/test_client.py rename to tests/py/unit/test_client.py index 911624c..665f5b4 100644 --- a/tests/unit/test_client.py +++ b/tests/py/unit/test_client.py @@ -4,7 +4,7 @@ from pydantic import ValidationError from exabyte_api_client import APIClient -from tests.unit import EndpointBaseUnitTest +from tests.py.unit import EndpointBaseUnitTest API_HOST = "platform.mat3ra.com" API_PORT = "4000" diff --git a/tests/py/unit/test_httpBase.py b/tests/py/unit/test_httpBase.py new file mode 100644 index 0000000..3d0219e --- /dev/null +++ b/tests/py/unit/test_httpBase.py @@ -0,0 +1,42 @@ +from unittest import mock + +from exabyte_api_client.utils.http import Connection +from requests.exceptions import HTTPError +from tests.py.unit import EndpointBaseUnitTest + +API_VERSION_1 = "2018-10-1" +API_VERSION_2 = "2018-10-2" +HTTP_STATUS_UNAUTHORIZED = 401 +HTTP_REASON_UNAUTHORIZED = "Unauthorized" +EMPTY_CONTENT = "" +TEST_ENTITY_ID = "28FMvD5knJZZx452H" +EMPTY_USERNAME = "" +EMPTY_PASSWORD = "" + + +class HTTPBaseUnitTest(EndpointBaseUnitTest): + """ + Class for testing functionality implemented inside HTTPBase module. + """ + + def __init__(self, *args, **kwargs): + super(HTTPBaseUnitTest, self).__init__(*args, **kwargs) + + def test_preamble_secure(self): + conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True) + self.assertEqual(conn.preamble, f"https://{self.host}:{self.port}/api/{API_VERSION_1}/") + + def test_preamble_unsecure(self): + conn = Connection(self.host, self.port, version=API_VERSION_1, secure=False) + self.assertEqual(conn.preamble, f"http://{self.host}:{self.port}/api/{API_VERSION_1}/") + + def test_preamble_version(self): + conn = Connection(self.host, self.port, version=API_VERSION_2, secure=True) + self.assertEqual(conn.preamble, f"https://{self.host}:{self.port}/api/{API_VERSION_2}/") + + @mock.patch("requests.sessions.Session.request") + def test_raise_http_error(self, mock_request): + mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_UNAUTHORIZED, reason=HTTP_REASON_UNAUTHORIZED) + with self.assertRaises(HTTPError): + conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True) + conn.request("POST", "login", data={"username": EMPTY_USERNAME, "password": EMPTY_PASSWORD}) diff --git a/tests/unit/test_jobs.py b/tests/py/unit/test_jobs.py similarity index 87% rename from tests/unit/test_jobs.py rename to tests/py/unit/test_jobs.py index a656861..ede7d87 100644 --- a/tests/unit/test_jobs.py +++ b/tests/py/unit/test_jobs.py @@ -1,7 +1,9 @@ from unittest import mock from exabyte_api_client.endpoints.jobs import JobEndpoints -from tests.unit.entity import EntityEndpointsUnitTest +from tests.py.unit.entity import EntityEndpointsUnitTest + +ENDPOINT_NAME = "jobs" class EndpointJobsUnitTest(EntityEndpointsUnitTest): @@ -11,7 +13,7 @@ class EndpointJobsUnitTest(EntityEndpointsUnitTest): def __init__(self, *args, **kwargs): super(EndpointJobsUnitTest, self).__init__(*args, **kwargs) - self.endpoint_name = "jobs" + self.endpoint_name = ENDPOINT_NAME self.endpoints = JobEndpoints(self.host, self.port, self.account_id, self.auth_token) @mock.patch("requests.sessions.Session.request") diff --git a/tests/unit/test_login.py b/tests/py/unit/test_login.py similarity index 62% rename from tests/unit/test_login.py rename to tests/py/unit/test_login.py index a27444f..f67cad9 100644 --- a/tests/unit/test_login.py +++ b/tests/py/unit/test_login.py @@ -1,7 +1,16 @@ from unittest import mock from exabyte_api_client.endpoints.login import LoginEndpoint -from tests.unit import EndpointBaseUnitTest +from tests.py.unit import EndpointBaseUnitTest + +TEST_USERNAME = "test" +TEST_PASSWORD = "test" +LOGIN_RESPONSE_FILE = "login.json" + +EXPECTED_LOGIN_RESULT = { + "X-Account-Id": "ubxMkAyx37Rjn8qK9", + "X-Auth-Token": "XihOnUA8EqytSui1icz6fYhsJ2tUsJGGTlV03upYPSF", +} class EndpointLoginUnitTest(EndpointBaseUnitTest): @@ -11,17 +20,13 @@ class EndpointLoginUnitTest(EndpointBaseUnitTest): def __init__(self, *args, **kwargs): super(EndpointLoginUnitTest, self).__init__(*args, **kwargs) - self.username = "test" - self.password = "test" + self.username = TEST_USERNAME + self.password = TEST_PASSWORD self.login_endpoint = LoginEndpoint(self.host, self.port, self.username, self.password) @mock.patch("requests.sessions.Session.request") def test_login(self, mock_request): - mock_request.return_value = self.mock_response(self.get_content("login.json")) - expected_result = { - "X-Account-Id": "ubxMkAyx37Rjn8qK9", - "X-Auth-Token": "XihOnUA8EqytSui1icz6fYhsJ2tUsJGGTlV03upYPSF", - } - self.assertEqual(self.login_endpoint.login(), expected_result) + mock_request.return_value = self.mock_response(self.get_content(LOGIN_RESPONSE_FILE)) + self.assertEqual(self.login_endpoint.login(), EXPECTED_LOGIN_RESULT) self.assertEqual(mock_request.call_args[1]["data"]["username"], self.username) self.assertEqual(mock_request.call_args[1]["data"]["password"], self.password) diff --git a/tests/unit/test_logout.py b/tests/py/unit/test_logout.py similarity index 73% rename from tests/unit/test_logout.py rename to tests/py/unit/test_logout.py index 7ee1e82..d4399d5 100644 --- a/tests/unit/test_logout.py +++ b/tests/py/unit/test_logout.py @@ -1,7 +1,13 @@ from unittest import mock from exabyte_api_client.endpoints.logout import LogoutEndpoint -from tests.unit import EndpointBaseUnitTest +from tests.py.unit import EndpointBaseUnitTest + +LOGOUT_RESPONSE_FILE = "logout.json" + +EXPECTED_LOGOUT_RESULT = { + "message": "You are successfully logged out" +} class EndpointLogoutUnitTest(EndpointBaseUnitTest): @@ -15,7 +21,7 @@ def __init__(self, *args, **kwargs): @mock.patch("requests.sessions.Session.request") def test_logout(self, mock_request): - mock_request.return_value = self.mock_response(self.get_content("logout.json")) - self.assertEqual(self.logout_endpoint.logout(), {"message": "You are successfully logged out"}) + mock_request.return_value = self.mock_response(self.get_content(LOGOUT_RESPONSE_FILE)) + self.assertEqual(self.logout_endpoint.logout(), EXPECTED_LOGOUT_RESULT) self.assertEqual(mock_request.call_args[1]["headers"]["X-Account-Id"], self.account_id) self.assertEqual(mock_request.call_args[1]["headers"]["X-Auth-Token"], self.auth_token) diff --git a/tests/unit/test_materials.py b/tests/py/unit/test_materials.py similarity index 87% rename from tests/unit/test_materials.py rename to tests/py/unit/test_materials.py index 073c583..9382f1e 100644 --- a/tests/unit/test_materials.py +++ b/tests/py/unit/test_materials.py @@ -1,7 +1,9 @@ from unittest import mock from exabyte_api_client.endpoints.materials import MaterialEndpoints -from tests.unit.entity import EntityEndpointsUnitTest +from tests.py.unit.entity import EntityEndpointsUnitTest + +ENDPOINT_NAME = "materials" class EndpointMaterialsUnitTest(EntityEndpointsUnitTest): @@ -11,7 +13,7 @@ class EndpointMaterialsUnitTest(EntityEndpointsUnitTest): def __init__(self, *args, **kwargs): super(EndpointMaterialsUnitTest, self).__init__(*args, **kwargs) - self.endpoint_name = "materials" + self.endpoint_name = ENDPOINT_NAME self.endpoints = MaterialEndpoints(self.host, self.port, self.account_id, self.auth_token) @mock.patch("requests.sessions.Session.request") diff --git a/tests/unit/test_properties.py b/tests/py/unit/test_properties.py similarity index 84% rename from tests/unit/test_properties.py rename to tests/py/unit/test_properties.py index f9c5302..506a6b8 100644 --- a/tests/unit/test_properties.py +++ b/tests/py/unit/test_properties.py @@ -1,6 +1,9 @@ from unittest import mock + from exabyte_api_client.endpoints.properties import PropertiesEndpoints -from tests.unit.entity import EntityEndpointsUnitTest +from tests.py.unit.entity import EntityEndpointsUnitTest + +ENDPOINT_NAME = "properties" class EndpointCharacteristicUnitTest(EntityEndpointsUnitTest): @@ -10,7 +13,7 @@ class EndpointCharacteristicUnitTest(EntityEndpointsUnitTest): def __init__(self, *args, **kwargs): super(EndpointCharacteristicUnitTest, self).__init__(*args, **kwargs) - self.endpoint_name = "properties" + self.endpoint_name = ENDPOINT_NAME self.endpoints = PropertiesEndpoints(self.host, self.port, self.account_id, self.auth_token) @mock.patch("requests.sessions.Session.request") diff --git a/tests/unit/test_workflows.py b/tests/py/unit/test_workflows.py similarity index 87% rename from tests/unit/test_workflows.py rename to tests/py/unit/test_workflows.py index 43cade9..2b3d7f0 100644 --- a/tests/unit/test_workflows.py +++ b/tests/py/unit/test_workflows.py @@ -1,7 +1,9 @@ from unittest import mock from exabyte_api_client.endpoints.workflows import WorkflowEndpoints -from tests.unit.entity import EntityEndpointsUnitTest +from tests.py.unit.entity import EntityEndpointsUnitTest + +ENDPOINT_NAME = "workflows" class EndpointWorkflowsUnitTest(EntityEndpointsUnitTest): @@ -11,7 +13,7 @@ class EndpointWorkflowsUnitTest(EntityEndpointsUnitTest): def __init__(self, *args, **kwargs): super(EndpointWorkflowsUnitTest, self).__init__(*args, **kwargs) - self.endpoint_name = "workflows" + self.endpoint_name = ENDPOINT_NAME self.endpoints = WorkflowEndpoints(self.host, self.port, self.account_id, self.auth_token) @mock.patch("requests.sessions.Session.request") diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index ae8737b..0000000 --- a/tests/unit/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -from requests import Response - -from tests import EndpointBaseTest - - -class EndpointBaseUnitTest(EndpointBaseTest): - """ - Base class for endpoints unit tests. - """ - - def __init__(self, *args, **kwargs): - super(EndpointBaseUnitTest, self).__init__(*args, **kwargs) - self.port = 4000 - self.version = "2018-10-01" - self.host = "platform.mat3ra.com" - self.account_id = "ubxMkAyx37Rjn8qK9" - self.auth_token = "XihOnUA8EqytSui1icz6fYhsJ2tUsJGGTlV03upYPSF" - - def mock_response(self, content, status_code=200, reason="OK"): - response = Response() - response._content = content.encode() - response.status_code = status_code - response.reason = reason - return response diff --git a/tests/unit/entity.py b/tests/unit/entity.py deleted file mode 100644 index 69faeda..0000000 --- a/tests/unit/entity.py +++ /dev/null @@ -1,37 +0,0 @@ -from tests.unit import EndpointBaseUnitTest - - -class EntityEndpointsUnitTest(EndpointBaseUnitTest): - """ - Base class for testing entity endpoints. - """ - - def __init__(self, *args, **kwargs): - super(EntityEndpointsUnitTest, self).__init__(*args, **kwargs) - self.endpoints = None - self.endpoint_name = None - - @property - def base_url(self): - return "https://{}:{}/api/{}/{}".format(self.host, self.port, self.version, self.endpoint_name) - - def list(self, mock_request): - mock_request.return_value = self.mock_response('{"status": "success", "data": []}') - self.assertEqual(self.endpoints.list(), []) - self.assertEqual(mock_request.call_args[1]["method"], "get") - - def get(self, mock_request): - mock_request.return_value = self.mock_response('{"status": "success", "data": {}}') - self.assertEqual(self.endpoints.get("28FMvD5knJZZx452H"), {}) - expected_url = "{}/28FMvD5knJZZx452H".format(self.base_url) - self.assertEqual(mock_request.call_args[1]["url"], expected_url) - - def create(self, mock_request): - mock_request.return_value = self.mock_response('{"status": "success", "data": {}}') - self.endpoints.create({}) - self.assertEqual(mock_request.call_args[1]["headers"]["Content-Type"], "application/json") - - def delete(self, mock_request): - mock_request.return_value = self.mock_response('{"status": "success", "data": {}}') - self.assertEqual(self.endpoints.delete("28FMvD5knJZZx452H"), {}) - self.assertEqual(mock_request.call_args[1]["method"], "delete") diff --git a/tests/unit/test_httpBase.py b/tests/unit/test_httpBase.py deleted file mode 100644 index 4a9cbcb..0000000 --- a/tests/unit/test_httpBase.py +++ /dev/null @@ -1,32 +0,0 @@ -from unittest import mock -from exabyte_api_client.utils.http import Connection -from requests.exceptions import HTTPError -from tests.unit import EndpointBaseUnitTest - - -class HTTPBaseUnitTest(EndpointBaseUnitTest): - """ - Class for testing functionality implemented inside HTTPBase module. - """ - - def __init__(self, *args, **kwargs): - super(HTTPBaseUnitTest, self).__init__(*args, **kwargs) - - def test_preamble_secure(self): - conn = Connection(self.host, self.port, version="2018-10-1", secure=True) - self.assertEqual(conn.preamble, "https://{}:{}/api/2018-10-1/".format(self.host, self.port)) - - def test_preamble_unsecure(self): - conn = Connection(self.host, self.port, version="2018-10-1", secure=False) - self.assertEqual(conn.preamble, "http://{}:{}/api/2018-10-1/".format(self.host, self.port)) - - def test_preamble_version(self): - conn = Connection(self.host, self.port, version="2018-10-2", secure=True) - self.assertEqual(conn.preamble, "https://{}:{}/api/2018-10-2/".format(self.host, self.port)) - - @mock.patch("requests.sessions.Session.request") - def test_raise_http_error(self, mock_request): - mock_request.return_value = self.mock_response("", 401, reason="Unauthorized") - with self.assertRaises(HTTPError): - conn = Connection(self.host, self.port, version="2018-10-1", secure=True) - conn.request("POST", "login", data={"username": "", "password": ""}) From 59e5808efa9e3b2b84ad584f1d20d9a6c473f3f3 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 16:44:01 -0800 Subject: [PATCH 04/19] chore: lint fix --- tests/py/integration/entity.py | 6 ++++-- tests/py/integration/test_jobs.py | 3 ++- tests/py/unit/test_httpBase.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/py/integration/entity.py b/tests/py/integration/entity.py index f67aa3a..cd3ff73 100644 --- a/tests/py/integration/entity.py +++ b/tests/py/integration/entity.py @@ -62,7 +62,8 @@ def create_entity(self, kwargs=None): def list_entities_test(self): entity = self.create_entity() - self.assertIn(entity[ENTITY_ID_KEY], [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())]) + self.assertIn(entity[ENTITY_ID_KEY], + [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())]) def get_entity_by_id_test(self): entity = self.create_entity() @@ -78,7 +79,8 @@ def create_entity_test(self): def delete_entity_test(self): entity = self.create_entity() self.endpoints.delete(entity[ENTITY_ID_KEY]) - self.assertNotIn(entity[ENTITY_ID_KEY], [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())]) + self.assertNotIn(entity[ENTITY_ID_KEY], + [e[ENTITY_ID_KEY] for e in self.endpoints.list(query=self.entities_selector())]) def update_entity_test(self): entity = self.create_entity() diff --git a/tests/py/integration/test_jobs.py b/tests/py/integration/test_jobs.py index eb1c598..46c3454 100644 --- a/tests/py/integration/test_jobs.py +++ b/tests/py/integration/test_jobs.py @@ -38,7 +38,8 @@ def get_default_config(self): now_time = datetime.datetime.today().strftime(DATETIME_FORMAT) return {"name": f"{JOB_NAME_PREFIX} {now_time}"} - def get_compute_params(self, nodes=DEFAULT_NODES, notify=DEFAULT_NOTIFY, ppn=DEFAULT_PPN, queue=DEFAULT_QUEUE, time_limit=DEFAULT_TIME_LIMIT): + def get_compute_params(self, nodes=DEFAULT_NODES, notify=DEFAULT_NOTIFY, ppn=DEFAULT_PPN, queue=DEFAULT_QUEUE, + time_limit=DEFAULT_TIME_LIMIT): return {"nodes": nodes, "notify": notify, "ppn": ppn, "queue": queue, "timeLimit": time_limit} def test_list_jobs(self): diff --git a/tests/py/unit/test_httpBase.py b/tests/py/unit/test_httpBase.py index 3d0219e..d852a92 100644 --- a/tests/py/unit/test_httpBase.py +++ b/tests/py/unit/test_httpBase.py @@ -36,7 +36,8 @@ def test_preamble_version(self): @mock.patch("requests.sessions.Session.request") def test_raise_http_error(self, mock_request): - mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_UNAUTHORIZED, reason=HTTP_REASON_UNAUTHORIZED) + mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_UNAUTHORIZED, + reason=HTTP_REASON_UNAUTHORIZED) with self.assertRaises(HTTPError): conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True) conn.request("POST", "login", data={"username": EMPTY_USERNAME, "password": EMPTY_PASSWORD}) From 1225046a1ac49636378e20a5210b6ef4e8c274e3 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 17:10:21 -0800 Subject: [PATCH 05/19] update: cicd --- .github/workflows/cicd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 23529d8..a774e91 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -55,12 +55,12 @@ jobs: uses: ./actions/py/test with: python-version: ${{ matrix.python-version }} - unit-test-directory: tests/unit + unit-test-directory: tests/py/unit # TMP disabled to allow publishing to PyPi - # - name: Run integration tests] + # - name: Run integration tests # uses: ./actions/py/test # with: - # integration-test-directory: tests/integration + # integration-test-directory: tests/py/integration # env: # TEST_HOST: platform.mat3ra.com # TEST_PORT: 443 From fb30d1bdf73bb82cb86ab0e8b9e2ee3e6f94be1b Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 17:14:10 -0800 Subject: [PATCH 06/19] chore: cleanup --- .gitignore | 2 +- README.md | 13 ++++++------- run-tests.sh | 49 ------------------------------------------------- setup.cfg | 34 ---------------------------------- 4 files changed, 7 insertions(+), 91 deletions(-) delete mode 100755 run-tests.sh delete mode 100644 setup.cfg diff --git a/.gitignore b/.gitignore index 9c487cd..cca5e6a 100644 --- a/.gitignore +++ b/.gitignore @@ -95,7 +95,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/README.md b/README.md index b5e1018..031e392 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,6 @@ Run all unit tests: pytest tests/py/unit ``` -Or using the test script: -```bash -./run-tests.sh -t=unit -``` - ### Integration Tests (Requires API credentials) Integration tests require the following environment variables to be set: @@ -69,14 +64,18 @@ export TEST_AUTH_TOKEN=your-auth-token export TEST_SECURE=true pytest tests/py/integration -# Or: ./run-tests.sh -t=integration ``` ### Run All Tests ```bash pytest tests/py -# Or: ./run-tests.sh -t=all +``` + +### Run Tests with Coverage + +```bash +pytest tests/py/unit --cov=exabyte_api_client --cov-report=term --cov-report=html ``` **Note:** Integration tests will be automatically skipped if required environment variables are not set. diff --git a/run-tests.sh b/run-tests.sh deleted file mode 100755 index 0b600bb..0000000 --- a/run-tests.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -e - -TEST_TYPE="unit" -PYTHON_BIN="python3" -THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)" -VENV_NAME="venv" - -usage() { - echo "run-tests.sh -p=PYTHON_BIN -v=VENV_NAME -t=TEST_TYPE" - exit 1 -} - -check_args() { - for i in "$@"; do - case $i in - -t=* | --test-type=*) - TEST_TYPE="${i#*=}" - ;; - -p=* | --python-bin=*) - PYTHON_BIN="${i#*=}" - ;; - -v=* | --venvdir=*) - VENV_NAME="${i#*=}" - ;; - *) - usage - ;; - esac - done -} - -check_args $@ - -# Prepare the execution virtualenv -virtualenv --python ${PYTHON_BIN} ${THIS_DIR}/${VENV_NAME} -source ${THIS_DIR}/${VENV_NAME}/bin/activate -trap "deactivate" EXIT -if [ -f ${THIS_DIR}/requirements-dev.txt ]; then - pip install -r ${THIS_DIR}/requirements-dev.txt --no-deps -fi - -# Execute the specified test suite -coverage run -m unittest discover --verbose --catch --start-directory ${THIS_DIR}/tests/${TEST_TYPE} - -# Generate the code coverage reports -coverage report -coverage html --directory htmlcov_${TEST_TYPE} -coverage xml -o coverage_${TEST_TYPE}.xml diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a37197b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,34 +0,0 @@ -[metadata] -name = exabyte-api-client -author = Exabyte Inc. -author_email = info@mat3ra.com -description = Exabyte Python Client for RESTful API -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/Exabyte-io/api-client -classifiers = - Programming Language :: Python - Programming Language :: Python :: 3 - Development Status :: 3 - Alpha - Intended Audience :: Developers - Topic :: Software Development - License :: OSI Approved :: Apache Software License - -[options] -package_dir = - = . -packages = find: -python_requires = >= 3.6 -install_requires = - requests>=2.26.0 - -[options.extras_require] -test = - coverage[toml]>=5.3 - mock>=1.3.0 - -[options.packages.find] -where = . -exclude = - tests - tests.* From c52e39832d562fe2002e2b5c0d1375af768ea856 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 19:14:01 -0800 Subject: [PATCH 07/19] update: rename exabyte -> mat3ra --- README.md | 14 +++++++------- pyproject.toml | 12 ++++++------ src/py/exabyte_api_client/__init__.py | 18 ------------------ src/py/mat3ra_api_client/__init__.py | 18 ++++++++++++++++++ .../client.py | 16 ++++++++-------- .../endpoints/__init__.py | 8 ++++---- .../endpoints/bank_entity.py | 6 +++--- .../endpoints/bank_materials.py | 6 +++--- .../endpoints/bank_workflows.py | 6 +++--- .../endpoints/charges.py | 6 +++--- .../endpoints/entity.py | 6 +++--- .../endpoints/enums.py | 0 .../endpoints/jobs.py | 6 +++--- .../endpoints/login.py | 12 ++++++------ .../endpoints/logout.py | 6 +++--- .../endpoints/materials.py | 6 +++--- .../endpoints/metaproperties.py | 6 +++--- .../endpoints/mixins/__init__.py | 0 .../endpoints/mixins/default.py | 0 .../endpoints/mixins/set.py | 0 .../endpoints/projects.py | 6 +++--- .../endpoints/properties.py | 6 +++--- .../endpoints/workflows.py | 6 +++--- .../helpers.py | 0 .../utils/__init__.py | 0 .../utils/http.py | 6 +++--- .../utils/materials.py | 0 tests/py/integration/test_jobs.py | 2 +- tests/py/integration/test_materials.py | 2 +- tests/py/unit/test_bank_materials.py | 2 +- tests/py/unit/test_bank_workflows.py | 2 +- tests/py/unit/test_client.py | 2 +- tests/py/unit/test_httpBase.py | 2 +- tests/py/unit/test_jobs.py | 2 +- tests/py/unit/test_login.py | 2 +- tests/py/unit/test_logout.py | 2 +- tests/py/unit/test_materials.py | 2 +- tests/py/unit/test_properties.py | 2 +- tests/py/unit/test_workflows.py | 2 +- 39 files changed, 100 insertions(+), 100 deletions(-) delete mode 100644 src/py/exabyte_api_client/__init__.py create mode 100644 src/py/mat3ra_api_client/__init__.py rename src/py/{exabyte_api_client => mat3ra_api_client}/client.py (93%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/__init__.py (89%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/bank_entity.py (91%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/bank_materials.py (85%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/bank_workflows.py (85%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/charges.py (90%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/entity.py (95%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/enums.py (100%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/jobs.py (97%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/login.py (87%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/logout.py (88%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/materials.py (95%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/metaproperties.py (89%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/mixins/__init__.py (100%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/mixins/default.py (100%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/mixins/set.py (100%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/projects.py (89%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/properties.py (93%) rename src/py/{exabyte_api_client => mat3ra_api_client}/endpoints/workflows.py (88%) rename src/py/{exabyte_api_client => mat3ra_api_client}/helpers.py (100%) rename src/py/{exabyte_api_client => mat3ra_api_client}/utils/__init__.py (100%) rename src/py/{exabyte_api_client => mat3ra_api_client}/utils/http.py (96%) rename src/py/{exabyte_api_client => mat3ra_api_client}/utils/materials.py (100%) diff --git a/README.md b/README.md index 031e392..5157ed6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![PyPI version](https://badge.fury.io/py/exabyte-api-client.svg)](https://badge.fury.io/py/exabyte-api-client) +[![PyPI version](https://badge.fury.io/py/mat3ra-api-client.svg)](https://badge.fury.io/py/mat3ra-api-client) -This package provides access to Exabyte.io [RESTful API](https://docs.mat3ra.com/rest-api/overview/). +This package provides access to Mat3ra.com [RESTful API](https://docs.mat3ra.com/rest-api/overview/). # Installation @@ -16,20 +16,20 @@ Install using pip: - from PyPI: ```bash -pip install exabyte-api-client +pip install mat3ra-api-client ``` - from source code in development mode: ```bash -git clone git@github.com:Exabyte-io/exabyte-api-client.git -cd exabyte-api-client +git clone git@github.com:Exabyte-io/api-client.git +cd api-client pip install -e . ``` # Examples -[exabyte-api-examples](https://github.com/Exabyte-io/exabyte-api-examples) repository contains examples for performing most-common tasks in the Exabyte.io platform through its RESTful API in Jupyter Notebook format. +[api-examples](https://github.com/Exabyte-io/api-examples) repository contains examples for performing most-common tasks in the Mat3ra.com platform through its RESTful API in Jupyter Notebook format. # Testing @@ -75,7 +75,7 @@ pytest tests/py ### Run Tests with Coverage ```bash -pytest tests/py/unit --cov=exabyte_api_client --cov-report=term --cov-report=html +pytest tests/py/unit --cov=mat3ra_api_client --cov-report=term --cov-report=html ``` **Note:** Integration tests will be automatically skipped if required environment variables are not set. diff --git a/pyproject.toml b/pyproject.toml index 93187fa..97be618 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "exabyte-api-client" +name = "mat3ra-api-client" dynamic = ["version"] -description = "Exabyte Python Client for RESTful API" +description = "Mat3ra.com Python Client for RESTful API" readme = "README.md" requires-python = ">=3.10" license = {file = "LICENSE.md"} @@ -41,8 +41,8 @@ tests = [ "mock>=4.0.3", ] all = [ - "exabyte-api-client[tests]", - "exabyte-api-client[dev]", + "mat3ra-api-client[tests]", + "mat3ra-api-client[dev]", ] [build-system] @@ -54,7 +54,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] git_describe_command = "git describe --tags --long" -write_to = "src/py/exabyte_api_client/_version.py" +write_to = "src/py/mat3ra_api_client/_version.py" [tool.setuptools.packages.find] where = ["src/py"] @@ -93,7 +93,7 @@ testpaths = [ [tool.coverage.run] source = ["src/py"] -omit = ["env*/*", "venv*/*", "tests/*", "src/py/exabyte_api_client/_version.py"] +omit = ["env*/*", "venv*/*", "tests/*", "src/py/mat3ra_api_client/_version.py"] [tool.coverage.report] exclude_lines = [ diff --git a/src/py/exabyte_api_client/__init__.py b/src/py/exabyte_api_client/__init__.py deleted file mode 100644 index cd1c62f..0000000 --- a/src/py/exabyte_api_client/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# ruff: noqa: F401 -try: - from ._version import version as __version__ -except ModuleNotFoundError: - __version__ = None - -from exabyte_api_client.endpoints.bank_materials import BankMaterialEndpoints -from exabyte_api_client.endpoints.bank_workflows import BankWorkflowEndpoints -from exabyte_api_client.endpoints.jobs import JobEndpoints -from exabyte_api_client.endpoints.login import LoginEndpoint -from exabyte_api_client.endpoints.logout import LogoutEndpoint -from exabyte_api_client.endpoints.materials import MaterialEndpoints -from exabyte_api_client.endpoints.metaproperties import MetaPropertiesEndpoints -from exabyte_api_client.endpoints.projects import ProjectEndpoints -from exabyte_api_client.endpoints.properties import PropertiesEndpoints -from exabyte_api_client.endpoints.workflows import WorkflowEndpoints - -from exabyte_api_client.client import APIClient diff --git a/src/py/mat3ra_api_client/__init__.py b/src/py/mat3ra_api_client/__init__.py new file mode 100644 index 0000000..d2a4de4 --- /dev/null +++ b/src/py/mat3ra_api_client/__init__.py @@ -0,0 +1,18 @@ +# ruff: noqa: F401 +try: + from ._version import version as __version__ +except ModuleNotFoundError: + __version__ = None + +from mat3ra_api_client.endpoints.bank_materials import BankMaterialEndpoints +from mat3ra_api_client.endpoints.bank_workflows import BankWorkflowEndpoints +from mat3ra_api_client.endpoints.jobs import JobEndpoints +from mat3ra_api_client.endpoints.login import LoginEndpoint +from mat3ra_api_client.endpoints.logout import LogoutEndpoint +from mat3ra_api_client.endpoints.materials import MaterialEndpoints +from mat3ra_api_client.endpoints.metaproperties import MetaPropertiesEndpoints +from mat3ra_api_client.endpoints.projects import ProjectEndpoints +from mat3ra_api_client.endpoints.properties import PropertiesEndpoints +from mat3ra_api_client.endpoints.workflows import WorkflowEndpoints + +from mat3ra_api_client.client import APIClient diff --git a/src/py/exabyte_api_client/client.py b/src/py/mat3ra_api_client/client.py similarity index 93% rename from src/py/exabyte_api_client/client.py rename to src/py/mat3ra_api_client/client.py index 39ff9de..f3020ff 100644 --- a/src/py/exabyte_api_client/client.py +++ b/src/py/mat3ra_api_client/client.py @@ -4,14 +4,14 @@ import requests from pydantic import BaseModel, ConfigDict, Field -from exabyte_api_client.endpoints.bank_materials import BankMaterialEndpoints -from exabyte_api_client.endpoints.bank_workflows import BankWorkflowEndpoints -from exabyte_api_client.endpoints.jobs import JobEndpoints -from exabyte_api_client.endpoints.materials import MaterialEndpoints -from exabyte_api_client.endpoints.metaproperties import MetaPropertiesEndpoints -from exabyte_api_client.endpoints.projects import ProjectEndpoints -from exabyte_api_client.endpoints.properties import PropertiesEndpoints -from exabyte_api_client.endpoints.workflows import WorkflowEndpoints +from mat3ra_api_client.endpoints.bank_materials import BankMaterialEndpoints +from mat3ra_api_client.endpoints.bank_workflows import BankWorkflowEndpoints +from mat3ra_api_client.endpoints.jobs import JobEndpoints +from mat3ra_api_client.endpoints.materials import MaterialEndpoints +from mat3ra_api_client.endpoints.metaproperties import MetaPropertiesEndpoints +from mat3ra_api_client.endpoints.projects import ProjectEndpoints +from mat3ra_api_client.endpoints.properties import PropertiesEndpoints +from mat3ra_api_client.endpoints.workflows import WorkflowEndpoints # Default OIDC Configuration OIDC_BASE_URL = "http://localhost:3000/oidc" diff --git a/src/py/exabyte_api_client/endpoints/__init__.py b/src/py/mat3ra_api_client/endpoints/__init__.py similarity index 89% rename from src/py/exabyte_api_client/endpoints/__init__.py rename to src/py/mat3ra_api_client/endpoints/__init__.py index e3b0161..3b0021a 100644 --- a/src/py/exabyte_api_client/endpoints/__init__.py +++ b/src/py/mat3ra_api_client/endpoints/__init__.py @@ -8,13 +8,13 @@ class BaseEndpoint(object): Base class for Exabyte RESTful API endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. - version (str): Exabyte API version. Defaults to 2018-10-1. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. + version (str): Mat3ra API version. Defaults to 2018-10-1. secure (bool): whether to use secure http protocol (https vs http). Defaults to True. Attributes: - conn (httplib.ExabyteConnection): ExabyteConnection instance. + conn (httplib.Mat3raConnection): Mat3raConnection instance. """ def __init__(self, host, port, version="2018-10-1", secure=True, **kwargs): diff --git a/src/py/exabyte_api_client/endpoints/bank_entity.py b/src/py/mat3ra_api_client/endpoints/bank_entity.py similarity index 91% rename from src/py/exabyte_api_client/endpoints/bank_entity.py rename to src/py/mat3ra_api_client/endpoints/bank_entity.py index 79b7d3a..fce9109 100644 --- a/src/py/exabyte_api_client/endpoints/bank_entity.py +++ b/src/py/mat3ra_api_client/endpoints/bank_entity.py @@ -7,11 +7,11 @@ class BankEntityEndpoints(EntityEndpoint): Bank Entity endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/bank_materials.py b/src/py/mat3ra_api_client/endpoints/bank_materials.py similarity index 85% rename from src/py/exabyte_api_client/endpoints/bank_materials.py rename to src/py/mat3ra_api_client/endpoints/bank_materials.py index e77a3e7..c3c6b11 100644 --- a/src/py/exabyte_api_client/endpoints/bank_materials.py +++ b/src/py/mat3ra_api_client/endpoints/bank_materials.py @@ -7,11 +7,11 @@ class BankMaterialEndpoints(BankEntityEndpoints): Bank material endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/bank_workflows.py b/src/py/mat3ra_api_client/endpoints/bank_workflows.py similarity index 85% rename from src/py/exabyte_api_client/endpoints/bank_workflows.py rename to src/py/mat3ra_api_client/endpoints/bank_workflows.py index d999257..96d34b8 100644 --- a/src/py/exabyte_api_client/endpoints/bank_workflows.py +++ b/src/py/mat3ra_api_client/endpoints/bank_workflows.py @@ -7,11 +7,11 @@ class BankWorkflowEndpoints(BankEntityEndpoints): Bank workflow endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/charges.py b/src/py/mat3ra_api_client/endpoints/charges.py similarity index 90% rename from src/py/exabyte_api_client/endpoints/charges.py rename to src/py/mat3ra_api_client/endpoints/charges.py index 3042417..bd58192 100644 --- a/src/py/exabyte_api_client/endpoints/charges.py +++ b/src/py/mat3ra_api_client/endpoints/charges.py @@ -7,11 +7,11 @@ class ChargeEndpoints(EntityEndpoint): Charge endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/entity.py b/src/py/mat3ra_api_client/endpoints/entity.py similarity index 95% rename from src/py/exabyte_api_client/endpoints/entity.py rename to src/py/mat3ra_api_client/endpoints/entity.py index 05d8c07..4c7c4ff 100644 --- a/src/py/exabyte_api_client/endpoints/entity.py +++ b/src/py/mat3ra_api_client/endpoints/entity.py @@ -9,11 +9,11 @@ class EntityEndpoint(BaseEndpoint): Exabyte Entity endpoint. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/enums.py b/src/py/mat3ra_api_client/endpoints/enums.py similarity index 100% rename from src/py/exabyte_api_client/endpoints/enums.py rename to src/py/mat3ra_api_client/endpoints/enums.py diff --git a/src/py/exabyte_api_client/endpoints/jobs.py b/src/py/mat3ra_api_client/endpoints/jobs.py similarity index 97% rename from src/py/exabyte_api_client/endpoints/jobs.py rename to src/py/mat3ra_api_client/endpoints/jobs.py index 1112d00..b2c37c1 100644 --- a/src/py/exabyte_api_client/endpoints/jobs.py +++ b/src/py/mat3ra_api_client/endpoints/jobs.py @@ -10,11 +10,11 @@ class JobEndpoints(EntitySetEndpointsMixin, EntityEndpoint): Job endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/login.py b/src/py/mat3ra_api_client/endpoints/login.py similarity index 87% rename from src/py/exabyte_api_client/endpoints/login.py rename to src/py/mat3ra_api_client/endpoints/login.py index 1e5050c..6813cbf 100644 --- a/src/py/exabyte_api_client/endpoints/login.py +++ b/src/py/mat3ra_api_client/endpoints/login.py @@ -7,11 +7,11 @@ class LoginEndpoint(BaseEndpoint): Login endpoint. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. username (str): username. password (str): password. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. @@ -44,11 +44,11 @@ def get_endpoint_options(host, port, username, password, version=DEFAULT_API_VER Logs in with given parameters and returns options to use for further calls to the RESTful API. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. username (str): username. password (str): password. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). Returns: diff --git a/src/py/exabyte_api_client/endpoints/logout.py b/src/py/mat3ra_api_client/endpoints/logout.py similarity index 88% rename from src/py/exabyte_api_client/endpoints/logout.py rename to src/py/mat3ra_api_client/endpoints/logout.py index 8cc615d..114fb00 100644 --- a/src/py/exabyte_api_client/endpoints/logout.py +++ b/src/py/mat3ra_api_client/endpoints/logout.py @@ -7,11 +7,11 @@ class LogoutEndpoint(BaseEndpoint): Logout endpoint. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/materials.py b/src/py/mat3ra_api_client/endpoints/materials.py similarity index 95% rename from src/py/exabyte_api_client/endpoints/materials.py rename to src/py/mat3ra_api_client/endpoints/materials.py index dd22546..f5b6326 100644 --- a/src/py/exabyte_api_client/endpoints/materials.py +++ b/src/py/mat3ra_api_client/endpoints/materials.py @@ -12,11 +12,11 @@ class MaterialEndpoints(EntitySetEndpointsMixin, DefaultableEntityEndpointsMixin Material endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/metaproperties.py b/src/py/mat3ra_api_client/endpoints/metaproperties.py similarity index 89% rename from src/py/exabyte_api_client/endpoints/metaproperties.py rename to src/py/mat3ra_api_client/endpoints/metaproperties.py index 5dee0d7..73affd7 100644 --- a/src/py/exabyte_api_client/endpoints/metaproperties.py +++ b/src/py/mat3ra_api_client/endpoints/metaproperties.py @@ -7,11 +7,11 @@ class MetaPropertiesEndpoints(BasePropertiesEndpoints): MetaProperties endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/mixins/__init__.py b/src/py/mat3ra_api_client/endpoints/mixins/__init__.py similarity index 100% rename from src/py/exabyte_api_client/endpoints/mixins/__init__.py rename to src/py/mat3ra_api_client/endpoints/mixins/__init__.py diff --git a/src/py/exabyte_api_client/endpoints/mixins/default.py b/src/py/mat3ra_api_client/endpoints/mixins/default.py similarity index 100% rename from src/py/exabyte_api_client/endpoints/mixins/default.py rename to src/py/mat3ra_api_client/endpoints/mixins/default.py diff --git a/src/py/exabyte_api_client/endpoints/mixins/set.py b/src/py/mat3ra_api_client/endpoints/mixins/set.py similarity index 100% rename from src/py/exabyte_api_client/endpoints/mixins/set.py rename to src/py/mat3ra_api_client/endpoints/mixins/set.py diff --git a/src/py/exabyte_api_client/endpoints/projects.py b/src/py/mat3ra_api_client/endpoints/projects.py similarity index 89% rename from src/py/exabyte_api_client/endpoints/projects.py rename to src/py/mat3ra_api_client/endpoints/projects.py index d11f94b..b7cbd6c 100644 --- a/src/py/exabyte_api_client/endpoints/projects.py +++ b/src/py/mat3ra_api_client/endpoints/projects.py @@ -8,11 +8,11 @@ class ProjectEndpoints(DefaultableEntityEndpointsMixin, EntityEndpoint): Project endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/properties.py b/src/py/mat3ra_api_client/endpoints/properties.py similarity index 93% rename from src/py/exabyte_api_client/endpoints/properties.py rename to src/py/mat3ra_api_client/endpoints/properties.py index adab202..3d3f2b1 100644 --- a/src/py/exabyte_api_client/endpoints/properties.py +++ b/src/py/mat3ra_api_client/endpoints/properties.py @@ -26,11 +26,11 @@ class PropertiesEndpoints(BasePropertiesEndpoints): Properties endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/endpoints/workflows.py b/src/py/mat3ra_api_client/endpoints/workflows.py similarity index 88% rename from src/py/exabyte_api_client/endpoints/workflows.py rename to src/py/mat3ra_api_client/endpoints/workflows.py index 13f460a..45c54a3 100644 --- a/src/py/exabyte_api_client/endpoints/workflows.py +++ b/src/py/mat3ra_api_client/endpoints/workflows.py @@ -8,11 +8,11 @@ class WorkflowEndpoints(DefaultableEntityEndpointsMixin, EntityEndpoint): Workflow endpoints. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Exabyte API version. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/helpers.py b/src/py/mat3ra_api_client/helpers.py similarity index 100% rename from src/py/exabyte_api_client/helpers.py rename to src/py/mat3ra_api_client/helpers.py diff --git a/src/py/exabyte_api_client/utils/__init__.py b/src/py/mat3ra_api_client/utils/__init__.py similarity index 100% rename from src/py/exabyte_api_client/utils/__init__.py rename to src/py/mat3ra_api_client/utils/__init__.py diff --git a/src/py/exabyte_api_client/utils/http.py b/src/py/mat3ra_api_client/utils/http.py similarity index 96% rename from src/py/exabyte_api_client/utils/http.py rename to src/py/mat3ra_api_client/utils/http.py index bfc8096..b42d315 100644 --- a/src/py/exabyte_api_client/utils/http.py +++ b/src/py/mat3ra_api_client/utils/http.py @@ -117,9 +117,9 @@ class Connection(BaseConnection): Exabyte connection class. Args: - host (str): Exabyte API hostname. - port (int): Exabyte API port number. - version (str): Exabyte API version. + host (str): Mat3ra API hostname. + port (int): Mat3ra API port number. + version (str): Mat3ra API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/exabyte_api_client/utils/materials.py b/src/py/mat3ra_api_client/utils/materials.py similarity index 100% rename from src/py/exabyte_api_client/utils/materials.py rename to src/py/mat3ra_api_client/utils/materials.py diff --git a/tests/py/integration/test_jobs.py b/tests/py/integration/test_jobs.py index 46c3454..c4fdd46 100644 --- a/tests/py/integration/test_jobs.py +++ b/tests/py/integration/test_jobs.py @@ -1,7 +1,7 @@ import datetime import time -from exabyte_api_client.endpoints.jobs import JobEndpoints +from mat3ra_api_client.endpoints.jobs import JobEndpoints from tests.py.integration.entity import EntityIntegrationTest KNOWN_COMPLETED_JOB_ID = "9gyhfncWDhnSyzALv" diff --git a/tests/py/integration/test_materials.py b/tests/py/integration/test_materials.py index de397ae..fc808c7 100644 --- a/tests/py/integration/test_materials.py +++ b/tests/py/integration/test_materials.py @@ -1,4 +1,4 @@ -from exabyte_api_client.endpoints.materials import MaterialEndpoints +from mat3ra_api_client.endpoints.materials import MaterialEndpoints from tests.py.integration.entity import EntityIntegrationTest MATERIAL_DATA_FILE = "material.json" diff --git a/tests/py/unit/test_bank_materials.py b/tests/py/unit/test_bank_materials.py index 90080ac..959b1e4 100644 --- a/tests/py/unit/test_bank_materials.py +++ b/tests/py/unit/test_bank_materials.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.bank_materials import BankMaterialEndpoints +from mat3ra_api_client.endpoints.bank_materials import BankMaterialEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "bank-materials" diff --git a/tests/py/unit/test_bank_workflows.py b/tests/py/unit/test_bank_workflows.py index 423acfe..930a3fd 100644 --- a/tests/py/unit/test_bank_workflows.py +++ b/tests/py/unit/test_bank_workflows.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.bank_workflows import BankWorkflowEndpoints +from mat3ra_api_client.endpoints.bank_workflows import BankWorkflowEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "bank-workflows" diff --git a/tests/py/unit/test_client.py b/tests/py/unit/test_client.py index 665f5b4..f209d1e 100644 --- a/tests/py/unit/test_client.py +++ b/tests/py/unit/test_client.py @@ -3,7 +3,7 @@ from pydantic import ValidationError -from exabyte_api_client import APIClient +from mat3ra_api_client import APIClient from tests.py.unit import EndpointBaseUnitTest API_HOST = "platform.mat3ra.com" diff --git a/tests/py/unit/test_httpBase.py b/tests/py/unit/test_httpBase.py index d852a92..af2c0ec 100644 --- a/tests/py/unit/test_httpBase.py +++ b/tests/py/unit/test_httpBase.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.utils.http import Connection +from mat3ra_api_client.utils.http import Connection from requests.exceptions import HTTPError from tests.py.unit import EndpointBaseUnitTest diff --git a/tests/py/unit/test_jobs.py b/tests/py/unit/test_jobs.py index ede7d87..caad47f 100644 --- a/tests/py/unit/test_jobs.py +++ b/tests/py/unit/test_jobs.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.jobs import JobEndpoints +from mat3ra_api_client.endpoints.jobs import JobEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "jobs" diff --git a/tests/py/unit/test_login.py b/tests/py/unit/test_login.py index f67cad9..4c923a1 100644 --- a/tests/py/unit/test_login.py +++ b/tests/py/unit/test_login.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.login import LoginEndpoint +from mat3ra_api_client.endpoints.login import LoginEndpoint from tests.py.unit import EndpointBaseUnitTest TEST_USERNAME = "test" diff --git a/tests/py/unit/test_logout.py b/tests/py/unit/test_logout.py index d4399d5..389c858 100644 --- a/tests/py/unit/test_logout.py +++ b/tests/py/unit/test_logout.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.logout import LogoutEndpoint +from mat3ra_api_client.endpoints.logout import LogoutEndpoint from tests.py.unit import EndpointBaseUnitTest LOGOUT_RESPONSE_FILE = "logout.json" diff --git a/tests/py/unit/test_materials.py b/tests/py/unit/test_materials.py index 9382f1e..d2a9a6e 100644 --- a/tests/py/unit/test_materials.py +++ b/tests/py/unit/test_materials.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.materials import MaterialEndpoints +from mat3ra_api_client.endpoints.materials import MaterialEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "materials" diff --git a/tests/py/unit/test_properties.py b/tests/py/unit/test_properties.py index 506a6b8..4046d0f 100644 --- a/tests/py/unit/test_properties.py +++ b/tests/py/unit/test_properties.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.properties import PropertiesEndpoints +from mat3ra_api_client.endpoints.properties import PropertiesEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "properties" diff --git a/tests/py/unit/test_workflows.py b/tests/py/unit/test_workflows.py index 2b3d7f0..b9a4436 100644 --- a/tests/py/unit/test_workflows.py +++ b/tests/py/unit/test_workflows.py @@ -1,6 +1,6 @@ from unittest import mock -from exabyte_api_client.endpoints.workflows import WorkflowEndpoints +from mat3ra_api_client.endpoints.workflows import WorkflowEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "workflows" From de928fa63402e1ab35da43815ad9552ec2a16805 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 19:50:27 -0800 Subject: [PATCH 08/19] chore: remove --- src/py/mat3ra_api_client/helpers.py | 215 ---------------------------- 1 file changed, 215 deletions(-) delete mode 100644 src/py/mat3ra_api_client/helpers.py diff --git a/src/py/mat3ra_api_client/helpers.py b/src/py/mat3ra_api_client/helpers.py deleted file mode 100644 index f28e1b4..0000000 --- a/src/py/mat3ra_api_client/helpers.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -Helper functions for simplified API interactions. - -This module provides convenient wrappers around the endpoint classes -with automatic OIDC authentication support. -""" - -import os -from typing import Any, Dict, List, Optional - -import requests - -from .endpoints.jobs import JobEndpoints -from .endpoints.materials import MaterialEndpoints -from .endpoints.projects import ProjectEndpoints -from .endpoints.workflows import WorkflowEndpoints - -# Configuration - can be overridden by environment variables -ACCESS_TOKEN_ENV_VAR = os.getenv("ACCESS_TOKEN_ENV_VAR", "OIDC_ACCESS_TOKEN") -ACCOUNT_ID_ENV_VAR = os.getenv("ACCOUNT_ID_ENV_VAR", "ACCOUNT_ID") - - -class Endpoints: - def __init__(self, material, workflow, job, project): - self.material = material - self.workflow = workflow - self.job = job - self.project = project - - -def _create_endpoints_with_auth( - host: str, - port: int, - account_id: str, - auth_token: str, - version: str = "2018-10-01", - secure: bool = True, -) -> Endpoints: - """ - Create endpoint instances with automatic OIDC authentication if available. - - Args: - host: API host - port: API port - account_id: Account ID (can be placeholder if using OIDC) - auth_token: Legacy auth token (can be placeholder if using OIDC) - version: API version - secure: Whether to use HTTPS - - Returns: - Endpoints instance with all endpoint objects - """ - endpoint_args = [host, port, account_id, auth_token, version, secure] - - material_endpoints = MaterialEndpoints(*endpoint_args) - workflow_endpoints = WorkflowEndpoints(*endpoint_args) - job_endpoints = JobEndpoints(*endpoint_args) - project_endpoints = ProjectEndpoints(*endpoint_args) - - # Check if OIDC token is available and use it instead of legacy auth - access_token = os.environ.get(ACCESS_TOKEN_ENV_VAR) - if access_token: - auth_header = {"Authorization": f"Bearer {access_token}"} - material_endpoints.headers.update(auth_header) - workflow_endpoints.headers.update(auth_header) - job_endpoints.headers.update(auth_header) - project_endpoints.headers.update(auth_header) - - return Endpoints(material_endpoints, workflow_endpoints, job_endpoints, project_endpoints) - - -def get_owner_id( - endpoints: Endpoints, - host: str, - port: int, - secure: bool = True, - fallback_account_id: Optional[str] = None, -) -> str: - """ - Get the owner/account ID, fetching from API if OIDC token is available. - - Args: - endpoints: Endpoints instance - host: API host - port: API port - secure: Whether to use HTTPS - fallback_account_id: Fallback account ID if OIDC not available - - Returns: - Account ID string - """ - access_token = os.environ.get(ACCESS_TOKEN_ENV_VAR) - if access_token: - protocol = "https" if secure else "http" - port_str = f":{port}" if port not in [80, 443] else "" - url = f"{protocol}://{host}{port_str}/api/v1/users/me" - - response = requests.get(url, headers={"Authorization": f"Bearer {access_token}"}, timeout=30) - response.raise_for_status() - account_id = response.json()["data"]["user"]["entity"]["defaultAccountId"] - - # Cache it for future use - os.environ[ACCOUNT_ID_ENV_VAR] = account_id - return account_id - - # Fallback to environment or provided account ID - account_id = os.environ.get(ACCOUNT_ID_ENV_VAR) or fallback_account_id - if not account_id or account_id == "ACCOUNT_ID": - raise ValueError( - f"{ACCOUNT_ID_ENV_VAR} is not set. Please authenticate first or provide account_id" - ) - return account_id - - -def get_material(endpoints: Endpoints, material_id: str) -> Dict[str, Any]: - return endpoints.material.get(material_id) - - -def list_materials( - endpoints: Endpoints, - query: Optional[Dict[str, Any]] = None, - owner_id: Optional[str] = None, -) -> List[Dict[str, Any]]: - query_params = query or {} - if owner_id and "owner._id" not in query_params: - query_params["owner._id"] = owner_id - return endpoints.material.list(query_params) - - -def create_material( - endpoints: Endpoints, - material: Any, - owner_id: str, -) -> Dict[str, Any]: - raw_config = material.to_dict() - fields = ["name", "lattice", "basis"] - config = {key: raw_config[key] for key in fields} - return endpoints.material.create(config, owner_id) - - -def get_workflow(endpoints: Endpoints, workflow_id: str) -> Dict[str, Any]: - return endpoints.workflow.get(workflow_id) - - -def list_workflows( - endpoints: Endpoints, - query: Optional[Dict[str, Any]] = None, - owner_id: Optional[str] = None, -) -> List[Dict[str, Any]]: - query_params = query or {} - if owner_id and "owner._id" not in query_params: - query_params["owner._id"] = owner_id - return endpoints.workflow.list(query_params) - - -def create_workflow( - endpoints: Endpoints, - workflow: Any, - owner_id: str, -) -> Dict[str, Any]: - config = workflow.to_dict() - return endpoints.workflow.create(config, owner_id) - - -def get_job(endpoints: Endpoints, job_id: str) -> Dict[str, Any]: - return endpoints.job.get(job_id) - - -def list_jobs( - endpoints: Endpoints, - query: Optional[Dict[str, Any]] = None, - owner_id: Optional[str] = None, -) -> List[Dict[str, Any]]: - query_params = query or {} - if owner_id and "owner._id" not in query_params: - query_params["owner._id"] = owner_id - return endpoints.job.list(query_params) - - -def create_job( - endpoints: Endpoints, - materials: List[str], - workflow_id: str, - project_id: str, - name: str, - compute: Dict[str, Any], - owner_id: str, -) -> Dict[str, Any]: - return endpoints.job.create_by_ids( - materials=materials, - workflow_id=workflow_id, - project_id=project_id, - owner_id=owner_id, - prefix=name, - compute=compute, - ) - - -def get_compute_config(endpoints: Endpoints, cluster: str = "cluster-001") -> Dict[str, Any]: - return endpoints.job.get_compute(cluster=cluster) - - -def submit_job(endpoints: Endpoints, job_id: str) -> Dict[str, Any]: - return endpoints.job.submit(job_id) - -def get_default_project( - endpoints: Endpoints, - owner_id: str, -) -> str: - projects = endpoints.project.list({"isDefault": True, "owner._id": owner_id}) - - if not projects: - raise Exception(f"No default project found for owner {owner_id}") - - return projects[0]["_id"] From e4c5fb21383c9813cd7df2291339b118413e59e7 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 20:49:57 -0800 Subject: [PATCH 09/19] chore: use explicit --- tests/py/integration/__init__.py | 28 +++++++++------------------- tests/py/unit/__init__.py | 20 ++++++-------------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/tests/py/integration/__init__.py b/tests/py/integration/__init__.py index e4c6352..b5b81c8 100644 --- a/tests/py/integration/__init__.py +++ b/tests/py/integration/__init__.py @@ -1,30 +1,20 @@ import os import pytest - from tests.conftest import EndpointBaseTest -ENV_TEST_HOST = "TEST_HOST" -ENV_TEST_PORT = "TEST_PORT" -ENV_TEST_ACCOUNT_ID = "TEST_ACCOUNT_ID" -ENV_TEST_AUTH_TOKEN = "TEST_AUTH_TOKEN" -ENV_TEST_SECURE = "TEST_SECURE" -ENV_TEST_VERSION = "TEST_VERSION" DEFAULT_TEST_SECURE = "False" DEFAULT_TEST_VERSION = "2018-10-01" -REQUIRED_ENV_VARS = [ENV_TEST_HOST, ENV_TEST_PORT, ENV_TEST_ACCOUNT_ID, ENV_TEST_AUTH_TOKEN] -MISSING_ENV_VARS_MESSAGE = ( - "Integration tests require environment variables: TEST_HOST, TEST_PORT, " - "TEST_ACCOUNT_ID, TEST_AUTH_TOKEN. Set them to run integration tests." -) +REQUIRED_ENV_VARS = ["TEST_HOST", "TEST_PORT", "TEST_ACCOUNT_ID", "TEST_AUTH_TOKEN"] def _check_integration_env(): """Check if required integration test environment variables are set.""" missing = [var for var in REQUIRED_ENV_VARS if var not in os.environ] if missing: - pytest.skip(f"{MISSING_ENV_VARS_MESSAGE} Missing: {', '.join(missing)}") + pytest.skip(f"Integration tests require environment variables: TEST_HOST, TEST_PORT, " + + f"TEST_ACCOUNT_ID, TEST_AUTH_TOKEN. Set them to run integration tests. Missing: {', '.join(missing)}") class BaseIntegrationTest(EndpointBaseTest): @@ -36,10 +26,10 @@ def __init__(self, *args, **kwargs): super(BaseIntegrationTest, self).__init__(*args, **kwargs) _check_integration_env() self.endpoint_kwargs = { - "host": os.environ[ENV_TEST_HOST], - "port": os.environ[ENV_TEST_PORT], - "account_id": os.environ[ENV_TEST_ACCOUNT_ID], - "auth_token": os.environ[ENV_TEST_AUTH_TOKEN], - "secure": os.environ.get(ENV_TEST_SECURE, DEFAULT_TEST_SECURE).lower() == "true", - "version": os.environ.get(ENV_TEST_VERSION, DEFAULT_TEST_VERSION), + "host": os.environ["TEST_HOST"], + "port": os.environ["TEST_PORT"], + "account_id": os.environ["TEST_ACCOUNT_ID"], + "auth_token": os.environ["TEST_AUTH_TOKEN"], + "secure": os.environ.get("TEST_SECURE", DEFAULT_TEST_SECURE).lower() == "true", + "version": os.environ.get("TEST_VERSION", DEFAULT_TEST_VERSION), } diff --git a/tests/py/unit/__init__.py b/tests/py/unit/__init__.py index 9f6f7f9..ea0e732 100644 --- a/tests/py/unit/__init__.py +++ b/tests/py/unit/__init__.py @@ -2,14 +2,6 @@ from tests.conftest import EndpointBaseTest -DEFAULT_TEST_PORT = 4000 -DEFAULT_TEST_VERSION = "2018-10-01" -DEFAULT_TEST_HOST = "platform.mat3ra.com" -DEFAULT_TEST_ACCOUNT_ID = "ubxMkAyx37Rjn8qK9" -DEFAULT_TEST_AUTH_TOKEN = "XihOnUA8EqytSui1icz6fYhsJ2tUsJGGTlV03upYPSF" -HTTP_STATUS_OK = 200 -HTTP_REASON_OK = "OK" - class EndpointBaseUnitTest(EndpointBaseTest): """ @@ -18,13 +10,13 @@ class EndpointBaseUnitTest(EndpointBaseTest): def __init__(self, *args, **kwargs): super(EndpointBaseUnitTest, self).__init__(*args, **kwargs) - self.port = DEFAULT_TEST_PORT - self.version = DEFAULT_TEST_VERSION - self.host = DEFAULT_TEST_HOST - self.account_id = DEFAULT_TEST_ACCOUNT_ID - self.auth_token = DEFAULT_TEST_AUTH_TOKEN + self.port = 4000 + self.version = "2018-10-01" + self.host = "platform.mat3ra.com" + self.account_id = "ubxMkAyx37Rjn8qK9" + self.auth_token = "XihOnUA8EqytSui1icz6fYhsJ2tUsJGGTlV03upYPSF" - def mock_response(self, content, status_code=HTTP_STATUS_OK, reason=HTTP_REASON_OK): + def mock_response(self, content, status_code=200, reason="OK"): response = Response() response._content = content.encode() response.status_code = status_code From d868018a3d6483051db722a9992d154f4b7bbfe6 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 23 Dec 2025 20:53:34 -0800 Subject: [PATCH 10/19] update: move to test/py --- tests/py/__init__.py | 0 tests/{ => py}/conftest.py | 2 +- tests/py/integration/__init__.py | 2 +- tests/py/unit/__init__.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 tests/py/__init__.py rename tests/{ => py}/conftest.py (88%) diff --git a/tests/py/__init__.py b/tests/py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/py/conftest.py similarity index 88% rename from tests/conftest.py rename to tests/py/conftest.py index b81fe30..d6a7641 100644 --- a/tests/conftest.py +++ b/tests/py/conftest.py @@ -15,7 +15,7 @@ def __init__(self, *args, **kwargs): super(EndpointBaseTest, self).__init__(*args, **kwargs) def get_file_path(self, filename): - return os.path.join(os.path.dirname(__file__), "py", "data", filename) + return os.path.join(os.path.dirname(__file__), "data", filename) def get_content(self, filename): with open(self.get_file_path(filename)) as f: diff --git a/tests/py/integration/__init__.py b/tests/py/integration/__init__.py index b5b81c8..a0e236f 100644 --- a/tests/py/integration/__init__.py +++ b/tests/py/integration/__init__.py @@ -1,7 +1,7 @@ import os import pytest -from tests.conftest import EndpointBaseTest +from tests.py.conftest import EndpointBaseTest DEFAULT_TEST_SECURE = "False" DEFAULT_TEST_VERSION = "2018-10-01" diff --git a/tests/py/unit/__init__.py b/tests/py/unit/__init__.py index ea0e732..3cf693a 100644 --- a/tests/py/unit/__init__.py +++ b/tests/py/unit/__init__.py @@ -1,6 +1,6 @@ from requests import Response -from tests.conftest import EndpointBaseTest +from tests.py.conftest import EndpointBaseTest class EndpointBaseUnitTest(EndpointBaseTest): From 4d0d97aa8c1d868ccb140ef55ef0dd6fb1762777 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 24 Dec 2025 16:30:02 -0800 Subject: [PATCH 11/19] update: move to mat3ra spacename' --- README.md | 14 +++++++++++++- pyproject.toml | 6 +++--- src/py/mat3ra/__init__.py | 3 +++ src/py/mat3ra/api_client/__init__.py | 18 ++++++++++++++++++ .../api_client}/client.py | 16 ++++++++-------- .../api_client}/endpoints/__init__.py | 0 .../api_client}/endpoints/bank_entity.py | 0 .../api_client}/endpoints/bank_materials.py | 0 .../api_client}/endpoints/bank_workflows.py | 0 .../api_client}/endpoints/charges.py | 0 .../api_client}/endpoints/entity.py | 0 .../api_client}/endpoints/enums.py | 0 .../api_client}/endpoints/jobs.py | 0 .../api_client}/endpoints/login.py | 0 .../api_client}/endpoints/logout.py | 0 .../api_client}/endpoints/materials.py | 0 .../api_client}/endpoints/metaproperties.py | 0 .../api_client}/endpoints/mixins/__init__.py | 0 .../api_client}/endpoints/mixins/default.py | 0 .../api_client}/endpoints/mixins/set.py | 0 .../api_client}/endpoints/projects.py | 0 .../api_client}/endpoints/properties.py | 0 .../api_client}/endpoints/workflows.py | 0 .../api_client}/utils/__init__.py | 0 .../api_client}/utils/http.py | 0 .../api_client}/utils/materials.py | 0 src/py/mat3ra_api_client/__init__.py | 18 ------------------ tests/py/integration/test_jobs.py | 2 +- tests/py/integration/test_materials.py | 2 +- tests/py/unit/test_bank_materials.py | 2 +- tests/py/unit/test_bank_workflows.py | 2 +- tests/py/unit/test_client.py | 2 +- tests/py/unit/test_httpBase.py | 2 +- tests/py/unit/test_jobs.py | 2 +- tests/py/unit/test_login.py | 2 +- tests/py/unit/test_logout.py | 2 +- tests/py/unit/test_materials.py | 2 +- tests/py/unit/test_properties.py | 2 +- tests/py/unit/test_workflows.py | 2 +- 39 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 src/py/mat3ra/__init__.py create mode 100644 src/py/mat3ra/api_client/__init__.py rename src/py/{mat3ra_api_client => mat3ra/api_client}/client.py (94%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/__init__.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/bank_entity.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/bank_materials.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/bank_workflows.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/charges.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/entity.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/enums.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/jobs.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/login.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/logout.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/materials.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/metaproperties.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/mixins/__init__.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/mixins/default.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/mixins/set.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/projects.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/properties.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/endpoints/workflows.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/utils/__init__.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/utils/http.py (100%) rename src/py/{mat3ra_api_client => mat3ra/api_client}/utils/materials.py (100%) delete mode 100644 src/py/mat3ra_api_client/__init__.py diff --git a/README.md b/README.md index 5157ed6..94d8221 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,18 @@ cd api-client pip install -e . ``` +# Usage + +```python +from mat3ra.api_client import APIClient + +# Authenticate with OIDC token +client = APIClient.authenticate() + +# Access endpoints +materials = client.materials.list() +``` + # Examples [api-examples](https://github.com/Exabyte-io/api-examples) repository contains examples for performing most-common tasks in the Mat3ra.com platform through its RESTful API in Jupyter Notebook format. @@ -75,7 +87,7 @@ pytest tests/py ### Run Tests with Coverage ```bash -pytest tests/py/unit --cov=mat3ra_api_client --cov-report=term --cov-report=html +pytest tests/py/unit --cov=mat3ra.api_client --cov-report=term --cov-report=html ``` **Note:** Integration tests will be automatically skipped if required environment variables are not set. diff --git a/pyproject.toml b/pyproject.toml index 97be618..6769ec3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "mat3ra-api-client" +name = "mat3ra.api-client" dynamic = ["version"] description = "Mat3ra.com Python Client for RESTful API" readme = "README.md" @@ -54,7 +54,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] git_describe_command = "git describe --tags --long" -write_to = "src/py/mat3ra_api_client/_version.py" +write_to = "src/py/mat3ra/api_client/_version.py" [tool.setuptools.packages.find] where = ["src/py"] @@ -93,7 +93,7 @@ testpaths = [ [tool.coverage.run] source = ["src/py"] -omit = ["env*/*", "venv*/*", "tests/*", "src/py/mat3ra_api_client/_version.py"] +omit = ["env*/*", "venv*/*", "tests/*", "src/py/mat3ra/api_client/_version.py"] [tool.coverage.report] exclude_lines = [ diff --git a/src/py/mat3ra/__init__.py b/src/py/mat3ra/__init__.py new file mode 100644 index 0000000..efa22de --- /dev/null +++ b/src/py/mat3ra/__init__.py @@ -0,0 +1,3 @@ +"""Mat3ra namespace package.""" +__path__ = __import__("pkgutil").extend_path(__path__, __name__) + diff --git a/src/py/mat3ra/api_client/__init__.py b/src/py/mat3ra/api_client/__init__.py new file mode 100644 index 0000000..43e090a --- /dev/null +++ b/src/py/mat3ra/api_client/__init__.py @@ -0,0 +1,18 @@ +# ruff: noqa: F401 +try: + from ._version import version as __version__ +except ModuleNotFoundError: + __version__ = None + +from mat3ra.api_client.endpoints.bank_materials import BankMaterialEndpoints +from mat3ra.api_client.endpoints.bank_workflows import BankWorkflowEndpoints +from mat3ra.api_client.endpoints.jobs import JobEndpoints +from mat3ra.api_client.endpoints.login import LoginEndpoint +from mat3ra.api_client.endpoints.logout import LogoutEndpoint +from mat3ra.api_client.endpoints.materials import MaterialEndpoints +from mat3ra.api_client.endpoints.metaproperties import MetaPropertiesEndpoints +from mat3ra.api_client.endpoints.projects import ProjectEndpoints +from mat3ra.api_client.endpoints.properties import PropertiesEndpoints +from mat3ra.api_client.endpoints.workflows import WorkflowEndpoints + +from mat3ra.api_client.client import APIClient diff --git a/src/py/mat3ra_api_client/client.py b/src/py/mat3ra/api_client/client.py similarity index 94% rename from src/py/mat3ra_api_client/client.py rename to src/py/mat3ra/api_client/client.py index f3020ff..cefed9c 100644 --- a/src/py/mat3ra_api_client/client.py +++ b/src/py/mat3ra/api_client/client.py @@ -4,14 +4,14 @@ import requests from pydantic import BaseModel, ConfigDict, Field -from mat3ra_api_client.endpoints.bank_materials import BankMaterialEndpoints -from mat3ra_api_client.endpoints.bank_workflows import BankWorkflowEndpoints -from mat3ra_api_client.endpoints.jobs import JobEndpoints -from mat3ra_api_client.endpoints.materials import MaterialEndpoints -from mat3ra_api_client.endpoints.metaproperties import MetaPropertiesEndpoints -from mat3ra_api_client.endpoints.projects import ProjectEndpoints -from mat3ra_api_client.endpoints.properties import PropertiesEndpoints -from mat3ra_api_client.endpoints.workflows import WorkflowEndpoints +from mat3ra.api_client.endpoints.bank_materials import BankMaterialEndpoints +from mat3ra.api_client.endpoints.bank_workflows import BankWorkflowEndpoints +from mat3ra.api_client.endpoints.jobs import JobEndpoints +from mat3ra.api_client.endpoints.materials import MaterialEndpoints +from mat3ra.api_client.endpoints.metaproperties import MetaPropertiesEndpoints +from mat3ra.api_client.endpoints.projects import ProjectEndpoints +from mat3ra.api_client.endpoints.properties import PropertiesEndpoints +from mat3ra.api_client.endpoints.workflows import WorkflowEndpoints # Default OIDC Configuration OIDC_BASE_URL = "http://localhost:3000/oidc" diff --git a/src/py/mat3ra_api_client/endpoints/__init__.py b/src/py/mat3ra/api_client/endpoints/__init__.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/__init__.py rename to src/py/mat3ra/api_client/endpoints/__init__.py diff --git a/src/py/mat3ra_api_client/endpoints/bank_entity.py b/src/py/mat3ra/api_client/endpoints/bank_entity.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/bank_entity.py rename to src/py/mat3ra/api_client/endpoints/bank_entity.py diff --git a/src/py/mat3ra_api_client/endpoints/bank_materials.py b/src/py/mat3ra/api_client/endpoints/bank_materials.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/bank_materials.py rename to src/py/mat3ra/api_client/endpoints/bank_materials.py diff --git a/src/py/mat3ra_api_client/endpoints/bank_workflows.py b/src/py/mat3ra/api_client/endpoints/bank_workflows.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/bank_workflows.py rename to src/py/mat3ra/api_client/endpoints/bank_workflows.py diff --git a/src/py/mat3ra_api_client/endpoints/charges.py b/src/py/mat3ra/api_client/endpoints/charges.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/charges.py rename to src/py/mat3ra/api_client/endpoints/charges.py diff --git a/src/py/mat3ra_api_client/endpoints/entity.py b/src/py/mat3ra/api_client/endpoints/entity.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/entity.py rename to src/py/mat3ra/api_client/endpoints/entity.py diff --git a/src/py/mat3ra_api_client/endpoints/enums.py b/src/py/mat3ra/api_client/endpoints/enums.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/enums.py rename to src/py/mat3ra/api_client/endpoints/enums.py diff --git a/src/py/mat3ra_api_client/endpoints/jobs.py b/src/py/mat3ra/api_client/endpoints/jobs.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/jobs.py rename to src/py/mat3ra/api_client/endpoints/jobs.py diff --git a/src/py/mat3ra_api_client/endpoints/login.py b/src/py/mat3ra/api_client/endpoints/login.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/login.py rename to src/py/mat3ra/api_client/endpoints/login.py diff --git a/src/py/mat3ra_api_client/endpoints/logout.py b/src/py/mat3ra/api_client/endpoints/logout.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/logout.py rename to src/py/mat3ra/api_client/endpoints/logout.py diff --git a/src/py/mat3ra_api_client/endpoints/materials.py b/src/py/mat3ra/api_client/endpoints/materials.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/materials.py rename to src/py/mat3ra/api_client/endpoints/materials.py diff --git a/src/py/mat3ra_api_client/endpoints/metaproperties.py b/src/py/mat3ra/api_client/endpoints/metaproperties.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/metaproperties.py rename to src/py/mat3ra/api_client/endpoints/metaproperties.py diff --git a/src/py/mat3ra_api_client/endpoints/mixins/__init__.py b/src/py/mat3ra/api_client/endpoints/mixins/__init__.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/mixins/__init__.py rename to src/py/mat3ra/api_client/endpoints/mixins/__init__.py diff --git a/src/py/mat3ra_api_client/endpoints/mixins/default.py b/src/py/mat3ra/api_client/endpoints/mixins/default.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/mixins/default.py rename to src/py/mat3ra/api_client/endpoints/mixins/default.py diff --git a/src/py/mat3ra_api_client/endpoints/mixins/set.py b/src/py/mat3ra/api_client/endpoints/mixins/set.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/mixins/set.py rename to src/py/mat3ra/api_client/endpoints/mixins/set.py diff --git a/src/py/mat3ra_api_client/endpoints/projects.py b/src/py/mat3ra/api_client/endpoints/projects.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/projects.py rename to src/py/mat3ra/api_client/endpoints/projects.py diff --git a/src/py/mat3ra_api_client/endpoints/properties.py b/src/py/mat3ra/api_client/endpoints/properties.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/properties.py rename to src/py/mat3ra/api_client/endpoints/properties.py diff --git a/src/py/mat3ra_api_client/endpoints/workflows.py b/src/py/mat3ra/api_client/endpoints/workflows.py similarity index 100% rename from src/py/mat3ra_api_client/endpoints/workflows.py rename to src/py/mat3ra/api_client/endpoints/workflows.py diff --git a/src/py/mat3ra_api_client/utils/__init__.py b/src/py/mat3ra/api_client/utils/__init__.py similarity index 100% rename from src/py/mat3ra_api_client/utils/__init__.py rename to src/py/mat3ra/api_client/utils/__init__.py diff --git a/src/py/mat3ra_api_client/utils/http.py b/src/py/mat3ra/api_client/utils/http.py similarity index 100% rename from src/py/mat3ra_api_client/utils/http.py rename to src/py/mat3ra/api_client/utils/http.py diff --git a/src/py/mat3ra_api_client/utils/materials.py b/src/py/mat3ra/api_client/utils/materials.py similarity index 100% rename from src/py/mat3ra_api_client/utils/materials.py rename to src/py/mat3ra/api_client/utils/materials.py diff --git a/src/py/mat3ra_api_client/__init__.py b/src/py/mat3ra_api_client/__init__.py deleted file mode 100644 index d2a4de4..0000000 --- a/src/py/mat3ra_api_client/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# ruff: noqa: F401 -try: - from ._version import version as __version__ -except ModuleNotFoundError: - __version__ = None - -from mat3ra_api_client.endpoints.bank_materials import BankMaterialEndpoints -from mat3ra_api_client.endpoints.bank_workflows import BankWorkflowEndpoints -from mat3ra_api_client.endpoints.jobs import JobEndpoints -from mat3ra_api_client.endpoints.login import LoginEndpoint -from mat3ra_api_client.endpoints.logout import LogoutEndpoint -from mat3ra_api_client.endpoints.materials import MaterialEndpoints -from mat3ra_api_client.endpoints.metaproperties import MetaPropertiesEndpoints -from mat3ra_api_client.endpoints.projects import ProjectEndpoints -from mat3ra_api_client.endpoints.properties import PropertiesEndpoints -from mat3ra_api_client.endpoints.workflows import WorkflowEndpoints - -from mat3ra_api_client.client import APIClient diff --git a/tests/py/integration/test_jobs.py b/tests/py/integration/test_jobs.py index c4fdd46..6d85d1f 100644 --- a/tests/py/integration/test_jobs.py +++ b/tests/py/integration/test_jobs.py @@ -1,7 +1,7 @@ import datetime import time -from mat3ra_api_client.endpoints.jobs import JobEndpoints +from mat3ra.api_client.endpoints.jobs import JobEndpoints from tests.py.integration.entity import EntityIntegrationTest KNOWN_COMPLETED_JOB_ID = "9gyhfncWDhnSyzALv" diff --git a/tests/py/integration/test_materials.py b/tests/py/integration/test_materials.py index fc808c7..daecba5 100644 --- a/tests/py/integration/test_materials.py +++ b/tests/py/integration/test_materials.py @@ -1,4 +1,4 @@ -from mat3ra_api_client.endpoints.materials import MaterialEndpoints +from mat3ra.api_client.endpoints.materials import MaterialEndpoints from tests.py.integration.entity import EntityIntegrationTest MATERIAL_DATA_FILE = "material.json" diff --git a/tests/py/unit/test_bank_materials.py b/tests/py/unit/test_bank_materials.py index 959b1e4..6d3591d 100644 --- a/tests/py/unit/test_bank_materials.py +++ b/tests/py/unit/test_bank_materials.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.bank_materials import BankMaterialEndpoints +from mat3ra.api_client.endpoints.bank_materials import BankMaterialEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "bank-materials" diff --git a/tests/py/unit/test_bank_workflows.py b/tests/py/unit/test_bank_workflows.py index 930a3fd..2081311 100644 --- a/tests/py/unit/test_bank_workflows.py +++ b/tests/py/unit/test_bank_workflows.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.bank_workflows import BankWorkflowEndpoints +from mat3ra.api_client.endpoints.bank_workflows import BankWorkflowEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "bank-workflows" diff --git a/tests/py/unit/test_client.py b/tests/py/unit/test_client.py index f209d1e..351cec3 100644 --- a/tests/py/unit/test_client.py +++ b/tests/py/unit/test_client.py @@ -3,7 +3,7 @@ from pydantic import ValidationError -from mat3ra_api_client import APIClient +from mat3ra.api_client import APIClient from tests.py.unit import EndpointBaseUnitTest API_HOST = "platform.mat3ra.com" diff --git a/tests/py/unit/test_httpBase.py b/tests/py/unit/test_httpBase.py index af2c0ec..be3a8dc 100644 --- a/tests/py/unit/test_httpBase.py +++ b/tests/py/unit/test_httpBase.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.utils.http import Connection +from mat3ra.api_client.utils.http import Connection from requests.exceptions import HTTPError from tests.py.unit import EndpointBaseUnitTest diff --git a/tests/py/unit/test_jobs.py b/tests/py/unit/test_jobs.py index caad47f..1e69c40 100644 --- a/tests/py/unit/test_jobs.py +++ b/tests/py/unit/test_jobs.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.jobs import JobEndpoints +from mat3ra.api_client.endpoints.jobs import JobEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "jobs" diff --git a/tests/py/unit/test_login.py b/tests/py/unit/test_login.py index 4c923a1..5ec6a29 100644 --- a/tests/py/unit/test_login.py +++ b/tests/py/unit/test_login.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.login import LoginEndpoint +from mat3ra.api_client.endpoints.login import LoginEndpoint from tests.py.unit import EndpointBaseUnitTest TEST_USERNAME = "test" diff --git a/tests/py/unit/test_logout.py b/tests/py/unit/test_logout.py index 389c858..6bab97c 100644 --- a/tests/py/unit/test_logout.py +++ b/tests/py/unit/test_logout.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.logout import LogoutEndpoint +from mat3ra.api_client.endpoints.logout import LogoutEndpoint from tests.py.unit import EndpointBaseUnitTest LOGOUT_RESPONSE_FILE = "logout.json" diff --git a/tests/py/unit/test_materials.py b/tests/py/unit/test_materials.py index d2a9a6e..4dcb849 100644 --- a/tests/py/unit/test_materials.py +++ b/tests/py/unit/test_materials.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.materials import MaterialEndpoints +from mat3ra.api_client.endpoints.materials import MaterialEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "materials" diff --git a/tests/py/unit/test_properties.py b/tests/py/unit/test_properties.py index 4046d0f..9cad06c 100644 --- a/tests/py/unit/test_properties.py +++ b/tests/py/unit/test_properties.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.properties import PropertiesEndpoints +from mat3ra.api_client.endpoints.properties import PropertiesEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "properties" diff --git a/tests/py/unit/test_workflows.py b/tests/py/unit/test_workflows.py index b9a4436..9586a85 100644 --- a/tests/py/unit/test_workflows.py +++ b/tests/py/unit/test_workflows.py @@ -1,6 +1,6 @@ from unittest import mock -from mat3ra_api_client.endpoints.workflows import WorkflowEndpoints +from mat3ra.api_client.endpoints.workflows import WorkflowEndpoints from tests.py.unit.entity import EntityEndpointsUnitTest ENDPOINT_NAME = "workflows" From d9ccf11bb2b4820878889bf0b6158c1cd8aeeeae Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 24 Dec 2025 16:40:44 -0800 Subject: [PATCH 12/19] chore: remove uselsess --- src/py/mat3ra/api_client/endpoints/__init__.py | 6 +++--- src/py/mat3ra/api_client/endpoints/bank_entity.py | 6 +++--- src/py/mat3ra/api_client/endpoints/bank_materials.py | 6 +++--- src/py/mat3ra/api_client/endpoints/bank_workflows.py | 6 +++--- src/py/mat3ra/api_client/endpoints/charges.py | 6 +++--- src/py/mat3ra/api_client/endpoints/entity.py | 6 +++--- src/py/mat3ra/api_client/endpoints/jobs.py | 6 +++--- src/py/mat3ra/api_client/endpoints/login.py | 12 ++++++------ src/py/mat3ra/api_client/endpoints/logout.py | 6 +++--- src/py/mat3ra/api_client/endpoints/materials.py | 6 +++--- src/py/mat3ra/api_client/endpoints/metaproperties.py | 6 +++--- src/py/mat3ra/api_client/endpoints/projects.py | 6 +++--- src/py/mat3ra/api_client/endpoints/properties.py | 6 +++--- src/py/mat3ra/api_client/endpoints/workflows.py | 6 +++--- src/py/mat3ra/api_client/utils/http.py | 6 +++--- tests/py/integration/__init__.py | 4 ++-- 16 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/py/mat3ra/api_client/endpoints/__init__.py b/src/py/mat3ra/api_client/endpoints/__init__.py index 3b0021a..12fd023 100644 --- a/src/py/mat3ra/api_client/endpoints/__init__.py +++ b/src/py/mat3ra/api_client/endpoints/__init__.py @@ -8,9 +8,9 @@ class BaseEndpoint(object): Base class for Exabyte RESTful API endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. - version (str): Mat3ra API version. Defaults to 2018-10-1. + host (str): API hostname. + port (int): API port number. + version (str): API version. Defaults to 2018-10-1. secure (bool): whether to use secure http protocol (https vs http). Defaults to True. Attributes: diff --git a/src/py/mat3ra/api_client/endpoints/bank_entity.py b/src/py/mat3ra/api_client/endpoints/bank_entity.py index fce9109..bae1573 100644 --- a/src/py/mat3ra/api_client/endpoints/bank_entity.py +++ b/src/py/mat3ra/api_client/endpoints/bank_entity.py @@ -7,11 +7,11 @@ class BankEntityEndpoints(EntityEndpoint): Bank Entity endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/bank_materials.py b/src/py/mat3ra/api_client/endpoints/bank_materials.py index c3c6b11..fb53de6 100644 --- a/src/py/mat3ra/api_client/endpoints/bank_materials.py +++ b/src/py/mat3ra/api_client/endpoints/bank_materials.py @@ -7,11 +7,11 @@ class BankMaterialEndpoints(BankEntityEndpoints): Bank material endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/bank_workflows.py b/src/py/mat3ra/api_client/endpoints/bank_workflows.py index 96d34b8..2267665 100644 --- a/src/py/mat3ra/api_client/endpoints/bank_workflows.py +++ b/src/py/mat3ra/api_client/endpoints/bank_workflows.py @@ -7,11 +7,11 @@ class BankWorkflowEndpoints(BankEntityEndpoints): Bank workflow endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/charges.py b/src/py/mat3ra/api_client/endpoints/charges.py index bd58192..7181cbb 100644 --- a/src/py/mat3ra/api_client/endpoints/charges.py +++ b/src/py/mat3ra/api_client/endpoints/charges.py @@ -7,11 +7,11 @@ class ChargeEndpoints(EntityEndpoint): Charge endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/entity.py b/src/py/mat3ra/api_client/endpoints/entity.py index 4c7c4ff..be0ab1e 100644 --- a/src/py/mat3ra/api_client/endpoints/entity.py +++ b/src/py/mat3ra/api_client/endpoints/entity.py @@ -9,11 +9,11 @@ class EntityEndpoint(BaseEndpoint): Exabyte Entity endpoint. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/jobs.py b/src/py/mat3ra/api_client/endpoints/jobs.py index b2c37c1..d5b703c 100644 --- a/src/py/mat3ra/api_client/endpoints/jobs.py +++ b/src/py/mat3ra/api_client/endpoints/jobs.py @@ -10,11 +10,11 @@ class JobEndpoints(EntitySetEndpointsMixin, EntityEndpoint): Job endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/login.py b/src/py/mat3ra/api_client/endpoints/login.py index 6813cbf..bd92023 100644 --- a/src/py/mat3ra/api_client/endpoints/login.py +++ b/src/py/mat3ra/api_client/endpoints/login.py @@ -7,11 +7,11 @@ class LoginEndpoint(BaseEndpoint): Login endpoint. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. username (str): username. password (str): password. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. @@ -44,11 +44,11 @@ def get_endpoint_options(host, port, username, password, version=DEFAULT_API_VER Logs in with given parameters and returns options to use for further calls to the RESTful API. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. username (str): username. password (str): password. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). Returns: diff --git a/src/py/mat3ra/api_client/endpoints/logout.py b/src/py/mat3ra/api_client/endpoints/logout.py index 114fb00..3d9d510 100644 --- a/src/py/mat3ra/api_client/endpoints/logout.py +++ b/src/py/mat3ra/api_client/endpoints/logout.py @@ -7,11 +7,11 @@ class LogoutEndpoint(BaseEndpoint): Logout endpoint. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/materials.py b/src/py/mat3ra/api_client/endpoints/materials.py index f5b6326..856e592 100644 --- a/src/py/mat3ra/api_client/endpoints/materials.py +++ b/src/py/mat3ra/api_client/endpoints/materials.py @@ -12,11 +12,11 @@ class MaterialEndpoints(EntitySetEndpointsMixin, DefaultableEntityEndpointsMixin Material endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/metaproperties.py b/src/py/mat3ra/api_client/endpoints/metaproperties.py index 73affd7..7d131c2 100644 --- a/src/py/mat3ra/api_client/endpoints/metaproperties.py +++ b/src/py/mat3ra/api_client/endpoints/metaproperties.py @@ -7,11 +7,11 @@ class MetaPropertiesEndpoints(BasePropertiesEndpoints): MetaProperties endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/projects.py b/src/py/mat3ra/api_client/endpoints/projects.py index b7cbd6c..91adff4 100644 --- a/src/py/mat3ra/api_client/endpoints/projects.py +++ b/src/py/mat3ra/api_client/endpoints/projects.py @@ -8,11 +8,11 @@ class ProjectEndpoints(DefaultableEntityEndpointsMixin, EntityEndpoint): Project endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/properties.py b/src/py/mat3ra/api_client/endpoints/properties.py index 3d3f2b1..d6208cb 100644 --- a/src/py/mat3ra/api_client/endpoints/properties.py +++ b/src/py/mat3ra/api_client/endpoints/properties.py @@ -26,11 +26,11 @@ class PropertiesEndpoints(BasePropertiesEndpoints): Properties endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/endpoints/workflows.py b/src/py/mat3ra/api_client/endpoints/workflows.py index 45c54a3..5e256e3 100644 --- a/src/py/mat3ra/api_client/endpoints/workflows.py +++ b/src/py/mat3ra/api_client/endpoints/workflows.py @@ -8,11 +8,11 @@ class WorkflowEndpoints(DefaultableEntityEndpointsMixin, EntityEndpoint): Workflow endpoints. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. + host (str): API hostname. + port (int): API port number. account_id (str): account ID. auth_token (str): authentication token. - version (str): Mat3ra API version. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/src/py/mat3ra/api_client/utils/http.py b/src/py/mat3ra/api_client/utils/http.py index b42d315..73b80de 100644 --- a/src/py/mat3ra/api_client/utils/http.py +++ b/src/py/mat3ra/api_client/utils/http.py @@ -117,9 +117,9 @@ class Connection(BaseConnection): Exabyte connection class. Args: - host (str): Mat3ra API hostname. - port (int): Mat3ra API port number. - version (str): Mat3ra API version. + host (str): API hostname. + port (int): API port number. + version (str): API version. secure (bool): whether to use secure http protocol (https vs http). kwargs (dict): a dictionary of HTTP session options. timeout (int): session timeout in seconds. diff --git a/tests/py/integration/__init__.py b/tests/py/integration/__init__.py index a0e236f..d7ddfc9 100644 --- a/tests/py/integration/__init__.py +++ b/tests/py/integration/__init__.py @@ -13,8 +13,8 @@ def _check_integration_env(): """Check if required integration test environment variables are set.""" missing = [var for var in REQUIRED_ENV_VARS if var not in os.environ] if missing: - pytest.skip(f"Integration tests require environment variables: TEST_HOST, TEST_PORT, " + - f"TEST_ACCOUNT_ID, TEST_AUTH_TOKEN. Set them to run integration tests. Missing: {', '.join(missing)}") + pytest.skip("Integration tests require environment variables: TEST_HOST, TEST_PORT, TEST_ACCOUNT_ID, " + + f"TEST_AUTH_TOKEN. Set them to run integration tests. Missing: {', '.join(missing)}") class BaseIntegrationTest(EndpointBaseTest): From 321b1e3fed74b90fdc060c633be4da4afc7c9940 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 24 Dec 2025 16:42:02 -0800 Subject: [PATCH 13/19] chore: remove useless --- src/py/mat3ra/api_client/endpoints/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/mat3ra/api_client/endpoints/__init__.py b/src/py/mat3ra/api_client/endpoints/__init__.py index 12fd023..5eae989 100644 --- a/src/py/mat3ra/api_client/endpoints/__init__.py +++ b/src/py/mat3ra/api_client/endpoints/__init__.py @@ -14,7 +14,7 @@ class BaseEndpoint(object): secure (bool): whether to use secure http protocol (https vs http). Defaults to True. Attributes: - conn (httplib.Mat3raConnection): Mat3raConnection instance. + conn (httplib.Connection): Connection instance. """ def __init__(self, host, port, version="2018-10-1", secure=True, **kwargs): From 1c885f15452e3cf34c73f7abf4b77fb128fa5c1f Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 24 Dec 2025 22:24:36 -0800 Subject: [PATCH 14/19] update: prod values --- src/py/mat3ra/api_client/client.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/py/mat3ra/api_client/client.py b/src/py/mat3ra/api_client/client.py index cefed9c..09c93e2 100644 --- a/src/py/mat3ra/api_client/client.py +++ b/src/py/mat3ra/api_client/client.py @@ -13,6 +13,12 @@ from mat3ra.api_client.endpoints.properties import PropertiesEndpoints from mat3ra.api_client.endpoints.workflows import WorkflowEndpoints +# Default API Configuration +DEFAULT_API_HOST = "platform.mat3ra.com" +DEFAULT_API_PORT = 443 +DEFAULT_API_VERSION = "2018-10-01" +DEFAULT_API_SECURE = True + # Default OIDC Configuration OIDC_BASE_URL = "http://localhost:3000/oidc" CLIENT_ID = "default-client" @@ -53,10 +59,10 @@ class AuthContext(BaseModel): class APIEnv(BaseModel): - host: str = Field(validation_alias=API_HOST_ENV_VAR) - port: int = Field(validation_alias=API_PORT_ENV_VAR) - version: str = Field(validation_alias=API_VERSION_ENV_VAR) - secure: bool = Field(validation_alias=API_SECURE_ENV_VAR) + host: str = Field(default=DEFAULT_API_HOST, validation_alias=API_HOST_ENV_VAR) + port: int = Field(default=DEFAULT_API_PORT, validation_alias=API_PORT_ENV_VAR) + version: str = Field(default=DEFAULT_API_VERSION, validation_alias=API_VERSION_ENV_VAR) + secure: bool = Field(default=DEFAULT_API_SECURE, validation_alias=API_SECURE_ENV_VAR) @classmethod def from_env(cls) -> "APIEnv": From 6b38bb2523adabe1ffc61dd987fcd18198ecc253 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 24 Dec 2025 22:27:00 -0800 Subject: [PATCH 15/19] update: we have defaults now --- tests/py/unit/test_client.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/py/unit/test_client.py b/tests/py/unit/test_client.py index 351cec3..a06be1d 100644 --- a/tests/py/unit/test_client.py +++ b/tests/py/unit/test_client.py @@ -34,22 +34,6 @@ def _mock_users_me(self, mock_get): mock_resp.raise_for_status.return_value = None mock_get.return_value = mock_resp - def test_authenticate_requires_env_config(self): - cases = [ - ("API_HOST",), - ("API_PORT",), - ("API_VERSION",), - ("API_SECURE",), - ] - for (missing_key,) in cases: - with self.subTest(missing_key=missing_key): - env = self._base_env() - env.pop(missing_key) - env["OIDC_ACCESS_TOKEN"] = OIDC_ACCESS_TOKEN - with mock.patch.dict("os.environ", env, clear=True): - with self.assertRaises(ValidationError): - APIClient.authenticate() - def test_authenticate_requires_auth(self): env = self._base_env() with mock.patch.dict("os.environ", env, clear=True): @@ -88,5 +72,3 @@ def test_my_account_id_fetches_and_caches(self, mock_get): self.assertEqual(mock_get.call_args[1]["headers"]["Authorization"], f"Bearer {OIDC_ACCESS_TOKEN}") self.assertEqual(mock_get.call_args[1]["timeout"], 30) self.assertEqual(os.environ.get("ACCOUNT_ID"), ME_ACCOUNT_ID) - - From 2805be6ac1611ea24f827ffa0714c94267ce2a96 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 24 Dec 2025 23:24:10 -0800 Subject: [PATCH 16/19] update: oidc url --- src/py/mat3ra/api_client/client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/py/mat3ra/api_client/client.py b/src/py/mat3ra/api_client/client.py index 09c93e2..90fcafd 100644 --- a/src/py/mat3ra/api_client/client.py +++ b/src/py/mat3ra/api_client/client.py @@ -20,7 +20,6 @@ DEFAULT_API_SECURE = True # Default OIDC Configuration -OIDC_BASE_URL = "http://localhost:3000/oidc" CLIENT_ID = "default-client" CLIENT_SECRET = "default-secret" SCOPE = "openid profile email" @@ -99,6 +98,12 @@ def _build_users_me_url(host: str, port: int, secure: bool) -> str: return f"{protocol}://{host}{port_str}{USERS_ME_PATH}" +def _build_oidc_base_url(host: str, port: int, secure: bool) -> str: + protocol = PROTOCOL_HTTPS if secure else PROTOCOL_HTTP + port_str = f":{port}" if port not in [80, 443] else "" + return f"{protocol}://{host}{port_str}/oidc" + + class APIClient(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow", validate_assignment=True) From 66d7be74f3d5249bc51e833f6987a06cb0201aae Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Thu, 25 Dec 2025 10:36:13 -0800 Subject: [PATCH 17/19] chore: lint fix --- tests/py/unit/test_client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/py/unit/test_client.py b/tests/py/unit/test_client.py index a06be1d..59306ad 100644 --- a/tests/py/unit/test_client.py +++ b/tests/py/unit/test_client.py @@ -1,9 +1,8 @@ import os from unittest import mock -from pydantic import ValidationError - from mat3ra.api_client import APIClient + from tests.py.unit import EndpointBaseUnitTest API_HOST = "platform.mat3ra.com" From 59edbbd6690164c5766f8f7059920b88e5d0605b Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Thu, 25 Dec 2025 10:44:03 -0800 Subject: [PATCH 18/19] chore: remove useless consts --- src/py/mat3ra/api_client/client.py | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/py/mat3ra/api_client/client.py b/src/py/mat3ra/api_client/client.py index 90fcafd..ecbe194 100644 --- a/src/py/mat3ra/api_client/client.py +++ b/src/py/mat3ra/api_client/client.py @@ -19,11 +19,6 @@ DEFAULT_API_VERSION = "2018-10-01" DEFAULT_API_SECURE = True -# Default OIDC Configuration -CLIENT_ID = "default-client" -CLIENT_SECRET = "default-secret" -SCOPE = "openid profile email" - # Environment Variable Names ACCESS_TOKEN_ENV_VAR = "OIDC_ACCESS_TOKEN" API_HOST_ENV_VAR = "API_HOST" @@ -33,23 +28,14 @@ ACCOUNT_ID_ENV_VAR = "ACCOUNT_ID" AUTH_TOKEN_ENV_VAR = "AUTH_TOKEN" -# Protocol Constants -PROTOCOL_HTTPS = "https" -PROTOCOL_HTTP = "http" +# Default OIDC Configuration +CLIENT_ID = "default-client" +CLIENT_SECRET = "default-secret" +SCOPE = "openid profile email" # API Paths USERS_ME_PATH = "/api/v1/users/me" -# JSON Response Keys -JSON_KEY_DATA = "data" -JSON_KEY_USER = "user" -JSON_KEY_ENTITY = "entity" -JSON_KEY_DEFAULT_ACCOUNT_ID = "defaultAccountId" - -# Error Messages -ERROR_MISSING_AUTH = "Missing auth. Provide OIDC_ACCESS_TOKEN or ACCOUNT_ID and AUTH_TOKEN." -ERROR_NO_ACCOUNT_ID_OR_TOKEN = "ACCOUNT_ID is not set and no OIDC access token is available." - class AuthContext(BaseModel): access_token: Optional[str] = None @@ -93,7 +79,7 @@ def id(self) -> str: def _build_users_me_url(host: str, port: int, secure: bool) -> str: - protocol = PROTOCOL_HTTPS if secure else PROTOCOL_HTTP + protocol = "https" if secure else "http" port_str = f":{port}" if port not in [80, 443] else "" return f"{protocol}://{host}{port_str}{USERS_ME_PATH}" @@ -198,7 +184,7 @@ def _validate_auth(auth: AuthContext) -> None: return if auth.account_id and auth.auth_token: return - raise ValueError(ERROR_MISSING_AUTH) + raise ValueError("Missing auth. Provide OIDC_ACCESS_TOKEN or ACCOUNT_ID and AUTH_TOKEN.") @classmethod def authenticate( @@ -228,12 +214,12 @@ def _resolve_account_id(self) -> str: access_token = self.auth.access_token or os.environ.get(ACCESS_TOKEN_ENV_VAR) if not access_token: - raise ValueError(ERROR_NO_ACCOUNT_ID_OR_TOKEN) + raise ValueError("ACCOUNT_ID is not set and no OIDC access token is available.") url = _build_users_me_url(self.host, self.port, self.secure) response = requests.get(url, headers={"Authorization": f"Bearer {access_token}"}, timeout=30) response.raise_for_status() - account_id = response.json()[JSON_KEY_DATA][JSON_KEY_USER][JSON_KEY_ENTITY][JSON_KEY_DEFAULT_ACCOUNT_ID] + account_id = response.json()["data"]["user"]["entity"]["defaultAccountId"] os.environ[ACCOUNT_ID_ENV_VAR] = account_id self.auth.account_id = account_id return account_id From 62c09aa4a9eaa4df0d0968f2077f21292d5b7306 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Thu, 25 Dec 2025 10:53:22 -0800 Subject: [PATCH 19/19] chore: reduce not needed --- src/py/mat3ra/api_client/client.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/py/mat3ra/api_client/client.py b/src/py/mat3ra/api_client/client.py index ecbe194..2aefd98 100644 --- a/src/py/mat3ra/api_client/client.py +++ b/src/py/mat3ra/api_client/client.py @@ -78,17 +78,14 @@ def id(self) -> str: return self.id_cache -def _build_users_me_url(host: str, port: int, secure: bool) -> str: +def _build_base_url(host: str, port: int, secure: bool, path: str) -> str: protocol = "https" if secure else "http" - port_str = f":{port}" if port not in [80, 443] else "" - return f"{protocol}://{host}{port_str}{USERS_ME_PATH}" - - -def _build_oidc_base_url(host: str, port: int, secure: bool) -> str: - protocol = PROTOCOL_HTTPS if secure else PROTOCOL_HTTP - port_str = f":{port}" if port not in [80, 443] else "" - return f"{protocol}://{host}{port_str}/oidc" + port_str = f":{port}" if port not in (80, 443) else "" + return f"{protocol}://{host}{port_str}{path}" +# Used in API-examples utils +def build_oidc_base_url(host: str, port: int, secure: bool) -> str: + return _build_base_url(host, port, secure, "/oidc") class APIClient(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow", validate_assignment=True) @@ -216,7 +213,7 @@ def _resolve_account_id(self) -> str: if not access_token: raise ValueError("ACCOUNT_ID is not set and no OIDC access token is available.") - url = _build_users_me_url(self.host, self.port, self.secure) + url = _build_base_url(self.host, self.port, self.secure, USERS_ME_PATH) response = requests.get(url, headers={"Authorization": f"Bearer {access_token}"}, timeout=30) response.raise_for_status() account_id = response.json()["data"]["user"]["entity"]["defaultAccountId"]