This commit is contained in:
2025-10-26 18:20:46 -05:00
parent f516e4f8e5
commit f847a0273f
13 changed files with 209 additions and 37 deletions

1
.gitignore vendored
View File

@@ -157,3 +157,4 @@ result
# for local dev might dump cred stuff here # for local dev might dump cred stuff here
cred/ cred/
local/

View File

@@ -8,11 +8,12 @@ dependencies = [
"requests>=2.31.0", "requests>=2.31.0",
"dacite>=1.9.2", "dacite>=1.9.2",
"tomli>=2.2.1", "tomli>=2.2.1",
"types-requests>=2.32.4.20250913",
] ]
[project.scripts] [project.scripts]
hello = "hello_world:main" hello = "hello_world:main"
taco = "trygo_py_cliclient.cli:main" taiga = "taiga_pycli.cli:main"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
@@ -46,6 +47,16 @@ junit_family = "xunit1"
log_format = "%(asctime)s | %(levelname)s | %(pathname)s:%(lineno)d | %(message)s" log_format = "%(asctime)s | %(levelname)s | %(pathname)s:%(lineno)d | %(message)s"
log_level = "WARNING" log_level = "WARNING"
[tool.pyright]
executionEnvironments = [
{ root = "src" }
]
exclude = [
".venv"
]
venvPath = "."
venv = ".venv"
# [tool.mypy] # [tool.mypy]
# If you need this # If you need this
# plugins = "numpy.typing.mypy_plugin" # plugins = "numpy.typing.mypy_plugin"

View File

@@ -9,4 +9,4 @@ banner() {
# utility script for easy testing # 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"

View File

@@ -1,17 +1,18 @@
import argparse import argparse
import pathlib import pathlib
import logging import logging
import trygo_py_cliclient.config import taiga_pycli.config
import trygo_py_cliclient.cli.common import taiga_pycli.cli.common
import trygo_py_cliclient.cli.register import taiga_pycli.cli.register
import trygo_py_cliclient.cli.login import taiga_pycli.cli.login
import taiga_pycli.cli.hats
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
def parse_args(): def parse_args():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
"trygo-client", formatter_class=argparse.ArgumentDefaultsHelpFormatter "taiga_pycli", formatter_class=argparse.ArgumentDefaultsHelpFormatter
) )
parser.add_argument( parser.add_argument(
@@ -19,8 +20,9 @@ def parse_args():
) )
subparsers = parser.add_subparsers(dest="cmd", required=True) subparsers = parser.add_subparsers(dest="cmd", required=True)
trygo_py_cliclient.cli.register.setup_parser(subparsers) taiga_pycli.cli.register.setup_parser(subparsers)
trygo_py_cliclient.cli.login.setup_parser(subparsers) taiga_pycli.cli.login.setup_parser(subparsers)
taiga_pycli.cli.hats.setup_parser(subparsers)
args = parser.parse_args() args = parser.parse_args()
return args return args
@@ -29,9 +31,9 @@ def parse_args():
def main(): def main():
args = parse_args() 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, config,
) )
_logger.info(f"Got args {args=}") _logger.info(f"Got args {args=}")

View File

@@ -1,11 +1,11 @@
import trygo_py_cliclient.config import taiga_pycli.config
import typing import typing
import logging import logging
import pathlib import pathlib
def set_up_logging( def set_up_logging(
config: trygo_py_cliclient.config.Config, config: taiga_pycli.config.Config,
create_logfile_parents: bool = True, create_logfile_parents: bool = True,
): ):
# for convenience # for convenience

View File

@@ -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

View File

