diff --git a/.gitignore b/.gitignore index 4d7edc6..ac30858 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,4 @@ result # for local dev might dump cred stuff here cred/ +local/ diff --git a/pyproject.toml b/pyproject.toml index 0655e8a..ad1d5cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,11 +8,12 @@ dependencies = [ "requests>=2.31.0", "dacite>=1.9.2", "tomli>=2.2.1", + "types-requests>=2.32.4.20250913", ] [project.scripts] hello = "hello_world:main" -taco = "trygo_py_cliclient.cli:main" +taiga = "taiga_pycli.cli:main" [build-system] requires = ["hatchling"] @@ -46,6 +47,16 @@ junit_family = "xunit1" log_format = "%(asctime)s | %(levelname)s | %(pathname)s:%(lineno)d | %(message)s" log_level = "WARNING" +[tool.pyright] +executionEnvironments = [ + { root = "src" } +] +exclude = [ + ".venv" +] +venvPath = "." +venv = ".venv" + # [tool.mypy] # If you need this # plugins = "numpy.typing.mypy_plugin" diff --git a/scripts/simple_create_user.sh b/scripts/simple_create_user.sh index 120b58e..d6a1bf7 100755 --- a/scripts/simple_create_user.sh +++ b/scripts/simple_create_user.sh @@ -9,4 +9,4 @@ banner() { # utility script for easy testing -uv run taco register --display-name "Display Test" --email "test@example.com" --password "test" +uv run taiga register --display-name "Display Test" --email "test@example.com" --password "test" diff --git a/src/taiga_pycli/cli/__init__.py b/src/taiga_pycli/cli/__init__.py index 16d5072..be12164 100644 --- a/src/taiga_pycli/cli/__init__.py +++ b/src/taiga_pycli/cli/__init__.py @@ -1,17 +1,18 @@ import argparse import pathlib import logging -import trygo_py_cliclient.config -import trygo_py_cliclient.cli.common -import trygo_py_cliclient.cli.register -import trygo_py_cliclient.cli.login +import taiga_pycli.config +import taiga_pycli.cli.common +import taiga_pycli.cli.register +import taiga_pycli.cli.login +import taiga_pycli.cli.hats _logger = logging.getLogger(__name__) def parse_args(): parser = argparse.ArgumentParser( - "trygo-client", formatter_class=argparse.ArgumentDefaultsHelpFormatter + "taiga_pycli", formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument( @@ -19,8 +20,9 @@ def parse_args(): ) subparsers = parser.add_subparsers(dest="cmd", required=True) - trygo_py_cliclient.cli.register.setup_parser(subparsers) - trygo_py_cliclient.cli.login.setup_parser(subparsers) + taiga_pycli.cli.register.setup_parser(subparsers) + taiga_pycli.cli.login.setup_parser(subparsers) + taiga_pycli.cli.hats.setup_parser(subparsers) args = parser.parse_args() return args @@ -29,9 +31,9 @@ def parse_args(): def main(): args = parse_args() - config = trygo_py_cliclient.config.read_config(pathlib.Path(args.config_file)) + config = taiga_pycli.config.read_config(pathlib.Path(args.config_file)) - trygo_py_cliclient.cli.common.set_up_logging( + taiga_pycli.cli.common.set_up_logging( config, ) _logger.info(f"Got args {args=}") diff --git a/src/taiga_pycli/cli/common/__init__.py b/src/taiga_pycli/cli/common/__init__.py index b7de9e9..d1a9b79 100644 --- a/src/taiga_pycli/cli/common/__init__.py +++ b/src/taiga_pycli/cli/common/__init__.py @@ -1,11 +1,11 @@ -import trygo_py_cliclient.config +import taiga_pycli.config import typing import logging import pathlib def set_up_logging( - config: trygo_py_cliclient.config.Config, + config: taiga_pycli.config.Config, create_logfile_parents: bool = True, ): # for convenience diff --git a/src/taiga_pycli/cli/hats.py b/src/taiga_pycli/cli/hats.py new file mode 100644 index 0000000..52338ec --- /dev/null +++ b/src/taiga_pycli/cli/hats.py @@ -0,0 +1,66 @@ +import argparse +import logging +import typing +import taiga_pycli.config +import taiga_pycli.models + +import taiga_pycli.service + +_logger = logging.getLogger(__name__) + + +if typing.TYPE_CHECKING: + _SubparserType = argparse._SubParsersAction[argparse.ArgumentParser] +else: + _SubparserType = typing.Any + + +def setup_parser(subparsers: _SubparserType) -> None: + parser = subparsers.add_parser("hat") + + parser.add_argument( + "--name", type=str, help="The name of the hat to add", default=None + ) + parser.add_argument( + "--description", + type=str, + help="The description of the hat to add", + default=None, + ) + + parser.set_defaults(func=run) + + +def run(cfg: taiga_pycli.config.Config, args): + # clnt = taiga_pycli.client.TryGoAPIClient("http://localhost:8080/") + + backend = taiga_pycli.service.BackendService(cfg) + + if args.name is not None: + if args.description is None: + _logger.error("Got a null description, exiting") + return + # both not None + response = backend.add_hat(args.name, args.description) + return + + else: + _logger.debug("Not creating, just list") + if args.description is not None: + _logger.error("Provided a description without name") + + response = backend.get_hats() + if response is None: + _logger.warning("none response here") + return + real_hats = [] + for hat in response: + rh = taiga_pycli.models.Hat( + name=hat["name"], + description=hat["description"], + user_id=hat["user_id"], + ) + real_hats.append(rh) + print(rh) + # _logger.info(response) + return diff --git a/src/taiga_pycli/cli/login.py b/src/taiga_pycli/cli/login.py index 866d6ae..544a04d 100644 --- a/src/taiga_pycli/cli/login.py +++ b/src/taiga_pycli/cli/login.py @@ -1,9 +1,10 @@ import argparse import logging import typing +import getpass -import trygo_py_cliclient.config -import trygo_py_cliclient.client +import taiga_pycli.config +import taiga_pycli.service _logger = logging.getLogger(__name__) @@ -14,16 +15,38 @@ else: _SubparserType = typing.Any +class Password: + DEFAULT = "Prompt if not provided" + + def __init__(self, value): + if value == self.DEFAULT: + value = getpass.getpass("Password: ") + self.value = value + + def __str__(self): + return self.value + + def setup_parser(subparsers: _SubparserType) -> None: parser = subparsers.add_parser("login") - parser.add_argument("--email", type=str, help="The email to log in with ", default="") - parser.add_argument("--password", type=str, help="Password", default="") + parser.add_argument( + "--email", type=str, help="The email to log in with ", default=None + ) + parser.add_argument( + "--password", type=Password, help="Password", default=Password.DEFAULT + ) parser.set_defaults(func=run) -def run(config: trygo_py_cliclient.config.Config, args): - clnt = trygo_py_cliclient.client.BackendService(config) +def run(config: taiga_pycli.config.Config, args): + clnt = taiga_pycli.service.BackendService(config) - response = clnt.login(args.email, args.password) - _logger.info(response) + _logger.info(f"using password {args.password}") + email_to_use = args.email + if args.email is None: + email_to_use = config.general_config.backend_user + + response = clnt.login(email_to_use, str(args.password)) + # _logger.info(response) + return diff --git a/src/taiga_pycli/cli/register.py b/src/taiga_pycli/cli/register.py index a348840..d7421d8 100644 --- a/src/taiga_pycli/cli/register.py +++ b/src/taiga_pycli/cli/register.py @@ -1,8 +1,9 @@ import argparse import logging import typing +import taiga_pycli.config -import trygo_py_cliclient.client +import taiga_pycli.service _logger = logging.getLogger(__name__) @@ -24,7 +25,9 @@ def setup_parser(subparsers: _SubparserType) -> None: parser.set_defaults(func=run) -def run(args): - clnt = trygo_py_cliclient.client.TryGoAPIClient("http://localhost:8080/") - response = clnt.register(args.email, args.password, args.display_name) +def run(cfg: taiga_pycli.config.Config, args): + # clnt = taiga_pycli.client.TryGoAPIClient("http://localhost:8080/") + + backend = taiga_pycli.service.BackendService(cfg) + response = backend.register(args.email, args.password, args.display_name) _logger.info(response) diff --git a/src/taiga_pycli/config/__init__.py b/src/taiga_pycli/config/__init__.py index 38be7d3..cfba530 100644 --- a/src/taiga_pycli/config/__init__.py +++ b/src/taiga_pycli/config/__init__.py @@ -1,9 +1,9 @@ -from trygo_py_cliclient.config.config import ( +from taiga_pycli.config.config import ( GeneralConfig, Config, ) -from trygo_py_cliclient.config.config_reader import ( +from taiga_pycli.config.config_reader import ( read_config, ) diff --git a/src/taiga_pycli/config/config_reader.py b/src/taiga_pycli/config/config_reader.py index d8fe740..37f5056 100644 --- a/src/taiga_pycli/config/config_reader.py +++ b/src/taiga_pycli/config/config_reader.py @@ -3,7 +3,7 @@ import dacite import pathlib import tomli -from trygo_py_cliclient.config import GeneralConfig, Config +from taiga_pycli.config import GeneralConfig, Config _logger = logging.getLogger(__name__) diff --git a/src/taiga_pycli/models.py b/src/taiga_pycli/models.py index ee1085f..96edde0 100644 --- a/src/taiga_pycli/models.py +++ b/src/taiga_pycli/models.py @@ -36,3 +36,12 @@ class AuthResponse: message: str email: Optional[str] = None id: Optional[str] = None + + +@dataclass +class Hat: + """A wearable hat""" + + name: str + description: str + user_id: int diff --git a/src/taiga_pycli/client.py b/src/taiga_pycli/service/__init__.py similarity index 72% rename from src/taiga_pycli/client.py rename to src/taiga_pycli/service/__init__.py index ff8583f..3e680c2 100644 --- a/src/taiga_pycli/client.py +++ b/src/taiga_pycli/service/__init__.py @@ -1,12 +1,14 @@ -import requests import logging from typing import Optional, Dict, Any from dataclasses import asdict from pathlib import Path -from trygo_py_cliclient.models import RegisterRequest, LoginRequest, AuthResponse -from trygo_py_cliclient.config import Config -from trygo_py_cliclient.exceptions import ( +import requests + + +from taiga_pycli.models import RegisterRequest, LoginRequest, AuthResponse +from taiga_pycli.config import Config +from taiga_pycli.exceptions import ( TryGoAPIError, AuthenticationError, ValidationError, @@ -26,6 +28,9 @@ class BackendService: {"Content-Type": "application/json", "User-Agent": "trygo-py-client/0.1.0"} ) + self._token_path = Path("cred") / "token" + self.token = None + def _make_request( self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: @@ -77,7 +82,9 @@ class BackendService: # Parse JSON response try: - return response.json() + rsp_json = response.json() + _logger.debug(rsp_json) + return rsp_json except ValueError as e: raise TryGoAPIError(f"Invalid JSON response: {e}") @@ -141,9 +148,45 @@ class BackendService: _logger.debug("Local authentication cleared") - def _store_credential(self, token: str) -> None: + def get_hats(self) -> Optional[Dict[str, Any]]: + # needs credential - token_path = Path("cred/token") - token_path.parent.mkdir(parents=True,exist_ok=True) - with token_path.open(mode="w") as tokenf: + cred = self._read_credential() + if cred is None: + return None + + _logger.debug("Credential was read") + response = self._make_request("GET", "/hats") + # _logger.debug(response) + return response + + def add_hat(self, name: str, description: str) -> Optional[Dict[str, Any]]: + cred = self._read_credential() + if cred is None: + return None + + _logger.debug("Credential was read") + response = self._make_request( + "POST", "/hats", {"name": name, "description": description} + ) + return response + + def _store_credential(self, token: str) -> None: + self._token_path.parent.mkdir(parents=True, exist_ok=True) + with self._token_path.open(mode="w") as tokenf: tokenf.write(token) + self.token = token + self.session.headers.update({"Authorization": f"Bearer {token}"}) + + def _read_credential(self) -> Optional[str]: + try: + with open(self._token_path) as token_file: + token_in_file = token_file.read() + self.token = token_in_file + self.session.headers.update( + {"Authorization": f"Bearer {token_in_file}"} + ) + return token_in_file + except FileNotFoundError: + _logger.error("No token file found, try logging in") + return None diff --git a/uv.lock b/uv.lock index 7a9512d..f1f1374 100644 --- a/uv.lock +++ b/uv.lock @@ -132,6 +132,7 @@ dependencies = [ { name = "dacite" }, { name = "requests" }, { name = "tomli" }, + { name = "types-requests" }, ] [package.dev-dependencies] @@ -149,6 +150,7 @@ requires-dist = [ { name = "dacite", specifier = ">=1.9.2" }, { name = "requests", specifier = ">=2.31.0" }, { name = "tomli", specifier = ">=2.2.1" }, + { name = "types-requests", specifier = ">=2.32.4.20250913" }, ] [package.metadata.requires-dev] @@ -367,6 +369,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "types-requests" +version = "2.32.4.20250913" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, +] + [[package]] name = "typing-extensions" version = "4.12.2"