terralab-cli 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, Broad Institute
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.1
2
+ Name: terralab-cli
3
+ Version: 0.0.1
4
+ Summary: Command line interface for interacting with the Terra Scientific Pipelines Service, or Teaspoons.
5
+ Author: Terra Scientific Services
6
+ Author-email: teaspoons-developers@broadinstitute.org
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: PyJWT (>=2.9.0,<3.0.0)
15
+ Requires-Dist: click (>=8.1.7,<9.0.0)
16
+ Requires-Dist: oauth2-cli-auth (>=1.5.0,<2.0.0)
17
+ Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
18
+ Requires-Dist: terra-scientific-pipelines-service-api-client (==0.0.0)
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Teaspoons CLI
22
+
23
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=DataBiosphere_terra-scientific-pipelines-service-cli&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=DataBiosphere_terra-scientific-pipelines-service-cli)
24
+
25
+
26
+ ## Python CLI structure
27
+ The CLI code is structured as follows:
28
+ ```
29
+ terra-scientific-pipelines-service-cli
30
+ ├── terralab
31
+ │ └── commands
32
+ │ │ └── __init__.py
33
+ │ │ └── auth_commands.py
34
+ │ │ └── pipelines_commands.py
35
+ │ └── logic
36
+ │ │ └── __init__.py
37
+ │ │ └── auth_logic.py
38
+ │ │ └── pipelines_logic.py
39
+ │ └── __init__.py
40
+ │ └── auth_helper.py
41
+ │ └── cli.py
42
+ │ └── client.py
43
+ │ └── config.py
44
+ │ └── teaspoons
45
+ ├── tests
46
+ │ └── commands
47
+ │ │ └── test_auth_commands.py
48
+ │ │ └── test_pipelines_commands.py
49
+ │ └── logic
50
+ │ │ └── test_auth_logic.py
51
+ │ │ └── test_pipelines_logic.py
52
+ │ └── __init__.py
53
+ │ └── auth_helper.py
54
+ │ └── cli.py
55
+ │ └── client.py
56
+ │ └── config.py
57
+ │ └── terralab
58
+ ├── .gitignore
59
+ ├── .terralab-cli-config
60
+ ├── poetry.lock
61
+ ├── pyproject.toml
62
+ ├── README.md
63
+ ```
64
+
65
+ In the `terralab` directory, we have the following files and subdirectories:
66
+ - `auth_helper.py` contains the code for authenticating with the Terra Scientific Pipelines Service (Terra, via b2c).
67
+ - `cli.py` assembles the CLI sub-modules that are defined in `commands/`.
68
+ - `client.py` contains the code for wrapping API calls to the Terra Scientific Pipelines Service.
69
+ - `config.py` contains the code for managing the CLI configuration via environment variables.
70
+ - `terralab` is the entrypoint for the CLI. It contains the main function that is called when the CLI is run.
71
+ - `utils.py` contains utility functions that are used across the CLI.
72
+ - The `commands` directory contains the CLI sub-modules. This is effectively the controller layer for the CLI.
73
+ - The `logic` directory contains the business logic for the CLI, including calling Terra Scientific Pipelines Service APIs via the thin `teaspoons_client`,
74
+ which is autogenerated and published by the [Terra Scientific Pipelines Service repository](https://github.com/DataBiosphere/terra-scientific-pipelines-service).
75
+
76
+ In the `tests` directory, we have test files that can be run with pytest.
77
+
78
+ ## Using the CLI
79
+ For now, the CLI requires poetry to be installed to run. See the [Development](#development) section for instructions on how to install poetry.
80
+
81
+ To run the CLI, navigate to the root (terra-scientific-pipelines-service-cli) directory and run the following command:
82
+ ```bash
83
+ ./terralab COMMAND [ARGS]
84
+ ```
85
+
86
+ For example, to authenticate with Terralab (via Terra b2c), run the following command:
87
+ ```bash
88
+ ./terralab auth login
89
+ ```
90
+
91
+ To list the pipelines you can run using Terralab, run the following command:
92
+ ```bash
93
+ ./terralab pipelines list
94
+ ```
95
+
96
+ See WIP documentation for the CLI [here](https://docs.google.com/document/d/1ovbcHCzdyuC8RjFfkVJZiuDTQ_UAVrglSxSGaZwppoY/edit?tab=t.0#heading=h.jfsr3j3x0zjr).
97
+
98
+
99
+ ## Development
100
+ You'll need to have poetry installed to manage python dependencies. Instructions for installing poetry can be found [here](https://python-poetry.org/docs/).
101
+
102
+ To install the CLI locally, run the following commands from the root project directory:
103
+ ```
104
+ poetry lock # only needed if you updated dependencies in pyproject.toml
105
+ poetry install
106
+ ```
107
+ You do not need to re-run these commands each time you update code locally, unless you've added dependencies in pyproject.toml.
108
+
109
+ If you do update dependencies in `pyproject.toml`, run `poetry lock` and check in the resulting changes to `poetry.lock` along with the rest of
110
+ your code changes.
111
+
112
+ To run the tests, execute the following command from the root project (terra-scientific-pipelines-service-cli) directory:
113
+ ```bash
114
+ poetry run pytest
115
+ ```
116
+
117
+ To run tests with a coverage report printed to the terminal:
118
+ ```bash
119
+ poetry run pytest --cov-report term --cov=terralab
120
+ ```
121
+
122
+ To run the formatter, execute the following command from the root project directory:
123
+ ```bash
124
+ poetry run black .
125
+ ```
126
+
127
+ To run the linter with fixes, execute the following command from the root project directory:
128
+ ```bash
129
+ poetry run ruff check --fix
130
+ ```
131
+ To run the linter as a check without fixes, omit the `--fix` flag.
132
+
@@ -0,0 +1,111 @@
1
+ # Teaspoons CLI
2
+
3
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=DataBiosphere_terra-scientific-pipelines-service-cli&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=DataBiosphere_terra-scientific-pipelines-service-cli)
4
+
5
+
6
+ ## Python CLI structure
7
+ The CLI code is structured as follows:
8
+ ```
9
+ terra-scientific-pipelines-service-cli
10
+ ├── terralab
11
+ │ └── commands
12
+ │ │ └── __init__.py
13
+ │ │ └── auth_commands.py
14
+ │ │ └── pipelines_commands.py
15
+ │ └── logic
16
+ │ │ └── __init__.py
17
+ │ │ └── auth_logic.py
18
+ │ │ └── pipelines_logic.py
19
+ │ └── __init__.py
20
+ │ └── auth_helper.py
21
+ │ └── cli.py
22
+ │ └── client.py
23
+ │ └── config.py
24
+ │ └── teaspoons
25
+ ├── tests
26
+ │ └── commands
27
+ │ │ └── test_auth_commands.py
28
+ │ │ └── test_pipelines_commands.py
29
+ │ └── logic
30
+ │ │ └── test_auth_logic.py
31
+ │ │ └── test_pipelines_logic.py
32
+ │ └── __init__.py
33
+ │ └── auth_helper.py
34
+ │ └── cli.py
35
+ │ └── client.py
36
+ │ └── config.py
37
+ │ └── terralab
38
+ ├── .gitignore
39
+ ├── .terralab-cli-config
40
+ ├── poetry.lock
41
+ ├── pyproject.toml
42
+ ├── README.md
43
+ ```
44
+
45
+ In the `terralab` directory, we have the following files and subdirectories:
46
+ - `auth_helper.py` contains the code for authenticating with the Terra Scientific Pipelines Service (Terra, via b2c).
47
+ - `cli.py` assembles the CLI sub-modules that are defined in `commands/`.
48
+ - `client.py` contains the code for wrapping API calls to the Terra Scientific Pipelines Service.
49
+ - `config.py` contains the code for managing the CLI configuration via environment variables.
50
+ - `terralab` is the entrypoint for the CLI. It contains the main function that is called when the CLI is run.
51
+ - `utils.py` contains utility functions that are used across the CLI.
52
+ - The `commands` directory contains the CLI sub-modules. This is effectively the controller layer for the CLI.
53
+ - The `logic` directory contains the business logic for the CLI, including calling Terra Scientific Pipelines Service APIs via the thin `teaspoons_client`,
54
+ which is autogenerated and published by the [Terra Scientific Pipelines Service repository](https://github.com/DataBiosphere/terra-scientific-pipelines-service).
55
+
56
+ In the `tests` directory, we have test files that can be run with pytest.
57
+
58
+ ## Using the CLI
59
+ For now, the CLI requires poetry to be installed to run. See the [Development](#development) section for instructions on how to install poetry.
60
+
61
+ To run the CLI, navigate to the root (terra-scientific-pipelines-service-cli) directory and run the following command:
62
+ ```bash
63
+ ./terralab COMMAND [ARGS]
64
+ ```
65
+
66
+ For example, to authenticate with Terralab (via Terra b2c), run the following command:
67
+ ```bash
68
+ ./terralab auth login
69
+ ```
70
+
71
+ To list the pipelines you can run using Terralab, run the following command:
72
+ ```bash
73
+ ./terralab pipelines list
74
+ ```
75
+
76
+ See WIP documentation for the CLI [here](https://docs.google.com/document/d/1ovbcHCzdyuC8RjFfkVJZiuDTQ_UAVrglSxSGaZwppoY/edit?tab=t.0#heading=h.jfsr3j3x0zjr).
77
+
78
+
79
+ ## Development
80
+ You'll need to have poetry installed to manage python dependencies. Instructions for installing poetry can be found [here](https://python-poetry.org/docs/).
81
+
82
+ To install the CLI locally, run the following commands from the root project directory:
83
+ ```
84
+ poetry lock # only needed if you updated dependencies in pyproject.toml
85
+ poetry install
86
+ ```
87
+ You do not need to re-run these commands each time you update code locally, unless you've added dependencies in pyproject.toml.
88
+
89
+ If you do update dependencies in `pyproject.toml`, run `poetry lock` and check in the resulting changes to `poetry.lock` along with the rest of
90
+ your code changes.
91
+
92
+ To run the tests, execute the following command from the root project (terra-scientific-pipelines-service-cli) directory:
93
+ ```bash
94
+ poetry run pytest
95
+ ```
96
+
97
+ To run tests with a coverage report printed to the terminal:
98
+ ```bash
99
+ poetry run pytest --cov-report term --cov=terralab
100
+ ```
101
+
102
+ To run the formatter, execute the following command from the root project directory:
103
+ ```bash
104
+ poetry run black .
105
+ ```
106
+
107
+ To run the linter with fixes, execute the following command from the root project directory:
108
+ ```bash
109
+ poetry run ruff check --fix
110
+ ```
111
+ To run the linter as a check without fixes, omit the `--fix` flag.
@@ -0,0 +1,71 @@
1
+ [tool.poetry]
2
+ name = "terralab-cli"
3
+ version = "0.0.1"
4
+ description = "Command line interface for interacting with the Terra Scientific Pipelines Service, or Teaspoons."
5
+ authors = ["Terra Scientific Services <teaspoons-developers@broadinstitute.org>"]
6
+ readme = "README.md"
7
+ packages = [
8
+ {include = "terralab"}
9
+ ]
10
+
11
+ [tool.poetry.dependencies]
12
+ python = "^3.9"
13
+ terra-scientific-pipelines-service-api-client = "0.0.0"
14
+ python-dotenv = "^1.0.1"
15
+ click = "^8.1.7"
16
+ oauth2-cli-auth = "^1.5.0"
17
+ PyJWT = "^2.9.0"
18
+
19
+ [tool.poetry.group.dev.dependencies]
20
+ pytest = "^8.3.3"
21
+ pytest-cov = "^3.0.0"
22
+ mockito = ">=1.4.0"
23
+ pytest-mockito = ">=0.0.4"
24
+ ruff = "^0.7.1"
25
+ black = "^24.8.0"
26
+
27
+ [build-system]
28
+ requires = ["poetry-core"]
29
+ build-backend = "poetry.core.masonry.api"
30
+
31
+ [tool.pytest.ini_options]
32
+ pythonpath = "terralab"
33
+
34
+ [tool.coverage.run]
35
+ branch = true
36
+ relative_files = true # allows sonarcloud GHA step to process the coverage files
37
+
38
+ [tool.ruff]
39
+ # Exclude a variety of commonly ignored directories.
40
+ exclude = [
41
+ ".bzr",
42
+ ".direnv",
43
+ ".eggs",
44
+ ".git",
45
+ ".git-rewrite",
46
+ ".hg",
47
+ ".ipynb_checkpoints",
48
+ ".mypy_cache",
49
+ ".nox",
50
+ ".pants.d",
51
+ ".pyenv",
52
+ ".pytest_cache",
53
+ ".pytype",
54
+ ".ruff_cache",
55
+ ".svn",
56
+ ".tox",
57
+ ".venv",
58
+ ".vscode",
59
+ "__pypackages__",
60
+ "_build",
61
+ "buck-out",
62
+ "build",
63
+ "dist",
64
+ "node_modules",
65
+ "site-packages",
66
+ "venv",
67
+ "generated"
68
+ ]
69
+ # Same as Black.
70
+ line-length = 88
71
+ indent-width = 4
@@ -0,0 +1,10 @@
1
+ """
2
+ Terralab: A CLI for the Terra Scientific Pipelines Service (Teaspoons).
3
+
4
+ This package provides a command-line interface to interact with the Teaspoons service,
5
+ built on top of an autogenerated thin client.
6
+ """
7
+
8
+ __version__ = "0.0.1"
9
+ __author__ = "Terra Scientific Services"
10
+ __email__ = "teaspoons-developers@broadinstitute.org"
@@ -0,0 +1,89 @@
1
+ # auth_helper.py
2
+
3
+ import jwt
4
+ import logging
5
+ import os
6
+ import typing as t
7
+
8
+ from oauth2_cli_auth import (
9
+ OAuth2ClientInfo,
10
+ OAuthCallbackHttpServer,
11
+ get_auth_url,
12
+ open_browser,
13
+ exchange_code_for_access_token,
14
+ )
15
+
16
+ from config import CliConfig
17
+
18
+
19
+ LOGGER = logging.getLogger(__name__)
20
+
21
+
22
+ def get_access_token_with_browser_open(client_info: OAuth2ClientInfo) -> str:
23
+ """
24
+ Note: this is overridden from the oauth2-cli-auth library to use a custom auth url
25
+
26
+ Provides a simplified API to:
27
+
28
+ - Spin up the callback server
29
+ - Open the browser with the authorization URL
30
+ - Wait for the code to arrive
31
+ - Get access token from code
32
+
33
+ :param client_info: Client Info for Oauth2 Interaction
34
+ :param server_port: Port of the local web server to spin up
35
+ :return: Access Token
36
+ """
37
+ server_port = CliConfig().server_port
38
+ callback_server = OAuthCallbackHttpServer(server_port)
39
+ auth_url = get_auth_url(client_info, callback_server.callback_url)
40
+ open_browser(f"{auth_url}&prompt=login")
41
+ code = callback_server.wait_for_code()
42
+ if code is None:
43
+ raise ValueError("No code could be obtained from browser callback page")
44
+ return exchange_code_for_access_token(
45
+ client_info, callback_server.callback_url, code
46
+ )
47
+
48
+
49
+ def _validate_token(token: str) -> bool:
50
+ try:
51
+ # Attempt to read the token to ensure it is valid. If it isn't, the file will be removed and None will be returned.
52
+ # Note: to simplify, not worrying about the signature of the token since that will be verified by the backend services
53
+ # This is just to ensure the token is not expired
54
+ jwt.decode(token, options={"verify_signature": False, "verify_exp": True})
55
+ return True
56
+ except jwt.ExpiredSignatureError:
57
+ LOGGER.debug("Token expired")
58
+ return False
59
+ except Exception as e:
60
+ LOGGER.error(f"Error validating token: {e}")
61
+ return False
62
+
63
+
64
+ def _clear_local_token(token_file: str):
65
+ try:
66
+ os.remove(token_file)
67
+ except FileNotFoundError:
68
+ LOGGER.debug("No local token found to clean up")
69
+
70
+
71
+ def _load_local_token(token_file: str) -> t.Optional[str]:
72
+ try:
73
+ with open(token_file, "r") as f:
74
+ token = f.read()
75
+ if _validate_token(token):
76
+ return token
77
+ else:
78
+ return None
79
+
80
+ except FileNotFoundError:
81
+ _clear_local_token(token_file)
82
+ return None
83
+
84
+
85
+ def _save_local_token(token_file: str, token: str):
86
+ # Create the containing directory if it doesn't exist
87
+ os.makedirs(os.path.dirname(token_file), exist_ok=True)
88
+ with open(token_file, "w") as f:
89
+ f.write(token)
@@ -0,0 +1,37 @@
1
+ # cli.py
2
+
3
+ import click
4
+ import logging
5
+
6
+ from terralab import __version__, log
7
+ from terralab.commands.auth_commands import auth
8
+ from terralab.commands.pipelines_commands import pipelines
9
+
10
+
11
+ # Context settings for commands, for overwriting some click defaults
12
+ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
13
+
14
+ LOGGER = logging.getLogger(__name__)
15
+
16
+
17
+ @click.group(name="terralab", context_settings=CONTEXT_SETTINGS)
18
+ @click.version_option(__version__)
19
+ @click.option(
20
+ "--debug",
21
+ is_flag=True,
22
+ help="DEBUG-level logging",
23
+ )
24
+ def cli(debug):
25
+ log.configure_logging(debug)
26
+ LOGGER.debug(
27
+ "Log level set to: %s", logging.getLevelName(logging.getLogger().level)
28
+ )
29
+
30
+
31
+ cli.add_command(auth)
32
+ cli.add_command(pipelines)
33
+ # will add runs_app later
34
+
35
+
36
+ if __name__ == "__main__":
37
+ cli()
@@ -0,0 +1,43 @@
1
+ # client.py
2
+
3
+ import logging
4
+
5
+ from teaspoons_client import Configuration, ApiClient
6
+ from config import CliConfig
7
+ from auth_helper import (
8
+ _load_local_token,
9
+ _validate_token,
10
+ _save_local_token,
11
+ get_access_token_with_browser_open,
12
+ )
13
+
14
+
15
+ LOGGER = logging.getLogger(__name__)
16
+
17
+
18
+ def _get_api_client(token: str, api_url: str) -> ApiClient:
19
+ api_config = Configuration()
20
+ api_config.host = api_url
21
+ api_config.access_token = token
22
+ return ApiClient(configuration=api_config)
23
+
24
+
25
+ class ClientWrapper:
26
+ """
27
+ Wrapper to ensure that the user is authenticated before running the callback and that provides the low level api client to be used
28
+ by subsequent commands
29
+ """
30
+
31
+ def __enter__(self):
32
+ cli_config = CliConfig() # initialize the config from environment variables
33
+ token = _load_local_token(cli_config.token_file)
34
+ if not (token and _validate_token(token)):
35
+ LOGGER.info("No valid token found. Logging you in...")
36
+ token = get_access_token_with_browser_open(cli_config.client_info)
37
+ _save_local_token(cli_config.token_file, token)
38
+
39
+ return _get_api_client(token, cli_config.config["TEASPOONS_API_URL"])
40
+
41
+ def __exit__(self, exc_type, exc_val, exc_tb):
42
+ # no action needed
43
+ pass
File without changes
@@ -0,0 +1,22 @@
1
+ # commands/auth_commands.py
2
+
3
+ import click
4
+
5
+ from logic import auth_logic
6
+
7
+
8
+ @click.group()
9
+ def auth():
10
+ """Commands for authenticating to Terralab"""
11
+
12
+
13
+ @auth.command()
14
+ def login():
15
+ """Authenticate with Terralab via browser login to Terra b2c"""
16
+ auth_logic.check_local_token_and_fetch_if_needed()
17
+
18
+
19
+ @auth.command()
20
+ def logout():
21
+ """Clear the local authentication token"""
22
+ auth_logic.clear_local_token()
@@ -0,0 +1,35 @@
1
+ # commands/pipelines_commands.py
2
+
3
+ import click
4
+ import logging
5
+
6
+ from logic import pipelines_logic
7
+ from utils import _pretty_print, handle_api_exceptions
8
+
9
+ LOGGER = logging.getLogger(__name__)
10
+
11
+
12
+ @click.group()
13
+ def pipelines():
14
+ """Commands for running Teaspooons pipelines"""
15
+
16
+
17
+ @pipelines.command()
18
+ @handle_api_exceptions
19
+ def list():
20
+ """List all available pipelines"""
21
+ pipelines_list = pipelines_logic.list_pipelines()
22
+ LOGGER.info(
23
+ f"Found {len(pipelines_list)} available pipeline{'' if len(pipelines_list) == 1 else 's'}:"
24
+ )
25
+ for pipeline in pipelines_list:
26
+ LOGGER.info(f"- {pipeline.pipeline_name} ({pipeline.description})")
27
+
28
+
29
+ @pipelines.command()
30
+ @click.argument("pipeline_name")
31
+ @handle_api_exceptions
32
+ def get_info(pipeline_name: str):
33
+ """Get information about a specific pipeline"""
34
+ pipeline_info = pipelines_logic.get_pipeline_info(pipeline_name)
35
+ _pretty_print(pipeline_info)
@@ -0,0 +1,24 @@
1
+ # config.py
2
+
3
+ from pathlib import Path
4
+ from oauth2_cli_auth import OAuth2ClientInfo
5
+ from dotenv import dotenv_values
6
+
7
+
8
+ class CliConfig:
9
+ """A class to hold configuration information for the CLI"""
10
+
11
+ def __init__(self, config_file="../.terralab-cli-config"):
12
+ self.config = dotenv_values(config_file)
13
+
14
+ self.client_info = OAuth2ClientInfo.from_oidc_endpoint(
15
+ self.config["OAUTH_OPENID_CONFIGURATION_URI"],
16
+ client_id=self.config["OAUTH_CLIENT_ID"],
17
+ scopes=[f"openid+email+profile+{self.config['OAUTH_CLIENT_ID']}"],
18
+ )
19
+
20
+ self.server_port = int(self.config["SERVER_PORT"])
21
+
22
+ self.token_file = (
23
+ f'{Path.home()}/{self.config["LOCAL_STORAGE_PATH"]}/access_token'
24
+ )
@@ -0,0 +1,8 @@
1
+ # log.py
2
+
3
+ import logging
4
+
5
+
6
+ def configure_logging(debug: bool):
7
+ log_level = logging.DEBUG if debug else logging.INFO
8
+ logging.basicConfig(level=log_level, format="%(message)s")
File without changes
@@ -0,0 +1,31 @@
1
+ # logic/auth_logic.py
2
+
3
+ import logging
4
+ from auth_helper import (
5
+ get_access_token_with_browser_open,
6
+ _validate_token,
7
+ _save_local_token,
8
+ _load_local_token,
9
+ _clear_local_token,
10
+ )
11
+ from config import CliConfig
12
+
13
+ LOGGER = logging.getLogger(__name__)
14
+
15
+
16
+ def check_local_token_and_fetch_if_needed():
17
+ """Authenticate with Teaspoons via browser login to Terra b2c"""
18
+ cli_config = CliConfig() # initialize the config from environment variables
19
+ token = _load_local_token(cli_config.token_file)
20
+ if token and _validate_token(token):
21
+ LOGGER.info("Already authenticated")
22
+ return
23
+ token = get_access_token_with_browser_open(cli_config.client_info)
24
+ _save_local_token(cli_config.token_file, token)
25
+
26
+
27
+ def clear_local_token():
28
+ """Clear the local authentication token"""
29
+ cli_config = CliConfig() # initialize the config from environment variables
30
+ _clear_local_token(cli_config.token_file)
31
+ LOGGER.info("Logged out")
@@ -0,0 +1,27 @@
1
+ # logic/pipelines_logic.py
2
+
3
+ from teaspoons_client import PipelinesApi
4
+ from teaspoons_client.models.pipeline_with_details import PipelineWithDetails
5
+ from teaspoons_client.models.pipeline import Pipeline
6
+
7
+ from client import ClientWrapper
8
+
9
+
10
+ def list_pipelines() -> list[Pipeline]:
11
+ """List all pipelines, returning a list of dictionaries."""
12
+ with ClientWrapper() as api_client:
13
+ pipeline_client = PipelinesApi(api_client=api_client)
14
+ pipelines = pipeline_client.get_pipelines()
15
+
16
+ result = []
17
+ for pipeline in pipelines.results:
18
+ result.append(pipeline)
19
+
20
+ return result
21
+
22
+
23
+ def get_pipeline_info(pipeline_name: str) -> PipelineWithDetails:
24
+ """Get the details of a pipeline, returning a dictionary."""
25
+ with ClientWrapper() as api_client:
26
+ pipeline_client = PipelinesApi(api_client=api_client)
27
+ return pipeline_client.get_pipeline_details(pipeline_name=pipeline_name)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env poetry run python
2
+
3
+ from cli import cli
4
+
5
+ if __name__ == '__main__':
6
+ cli()
@@ -0,0 +1,38 @@
1
+ # utils.py
2
+
3
+ import json
4
+ import logging
5
+
6
+ from functools import wraps
7
+ from pydantic import BaseModel
8
+
9
+ from teaspoons_client.exceptions import ApiException
10
+
11
+
12
+ LOGGER = logging.getLogger(__name__)
13
+
14
+
15
+ def _pretty_print(obj: BaseModel):
16
+ """
17
+ Prints a pydantic model in a pretty format to the console
18
+ """
19
+ try:
20
+ LOGGER.info(json.dumps(obj.model_dump(), indent=4))
21
+ except Exception:
22
+ LOGGER.error(obj)
23
+
24
+
25
+ def handle_api_exceptions(func):
26
+ @wraps(func)
27
+ def wrapper(*args, **kwargs):
28
+ try:
29
+ return func(*args, **kwargs)
30
+ except ApiException as e:
31
+ formatted_message = f"API call failed with status code {e.status} ({e.reason}): {json.loads(e.body)['message']}"
32
+ LOGGER.error(formatted_message)
33
+ exit(1)
34
+ except Exception as e:
35
+ LOGGER.error(str(e))
36
+ exit(1)
37
+
38
+ return wrapper