hats
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -157,3 +157,4 @@ result
|
|||||||
|
|
||||||
# for local dev might dump cred stuff here
|
# for local dev might dump cred stuff here
|
||||||
cred/
|
cred/
|
||||||
|
local/
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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=}")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
66
src/taiga_pycli/cli/hats.py
Normal file
66
src/taiga_pycli/cli/hats.py
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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__)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
14
uv.lock
generated
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user