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
cred/
local/

View File

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

View File

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

View File

@@ -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=}")

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

14
uv.lock generated
View File

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