Skip to content

Commit 0af69d2

Browse files
committed
put runtime build in user data dir
Additionally implifies CLI output - unfortunately Rich tables are too clunky for the simple things we need. An __author__ attribute is also added to the root module for use in constructing the name of the user data directory that will be used.
1 parent e7e14f8 commit 0af69d2

File tree

8 files changed

+47
-118
lines changed

8 files changed

+47
-118
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-purple.svg">
1111
</a>
1212

13-
Libraries for creating and controlling interactive web pages with Python 3.7 and above.
13+
A package for building highly interactive user interfaces in pure Python inspred by
14+
[ReactJS](https://reactjs.org/).
1415

1516
**Be sure to [read the Documentation](https://idom-docs.herokuapp.com)**
1617

requirements/pkg-deps.txt

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ typer >=0.3.2
77
click-spinner >=0.1.10
88
fastjsonschema >=2.14.5
99
rich >=9.13.0
10+
appdirs >=1.4.4

src/idom/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
# package is not installed
99
__version__ = "0.0.0"
1010

11+
__author__ = "idom-team"
12+
1113
from . import config, log
1214
from .client.module import Import, Module, install
1315
from .core import hooks

src/idom/cli.py

+19-43
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,35 @@
22
from typing import List
33

44
import typer
5-
from rich.console import Console
6-
from rich.table import Table
75

86
import idom
9-
from idom.client import _private
107
from idom.client import manage as manage_client
118

129
from .config import all_options
1310
from .log import logging_config_defaults
1411

1512

1613
main = typer.Typer()
17-
console = Console()
1814

1915

20-
@main.callback()
21-
def init() -> None:
16+
@main.callback(invoke_without_command=True, no_args_is_help=True)
17+
def root(
18+
version: bool = typer.Option(
19+
False,
20+
"--version",
21+
help="show the current version",
22+
show_default=False,
23+
is_eager=True,
24+
)
25+
):
26+
"""Command line interface for IDOM"""
27+
2228
# reset logging config after Typer() has wrapped stdout
2329
dictConfig(logging_config_defaults())
2430

31+
if version:
32+
typer.echo(idom.__version__)
33+
2534

2635
@main.command()
2736
def install(packages: List[str]) -> None:
@@ -32,47 +41,14 @@ def install(packages: List[str]) -> None:
3241

3342
@main.command()
3443
def restore() -> None:
35-
"""Return to a fresh install of Ithe client"""
44+
"""Return to a fresh install of the client"""
3645
manage_client.restore()
3746
return None
3847

3948

40-
@main.command()
41-
def version(verbose: bool = False) -> None:
42-
"""Show version information"""
43-
if not verbose:
44-
console.print(idom.__version__)
45-
else:
46-
table = Table()
47-
48-
table.add_column("Package")
49-
table.add_column("Version")
50-
table.add_column("Language")
51-
52-
table.add_row("idom", str(idom.__version__), "Python")
53-
54-
for js_pkg, js_ver in _private.build_dependencies().items():
55-
table.add_row(js_pkg, js_ver, "Javascript")
56-
57-
console.print(table)
58-
59-
6049
@main.command()
6150
def options() -> None:
6251
"""Show available global options and their current values"""
63-
options = list(sorted(all_options(), key=lambda opt: opt.name))
64-
65-
table = Table()
66-
67-
table.add_column("Name", min_width=len(max([opt.name for opt in options])))
68-
table.add_column("Value")
69-
table.add_column("Default")
70-
table.add_column("Mutable")
71-
72-
for opt in options:
73-
value, default, mutable = list(
74-
map(str, [opt.current, opt.default, opt.mutable])
75-
)
76-
table.add_row(opt.name, value, default, mutable)
77-
78-
console.print(table)
52+
for opt in list(sorted(all_options(), key=lambda opt: opt.name)):
53+
name = typer.style(opt.name, bold=True)
54+
typer.echo(f"{name} = {opt.current}")

src/idom/client/_private.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
def _run_build_dir_init_only_once() -> None: # pragma: no cover
2020
"""Initialize the runtime build directory - this should only be called once"""
2121
if not IDOM_CLIENT_BUILD_DIR.current.exists():
22+
IDOM_CLIENT_BUILD_DIR.current.parent.mkdir(parents=True, exist_ok=True)
2223
# populate the runtime build directory if it doesn't exist
2324
shutil.copytree(BACKUP_BUILD_DIR, IDOM_CLIENT_BUILD_DIR.current, symlinks=True)
2425
elif getmtime(BACKUP_BUILD_DIR) > getmtime(IDOM_CLIENT_BUILD_DIR.current):
@@ -28,7 +29,7 @@ def _run_build_dir_init_only_once() -> None: # pragma: no cover
2829
shutil.copytree(BACKUP_BUILD_DIR, IDOM_CLIENT_BUILD_DIR.current, symlinks=True)
2930

3031

31-
_run_build_dir_init_only_once() # this is only ever called once!
32+
_run_build_dir_init_only_once() # this is only ever called once at runtime!
3233

3334

3435
def get_user_packages_file(app_dir: Path) -> Path:

src/idom/client/app/package.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
"snowpack": "^3.5.2"
2222
},
2323
"dependencies": {
24-
"idom-client-react": "file:packages/idom-client-react",
25-
"react": "^16.13.1",
26-
"react-dom": "^16.13.1"
24+
"idom-client-react": "file:packages/idom-client-react"
2725
}
2826
}

