Skip to content

Commit 3459acb

Browse files
authored
Merge pull request #4 from gitmobkab/cli_refactor
merge of the Cli refactor
2 parents 5b3b55b + 110def6 commit 3459acb

36 files changed

Lines changed: 561 additions & 269 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- security improvements against sql injections
1213
- official postgres adapter
1314
- unique keyword and generated values
15+
- a `copia run` command
1416

1517
### Changed
1618

19+
- the tui can now be launch with `copia tui` rather than `copia`
1720
- the dsl syntax now explicitly require parantheses after the name of a generator
1821
- the `ref` generator is now called `fetch` as the name is more intuitive
1922
- the cli logs messages appereance

src/copia/adapters/mysql_adapter.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,16 @@ def insert(self, table: str, rows: Sequence[dict[str, Any]], batch_size: int = 2
6969
query = f"INSERT INTO {table} ({columns_query}) VALUES ({placeholders})"
7070

7171
iterator = iter(rows)
72-
with self._connection.cursor() as cursor:
73-
while batch := list(islice(iterator, batch_size)):
74-
coerced_batch = list(map(self._coerce_row, batch))
75-
cursor.executemany(query, coerced_batch)
76-
self._connection.commit()
72+
73+
try:
74+
with self._connection.cursor() as cursor:
75+
while batch := list(islice(iterator, batch_size)):
76+
coerced_batch = list(map(self._coerce_row, batch))
77+
cursor.executemany(query, coerced_batch)
78+
self._connection.commit()
79+
except Exception as err:
80+
self._connection.rollback()
81+
raise err
7782

7883
def _coerce_row(self, row: dict[str, Any]) -> dict[str, Any]:
7984
coerced_row: dict[str, Any] = {}

src/copia/adapters/postgres_adapter.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ def insert(self, table: str, rows: Sequence[dict[str, Any]], batch_size: int = 2
7070
self.escape_columns(columns),
7171
SQL(", ").join(placeholders)
7272
)
73-
74-
iterator = iter(rows)
75-
with self._connection.cursor() as cursor:
76-
while batch := list(islice(iterator, batch_size)):
77-
cursor.executemany(composed_query, batch)
78-
self._connection.commit()
79-
73+
74+
try:
75+
iterator = iter(rows)
76+
with self._connection.cursor() as cursor:
77+
while batch := list(islice(iterator, batch_size)):
78+
cursor.executemany(composed_query, batch)
79+
self._connection.commit()
80+
except Exception as err:
81+
self._connection.rollback()
82+
raise err
8083

8184
def close(self) -> None:
8285
self._connection.close()

src/copia/cli/app.py

Lines changed: 35 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,51 @@
11
import typer
2-
2+
from typing import get_args
33
from copia._version import __version__
4-
from .commands import list_app, init_command
5-
from .exit_codes import ExitCodes
6-
from copia.adapters import get_adapter
7-
from ..tui import CopiaApp
8-
from copia.cli.console_utils import print_error, info, success, error, echo
9-
from .config import (
10-
get_profile,
11-
LOCAL_COPIA_FILE,
12-
GLOBAL_COPIA_FILE
4+
from .commands import (
5+
list_command,
6+
init_command,
7+
tui_command,
8+
run_command
9+
)
10+
from .config import Adapter
11+
from .utils import echo
12+
13+
14+
AVAILABLES_ADAPTERS: tuple = get_args(Adapter)
15+
16+
def get_help_msg():
17+
help_msg = "An Interactive TUI app for Databases seeding"
18+
help_msg += "\n\nSupported databases:"
19+
for value in AVAILABLES_ADAPTERS:
20+
help_msg += f'\n- {value}'
21+
return help_msg
22+
23+
app = typer.Typer(
24+
name='copia',
25+
help=get_help_msg(),
26+
no_args_is_help=True,
27+
invoke_without_command=True,
28+
context_settings={
29+
'help_option_names': ['--help', '-h']
30+
}
1331
)
14-
15-
app = typer.Typer(invoke_without_command=True)
1632

1733
app.add_typer(init_command)
18-
app.add_typer(list_app)
34+
app.add_typer(list_command)
35+
app.add_typer(tui_command)
36+
app.add_typer(run_command)
1937

2038
@app.callback()
2139
def main(
2240
ctx: typer.Context,
23-
profile_name: str = typer.Option("default",
24-
"-p", "--profile",
25-
help="The profile to use for the session"),
41+
2642
version_flag: bool = typer.Option(False,
2743
"-v", "--version",
2844
help="Display the current version and exit"),
29-
30-
help_flag: bool = typer.Option(False,
31-
"-h", "--help",
32-
help="Display this message and exit"),
33-
34-
search_globals_only: bool = typer.Option(False,
35-
"-g", "--global",
36-
help=f"Search only in [green]'{GLOBAL_COPIA_FILE}'"),
37-
search_locals_only: bool = typer.Option(False,
38-
"-l", "--local",
39-
help=f"Search only in [green]'{LOCAL_COPIA_FILE}'"),
40-
):
41-
42-
"""An Interactive TUI app for MySQL Database seeding"""
43-
45+
):
4446
if ctx.invoked_subcommand is not None:
4547
return
46-
47-
if help_flag:
48-
echo(ctx.get_help())
49-
raise typer.Exit()
50-
48+
5149
if version_flag:
5250
echo(f"[blue]copia {__version__}")
53-
raise typer.Exit()
54-
55-
profile = None
56-
try:
57-
if search_globals_only and search_locals_only:
58-
error("Cannot use --global | -g and --local | -l at the same time")
59-
raise typer.Exit()
60-
61-
if search_globals_only:
62-
profile = get_profile(profile_name, "global")
63-
elif search_locals_only:
64-
profile = get_profile(profile_name, "local")
65-
else:
66-
profile = get_any_profile(profile_name)
67-
except Exception as err:
68-
print_error(err)
69-
raise typer.Exit()
70-
71-
72-
info(f"Found profile: {profile}")
73-
info("Connecting to db...")
74-
75-
try:
76-
adapter = get_adapter(profile)
77-
success("Connection to db successfull")
78-
except ImportError:
79-
print_error(f"Missing dependencies for adapter {profile.adapter!r}",
80-
f'Try "pip install copia-seed\\[{profile.adapter}]"')
81-
raise typer.Exit(ExitCodes.RESOURCE_ERROR)
82-
except Exception as connection_err:
83-
print_error(connection_err)
84-
raise typer.Exit(ExitCodes.CONNEXION_TO_DB_FAILED)
85-
86-
CopiaApp(adapter).run()
87-
success("Bye.")
88-
89-
90-
def get_any_profile(profile_name: str):
91-
"""
92-
Try to fetch the profile from the local config file,
93-
and fall back to global on any fail.
94-
Any exception from local fetching get silently swallowed
95-
96-
All the exceptions raised by this function are always in the global fetch state
97-
98-
Args:
99-
profile_name (str): the name by which the profile is identified
100-
101-
Raises:
102-
FileNotFoundError: when the file couldn't be found.
103-
PermissionError: when permission to read the file had been denied by the os.
104-
InvalidConfigError: when the config isn't valid TOML or the value of the "profiles" key is not a dict/TOML Table
105-
ProfileNotFoundError: when the "profiles.profile_name" key don't exist, this also includes missing "profiles" key
106-
FoundProfileIsNotATableError: when the "profiles.profile_name" key exist but is not a dict
107-
InvalidProfileError: when the profile isn't valid
108-
109-
Returns:
110-
Profile: a valid Profile object
111-
"""
112-
try:
113-
info(f"Fetching profiles.\"{profile_name}\" from local config...")
114-
return get_profile(profile_name, "local")
115-
except Exception as err:
116-
error(f"{err}")
117-
info("Falling back to global config...")
118-
info(f"Fetching profiles.\"{profile_name}\" from global config...")
119-
return get_profile(profile_name, "global")
51+
raise typer.Exit()

src/copia/cli/commands/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
from .list import list_profile_app as list_app
2-
from .init import init_command
1+
from .list import list_command
2+
from .init import init_command
3+
from .tui import tui_command
4+
from .run import run_command

src/copia/cli/commands/init.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from rich.prompt import Confirm
55
import typer
66

7-
from copia.cli.exit_codes import ExitCodes
8-
from copia.cli.console_utils import echo, print_error, info, warning, success
7+
from copia.cli.utils.exit_codes import ExitCodes
8+
from copia.cli.utils.console_utils import echo, print_error, info, warning, success
99
from ..config.loaders import (
1010
resolve_config_path
1111
)
@@ -19,9 +19,9 @@ def main(ctx: typer.Context,
1919
is_global_config: bool = typer.Option(
2020
False, "-g", "--global", help="create the config file as a global config"
2121
)):
22-
"""[blue]generate a template config file for copia[/]
22+
"""Generate a template config file for copia.
2323
24-
by default it create a local scoped config file
24+
By default it create a local scoped config file
2525
"""
2626

2727
if help_flag:

src/copia/cli/commands/list.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
import typer
2-
from copia.cli.console_utils import echo
2+
from copia.cli.utils.console_utils import echo
33

44
from ..config.loaders import load_config, get_profile_from_config
55
from ..config.exceptions import InvalidProfileError
66

7-
list_profile_app = typer.Typer()
7+
list_command = typer.Typer()
88

9-
@list_profile_app.command("list")
10-
def list_profiles(
11-
ctx: typer.Context,
12-
help_flag: bool =
13-
typer.Option(False, "-h", "--help", help="Display this message and exit")):
9+
# now that i look at it, it's horrible
10+
# better refactor that shit soon
11+
12+
# TODO: FUCKING REFACTOR THAT, IT'S UGLY JUST BY SCROLLING
13+
# I DON'T WANNA THINK ABOUT WHY I WROTE IT LIKE THAT
14+
15+
@list_command.command("list")
16+
def main():
1417

15-
"""[blue]list all profiles defined in both global and local config files"""
18+
"""List all profiles defined in both global and local config files."""
1619
local_config = None
1720
global_config = None
1821
local_failure_reason = None
1922
global_failure_reason = None
2023

21-
if help_flag:
22-
echo(ctx.get_help())
23-
return
24-
2524
try:
2625
local_config = load_config("local")
2726
except Exception as err:

0 commit comments

Comments
 (0)