Skip to content

Commit cd17f0c

Browse files
committed
cli config
1 parent 766f222 commit cd17f0c

7 files changed

Lines changed: 162 additions & 20 deletions

File tree

poetry.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ httpx = ">=0.15.4,<0.24.0"
1717
attrs = ">=21.3.0"
1818
python-dateutil = "^2.8.0"
1919
typer = {extras = ["all"], version = "^0.7.0"}
20+
tomlkit = "^0.11.6"
2021

2122
[tool.poetry.group.dev.dependencies]
2223
openapi-python-client = "^0.13.1"

taskbadger/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
from .integrations import Action, EmailIntegration
2-
from .sdk import Task, init, init_from_env, update_task
2+
from .sdk import Task, init, update_task

taskbadger/cli.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,71 @@
11
import subprocess
2-
from typing import List
2+
from typing import Optional
33

44
import typer
5+
from rich import print
56

67
import taskbadger as tb
8+
from taskbadger.config import get_config, write_config
9+
from taskbadger.exceptions import ConfigurationError
710

811
app = typer.Typer()
912

10-
tb.init_from_env()
1113

14+
def _configure_api(ctx):
15+
config = ctx.meta["tb_config"]
16+
try:
17+
config.init_api()
18+
except ConfigurationError as e:
19+
print(f"[red]{str(e)}[/red]")
20+
raise typer.Exit(code=1)
1221

13-
@app.command()
14-
def monitor(name: str, command: List[str] = typer.Argument(None)):
22+
23+
@app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
24+
def monitor(ctx: typer.Context, name: str):
25+
_configure_api(ctx)
1526
task = tb.Task.create(name)
16-
result = subprocess.run(command, env={"TASKBADGER_TASK_ID": task.id})
27+
result = subprocess.run(ctx.args, env={"TASKBADGER_TASK_ID": task.id})
1728
if result.returncode != 0:
1829
task.success()
1930
else:
2031
task.error(data={"return_code": result.returncode})
2132

2233

34+
@app.command()
35+
def configure(ctx: typer.Context):
36+
config = ctx.meta["tb_config"]
37+
config.token = typer.prompt(f"Token", default=config.token)
38+
config.organization_slug = typer.prompt(f"Organization slug", default=config.organization_slug)
39+
config.project_slug = typer.prompt(f"Project slug", default=config.project_slug)
40+
path = write_config(config)
41+
print(f"Config written to [green]{path}[/green]")
42+
43+
44+
@app.command()
45+
def docs():
46+
typer.launch("https://docs.taskbadger.net")
47+
48+
49+
@app.command()
50+
def info(ctx: typer.Context):
51+
config = ctx.meta["tb_config"]
52+
print(f"Default Organization: {config.organization_slug or '-'}")
53+
print(f"Default Project: {config.project_slug or '-'}")
54+
print(f"Auth Token: {config.token or '-'}")
55+
56+
57+
@app.callback()
58+
def main(
59+
ctx: typer.Context,
60+
org: Optional[str] = typer.Option(None, "--org", "-o", metavar="ORG"),
61+
project: Optional[str] = typer.Option(None, "--project", "-p", show_envvar=False, metavar="PROJECT"),
62+
):
63+
"""
64+
Manage users in the awesome CLI app.
65+
"""
66+
config = get_config(org=org, project=project)
67+
ctx.meta["tb_config"] = config
68+
69+
2370
if __name__ == "__main__":
2471
app()

