Skip to content

Commit 5029f6a

Browse files
committed
Refactor CLI
1 parent f75893e commit 5029f6a

12 files changed

+991
-1517
lines changed

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ dependencies = [
2929
"pydantic >=2, <3",
3030
"pydantic-settings >=2, <3",
3131
"pyyaml",
32-
"typer",
3332
]
3433

3534
classifiers = [

tiled/commandline/_admin.py

Lines changed: 11 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -1,193 +1,15 @@
1-
from pathlib import Path
2-
from typing import Optional
1+
from pydantic import BaseModel
2+
from pydantic_settings import CliApp, CliSubCommand
33

4-
import typer
4+
from tiled.commandline._api_key import APIKeys
5+
from tiled.commandline._database import Database
6+
from tiled.commandline._principal import Principals
57

6-
from ._utils import get_context, get_profile # noqa E402
78

8-
admin_app = typer.Typer()
9+
class Admin(BaseModel):
10+
database: CliSubCommand[Database]
11+
api_keys: CliSubCommand[APIKeys]
12+
principals: CliSubCommand[Principals]
913

10-
11-
@admin_app.command("initialize-database")
12-
def initialize_database(database_uri: str):
13-
"""
14-
Initialize a SQL database for use by Tiled.
15-
"""
16-
import asyncio
17-
18-
from sqlalchemy.ext.asyncio import create_async_engine
19-
20-
from ..alembic_utils import UninitializedDatabase, check_database, stamp_head
21-
from ..authn_database.alembic_constants import (
22-
ALEMBIC_DIR,
23-
ALEMBIC_INI_TEMPLATE_PATH,
24-
)
25-
from ..authn_database.core import (
26-
ALL_REVISIONS,
27-
REQUIRED_REVISION,
28-
initialize_database,
29-
)
30-
from ..utils import ensure_specified_sql_driver
31-
32-
database_uri = ensure_specified_sql_driver(database_uri)
33-
34-
async def do_setup():
35-
engine = create_async_engine(database_uri)
36-
redacted_url = engine.url._replace(password="[redacted]")
37-
try:
38-
await check_database(engine, REQUIRED_REVISION, ALL_REVISIONS)
39-
except UninitializedDatabase:
40-
# Create tables and stamp (alembic) revision.
41-
typer.echo(
42-
f"Database {redacted_url} is new. Creating tables and marking revision {REQUIRED_REVISION}.",
43-
err=True,
44-
)
45-
await initialize_database(engine)
46-
typer.echo("Database initialized.", err=True)
47-
else:
48-
typer.echo(f"Database at {redacted_url} is already initialized.", err=True)
49-
raise typer.Abort()
50-
await engine.dispose()
51-
52-
asyncio.run(do_setup())
53-
stamp_head(ALEMBIC_INI_TEMPLATE_PATH, ALEMBIC_DIR, database_uri)
54-
55-
56-
@admin_app.command("upgrade-database")
57-
def upgrade_database(
58-
database_uri: str,
59-
revision: Optional[str] = typer.Argument(
60-
None,
61-
help="The ID of a revision to upgrade to. By default, upgrade to the latest one.",
62-
),
63-
):
64-
"""
65-
Upgrade the database schema to the latest version.
66-
"""
67-
import asyncio
68-
69-
from sqlalchemy.ext.asyncio import create_async_engine
70-
71-
from ..alembic_utils import get_current_revision, upgrade
72-
from ..authn_database.alembic_constants import (
73-
ALEMBIC_DIR,
74-
ALEMBIC_INI_TEMPLATE_PATH,
75-
)
76-
from ..authn_database.core import ALL_REVISIONS
77-
from ..utils import ensure_specified_sql_driver
78-
79-
database_uri = ensure_specified_sql_driver(database_uri)
80-
81-
async def do_setup():
82-
engine = create_async_engine(database_uri)
83-
redacted_url = engine.url._replace(password="[redacted]")
84-
current_revision = await get_current_revision(engine, ALL_REVISIONS)
85-
await engine.dispose()
86-
if current_revision is None:
87-
# Create tables and stamp (alembic) revision.
88-
typer.echo(
89-
f"Database {redacted_url} has not been initialized. Use `tiled admin initialize-database`.",
90-
err=True,
91-
)
92-
raise typer.Abort()
93-
94-
asyncio.run(do_setup())
95-
upgrade(ALEMBIC_INI_TEMPLATE_PATH, ALEMBIC_DIR, database_uri, revision or "head")
96-
97-
98-
@admin_app.command("downgrade-database")
99-
def downgrade_database(
100-
database_uri: str,
101-
revision: str = typer.Argument(..., help="The ID of a revision to downgrade to."),
102-
):
103-
"""
104-
Upgrade the database schema to the latest version.
105-
"""
106-
import asyncio
107-
108-
from sqlalchemy.ext.asyncio import create_async_engine
109-
110-
from ..alembic_utils import downgrade, get_current_revision
111-
from ..authn_database.alembic_constants import (
112-
ALEMBIC_DIR,
113-
ALEMBIC_INI_TEMPLATE_PATH,
114-
)
115-
from ..authn_database.core import ALL_REVISIONS
116-
from ..utils import ensure_specified_sql_driver
117-
118-
database_uri = ensure_specified_sql_driver(database_uri)
119-
120-
async def do_setup():
121-
engine = create_async_engine(database_uri)
122-
redacted_url = engine.url._replace(password="[redacted]")
123-
current_revision = await get_current_revision(engine, ALL_REVISIONS)
124-
if current_revision is None:
125-
# Create tables and stamp (alembic) revision.
126-
typer.echo(
127-
f"Database {redacted_url} has not been initialized. Use `tiled admin initialize-database`.",
128-
err=True,
129-
)
130-
raise typer.Abort()
131-
132-
asyncio.run(do_setup())
133-
downgrade(ALEMBIC_INI_TEMPLATE_PATH, ALEMBIC_DIR, database_uri, revision)
134-
135-
136-
@admin_app.command("check-config")
137-
def check_config(
138-
config_path: Path = typer.Argument(
139-
None,
140-
help=(
141-
"Path to a config file or directory of config files. "
142-
"If None, check environment variable TILED_CONFIG. "
143-
"If that is unset, try default location ./config.yml."
144-
),
145-
),
146-
):
147-
"Check configuration file for syntax and validation errors."
148-
import os
149-
150-
from ..config import parse_configs
151-
152-
config_path = config_path or os.getenv("TILED_CONFIG", "config.yml")
153-
try:
154-
parse_configs(config_path)
155-
except Exception as err:
156-
typer.echo(str(err), err=True)
157-
raise typer.Exit(1)
158-
typer.echo("No errors found in configuration.")
159-
160-
161-
@admin_app.command("list-principals")
162-
def list_principals(
163-
profile: Optional[str] = typer.Option(
164-
None, help="If you use more than one Tiled server, use this to specify which."
165-
),
166-
page_offset: int = typer.Argument(0),
167-
page_limit: int = typer.Argument(100, help="Max items to show"),
168-
):
169-
"""
170-
List information about all Principals (users or services) that have ever logged in.
171-
"""
172-
import json
173-
174-
context = get_context(profile)
175-
result = context.admin.list_principals(offset=page_offset, limit=page_limit)
176-
typer.echo(json.dumps(result, indent=2))
177-
178-
179-
@admin_app.command("show-principal")
180-
def show_principal(
181-
profile: Optional[str] = typer.Option(
182-
None, help="If you use more than one Tiled server, use this to specify which."
183-
),
184-
uuid: str = typer.Argument(..., help="UUID identifying Principal of interest"),
185-
):
186-
"""
187-
Show information about one Principal (user or service).
188-
"""
189-
import json
190-
191-
context = get_context(profile)
192-
result = context.admin.show_principal(uuid)
193-
typer.echo(json.dumps(result, indent=2))
14+
def cli_cmd(self) -> None:
15+
CliApp.run_subcommand(self)

0 commit comments

Comments
 (0)