@@ -1,9 +1,10 @@
import argparse import argparse
import logging import logging
import typing import typing
import getpass
import trygo_py_cliclient.config import taiga_pycli.config
import trygo_py_cliclient.client import taiga_pycli.service
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -14,16 +15,38 @@ else:
_SubparserType = typing.Any _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: def setup_parser(subparsers: _SubparserType) -> None:
parser = subparsers.add_parser("login") parser = subparsers.add_parser("login")
parser.add_argument("--email", type=str, help="The email to log in with ", default="") parser.add_argument(
parser.add_argument("--password", type=str, help="Password", default="") "--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) parser.set_defaults(func=run)
def run(config: trygo_py_cliclient.config.Config, args): def run(config: taiga_pycli.config.Config, args):
clnt = trygo_py_cliclient.client.BackendService(config) clnt = taiga_pycli.service.BackendService(config)
response = clnt.login(args.email, args.password) _logger.info(f"using password {args.password}")
_logger.info(response) 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

View File

@@ -1,8 +1,9 @@
import argparse import argparse
import logging import logging
import typing import typing
import taiga_pycli.config
import trygo_py_cliclient.client import taiga_pycli.service
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -24,7 +25,9 @@ def setup_parser(subparsers: _SubparserType) -> None:
parser.set_defaults(func=run) parser.set_defaults(func=run)
def run(args): def run(cfg: taiga_pycli.config.Config, args):
clnt = trygo_py_cliclient.client.TryGoAPIClient("http://localhost:8080/") # clnt = taiga_pycli.client.TryGoAPIClient("http://localhost:8080/")
response = clnt.register(args.email, args.password, args.display_name)
backend = taiga_pycli.service.BackendService(cfg)
response = backend.register(args.email, args.password, args.display_name)
_logger.info(response) _logger.info(response)

View File

@@ -1,9 +1,9 @@
from trygo_py_cliclient.config.config import ( from taiga_pycli.config.config import (
GeneralConfig, GeneralConfig,
Config, Config,
) )
from trygo_py_cliclient.config.config_reader import ( from taiga_pycli.config.config_reader import (
read_config, read_config,
) )

View File

@@ -3,7 +3,7 @@ import dacite
import pathlib import pathlib
import tomli import tomli
from trygo_py_cliclient.config import GeneralConfig, Config from taiga_pycli.config import GeneralConfig, Config
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)

View File

@@ -36,3 +36,12 @@ class AuthResponse:
message: str message: str
email: Optional[str] = None email: Optional[str] = None
id: Optional[str] = None id: Optional[str] = None
@dataclass
class Hat:
"""A wearable hat"""
name: str
description: str
user_id: int

View File

@@ -1,12 +1,14 @@
import requests
import logging import logging
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from dataclasses import asdict from dataclasses import asdict
from pathlib import Path from pathlib import Path
from trygo_py_cliclient.models import RegisterRequest, LoginRequest, AuthResponse import requests
from trygo_py_cliclient.config import Config
from trygo_py_cliclient.exceptions import (
from taiga_pycli.models import RegisterRequest, LoginRequest, AuthResponse
from taiga_pycli.config import Config
from taiga_pycli.exceptions import (
TryGoAPIError, TryGoAPIError,
AuthenticationError, AuthenticationError,
ValidationError, ValidationError,
@@ -26,6 +28,9 @@ class BackendService:
{"Content-Type": "application/json", "User-Agent": "trygo-py-client/0.1.0"} {"Content-Type": "application/json", "User-Agent": "trygo-py-client/0.1.0"}
) )
self._token_path = Path("cred") / "token"
self.token = None
def _make_request( def _make_request(
self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None self, method: str, endpoint: str, data: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
@@ -77,7 +82,9 @@ class BackendService:
# Parse JSON response # Parse JSON response
try: try:
return response.json() rsp_json = response.json()
_logger.debug(rsp_json)
return rsp_json
except ValueError as e: except ValueError as e:
raise TryGoAPIError(f"Invalid JSON response: {e}") raise TryGoAPIError(f"Invalid JSON response: {e}")
@@ -141,9 +148,45 @@ class BackendService:
_logger.debug("Local authentication cleared") _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") cred = self._read_credential()
token_path.parent.mkdir(parents=True,exist_ok=True) if cred is None:
with token_path.open(mode="w") as tokenf: 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) 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

14
uv.lock generated
View File

@@ -132,6 +132,7 @@ dependencies = [
{ name = "dacite" }, { name = "dacite" },
{ name = "requests" }, { name = "requests" },
{ name = "tomli" }, { name = "tomli" },
{ name = "types-requests" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@@ -149,6 +150,7 @@ requires-dist = [
{ name = "dacite", specifier = ">=1.9.2" }, { name = "dacite", specifier = ">=1.9.2" },
{ name = "requests", specifier = ">=2.31.0" }, { name = "requests", specifier = ">=2.31.0" },
{ name = "tomli", specifier = ">=2.2.1" }, { name = "tomli", specifier = ">=2.2.1" },
{ name = "types-requests", specifier = ">=2.32.4.20250913" },
] ]
[package.metadata.requires-dev] [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" }, { 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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"