src/idom/config.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@
66
variables or, for those which allow it, a programatic interface.
77
"""
88

9+
import shutil
910
from pathlib import Path
1011
from typing import Any, List
1112

13+
from appdirs import user_data_dir
14+
15+
import idom
16+
1217
from ._option import ALL_OPTIONS as _ALL_OPTIONS
1318
from ._option import Option as _Option
1419

@@ -35,7 +40,7 @@ def all_options() -> List[_Option[Any]]:
3540

3641
IDOM_CLIENT_BUILD_DIR = _Option(
3742
"IDOM_CLIENT_BUILD_DIR",
38-
default=Path(__file__).parent / "client" / "build",
43+
default=Path(user_data_dir(idom.__name__, idom.__author__)) / "client",
3944
validator=Path,
4045
)
4146
"""The location IDOM will use to store its client application
@@ -45,6 +50,11 @@ def all_options() -> List[_Option[Any]]:
4550
a set of publically available APIs for working with the client.
4651
"""
4752

53+
# TODO: remove this in 0.30.0
54+
_DEPRECATED_BUILD_DIR = Path(__file__).parent / "client" / "build"
55+
if _DEPRECATED_BUILD_DIR.exists(): # pragma: no cover
56+
shutil.rmtree(_DEPRECATED_BUILD_DIR)
57+
4858
IDOM_FEATURE_INDEX_AS_DEFAULT_KEY = _Option(
4959
"IDOM_FEATURE_INDEX_AS_DEFAULT_KEY",
5060
default=False,

tests/test_cli.py

+9-69
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,18 @@
1-
from unittest.mock import patch
2-
3-
from rich.console import Console
41
from typer.testing import CliRunner
52

63
import idom
74
from idom.cli import main
8-
from idom.client.manage import _private, web_module_exists
9-
from idom.config import IDOM_CLIENT_BUILD_DIR, IDOM_DEBUG_MODE
5+
from idom.client.manage import web_module_exists
6+
from idom.config import all_options
107

118

129
cli_runner = CliRunner()
1310

1411

15-
with_large_console = patch("idom.cli.console", Console(width=10000))
16-
17-
18-
def assert_rich_table_equals(stdout, expected_header, expected_rows):
19-
parsed_lines = []
20-
for line in stdout.split("\n"):
21-
maybe_row = list(
22-
map(str.strip, filter(None, line.replace("┃", "│").split("│")))
23-
)
24-
if len(maybe_row) > 1:
25-
parsed_lines.append(maybe_row)
26-
27-
actual_header, *actual_rows = parsed_lines
28-
29-
assert actual_header == list(map(str, expected_header))
30-
assert actual_rows == [list(map(str, row)) for row in expected_rows]
12+
def test_root():
13+
assert idom.__version__ in cli_runner.invoke(main, ["--version"]).stdout
3114

3215

33-
@with_large_console
3416
def test_install():
3517
cli_runner.invoke(main, ["restore"])
3618
assert cli_runner.invoke(main, ["install", "jquery"]).exit_code == 0
@@ -44,54 +26,12 @@ def test_install():
4426
assert web_module_exists("jquery")
4527

4628

47-
@with_large_console
4829
def test_restore():
4930
assert cli_runner.invoke(main, ["restore"]).exit_code == 0
5031

5132

52-
@with_large_console
53-
def test_show_version():
54-
terse_result = cli_runner.invoke(main, ["version"])
55-
assert idom.__version__ in terse_result.stdout
56-
57-
verbose_result = cli_runner.invoke(main, ["version", "--verbose"])
58-
59-
assert_rich_table_equals(
60-
verbose_result.stdout,
61-
["Package", "Version", "Language"],
62-
(
63-
[["idom", idom.__version__, "Python"]]
64-
+ [
65-
[js_pkg, js_ver, "Javascript"]
66-
for js_pkg, js_ver in _private.build_dependencies().items()
67-
]
68-
),
69-
)
70-
71-
72-
@with_large_console
73-
def test_show_options():
74-
assert_rich_table_equals(
75-
cli_runner.invoke(main, ["options"]).stdout,
76-
["Name", "Value", "Default", "Mutable"],
77-
[
78-
[
79-
"IDOM_CLIENT_BUILD_DIR",
80-
IDOM_CLIENT_BUILD_DIR.current,
81-
IDOM_CLIENT_BUILD_DIR.default,
82-
"True",
83-
],
84-
[
85-
"IDOM_DEBUG_MODE",
86-
IDOM_DEBUG_MODE.current,
87-
IDOM_DEBUG_MODE.default,
88-
"False",
89-
],
90-
[
91-
"IDOM_FEATURE_INDEX_AS_DEFAULT_KEY",
92-
"False",
93-
"False",
94-
"False",
95-
],
96-
],
97-
)
33+
def test_options():
34+
assert cli_runner.invoke(main, ["options"]).stdout.strip().split("\n") == [
35+
f"{opt.name} = {opt.current}"
36+
for opt in sorted(all_options(), key=lambda o: o.name)
37+
]

0 commit comments

Comments
 (0)