taskbadger/config.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import dataclasses
2+
import os
3+
from pathlib import Path
4+
5+
import tomlkit
6+
import typer
7+
from tomlkit import document, table
8+
9+
import taskbadger as tb
10+
11+
APP_NAME = "taskbadger"
12+
13+
14+
@dataclasses.dataclass
15+
class Config:
16+
token: str = None
17+
organization_slug: str = None
18+
project_slug: str = None
19+
20+
def is_valid(self):
21+
return bool(self.token and self.organization_slug and self.project_slug)
22+
23+
def init_api(self):
24+
tb.init(self.organization_slug, self.project_slug, self.token)
25+
26+
@staticmethod
27+
def from_dict(config_dict, **overrides) -> "Config":
28+
defaults = config_dict.get("defaults", {})
29+
auth = config_dict.get("auth", {})
30+
return Config(
31+
token=overrides.get("token") or _from_env("TOKEN", auth.get("token")),
32+
organization_slug=overrides.get("org") or _from_env("ORG", defaults.get("org")),
33+
project_slug=overrides.get("project") or _from_env("PROJECT", defaults.get("project")),
34+
)
35+
36+
37+
def _from_env(name, default=None, prefix="TASKBADGER_"):
38+
return os.environ.get(f"{prefix}{name}", default)
39+
40+
41+
def write_config(config):
42+
doc = document()
43+
44+
doc.add("defaults", table().add("org", config.organization_slug).add("project", config.project_slug))
45+
46+
doc.add("auth", table().add("token", config.token))
47+
48+
config_path = _get_config_path()
49+
if not config_path.parent.exists():
50+
config_path.parent.mkdir(parents=True)
51+
with config_path.open("wt", encoding="utf-8") as fp:
52+
tomlkit.dump(doc, fp)
53+
54+
return config_path
55+
56+
57+
def get_config(**overrides):
58+
config_dict = {}
59+
config_path = _get_config_path()
60+
if config_path.is_file():
61+
with config_path.open("rt", encoding="utf-8") as fp:
62+
raw_config = tomlkit.load(fp)
63+
config_dict = raw_config.unwrap()
64+
65+
return Config.from_dict(config_dict, **overrides)
66+
67+
68+
def _get_config_path():
69+
app_dir = typer.get_app_dir(APP_NAME)
70+
config_path: Path = Path(app_dir) / "config"
71+
return config_path

taskbadger/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class ConfigurationError(Exception):
2+
def __init__(self, **kwargs):
3+
self.missing = [name for name, arg in kwargs.items() if arg is None]
4+
5+
def __str__(self):
6+
return f"Missing configuration parameters: {', '.join(self.missing)}"

taskbadger/sdk.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from _contextvars import ContextVar
66

77
from taskbadger import Action
8+
from taskbadger.exceptions import ConfigurationError
89
from taskbadger.internal import AuthenticatedClient, errors
910
from taskbadger.internal.api.task_endpoints import task_create, task_get, task_partial_update
1011
from taskbadger.internal.models import PatchedTaskRequest, PatchedTaskRequestData, StatusEnum
@@ -15,23 +16,27 @@
1516
_local = ContextVar("taskbadger_client")
1617

1718

18-
def init_from_env():
19-
_init(
20-
os.environ["TASKBADGER_ORG"],
21-
os.environ["TASKBADGER_PROJECT"],
22-
os.environ["TASKBADGER_TOKEN"],
23-
)
24-
25-
26-
def init(organization_slug: str, project_slug: str, token: str):
19+
def init(organization_slug: str = None, project_slug: str = None, token: str = None):
2720
_init("https://taskbadger.net", organization_slug, project_slug, token)
2821

2922

30-
def _init(host: str, organization_slug: str, project_slug: str, token: str):
31-
client = AuthenticatedClient(host, token)
32-
settings = Settings(client, organization_slug, project_slug)
23+
def _init(host: str = None, organization_slug: str = None, project_slug: str = None, token: str = None):
24+
host = host or os.environ.get("TASKBADGER_HOST", "https://taskbadger.net")
25+
organization_slug = organization_slug or os.environ.get("TASKBADGER_ORG")
26+
project_slug = project_slug or os.environ.get("TASKBADGER_PROJECT")
27+
token = token or os.environ.get("TASKBADGER_TOKEN")
3328

34-
_local.set(settings)
29+
if host and organization_slug and project_slug and token:
30+
client = AuthenticatedClient(host, token)
31+
settings = Settings(client, organization_slug, project_slug)
32+
_local.set(settings)
33+
else:
34+
raise ConfigurationError(
35+
host=host,
36+
organization_slug=organization_slug,
37+
project_slug=project_slug,
38+
token=token,
39+
)
3540

3641

3742
def get_task(task_id: str):

0 commit comments

Comments
 (0)