Skip to content

Commit

Permalink
put runtime build in user data dir
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rmorshea committed Jun 10, 2021
1 parent e7e14f8 commit 0af69d2
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 118 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-purple.svg">
</a>

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

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

Expand Down
1 change: 1 addition & 0 deletions requirements/pkg-deps.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ typer >=0.3.2
click-spinner >=0.1.10
fastjsonschema >=2.14.5
rich >=9.13.0
appdirs >=1.4.4
2 changes: 2 additions & 0 deletions src/idom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# package is not installed
__version__ = "0.0.0"

__author__ = "idom-team"

from . import config, log
from .client.module import Import, Module, install
from .core import hooks
Expand Down
62 changes: 19 additions & 43 deletions src/idom/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,35 @@
from typing import List

import typer
from rich.console import Console
from rich.table import Table

import idom
from idom.client import _private
from idom.client import manage as manage_client

from .config import all_options
from .log import logging_config_defaults


main = typer.Typer()
console = Console()


@main.callback()
def init() -> None:
@main.callback(invoke_without_command=True, no_args_is_help=True)
def root(
version: bool = typer.Option(
False,
"--version",
help="show the current version",
show_default=False,
is_eager=True,
)
):
"""Command line interface for IDOM"""

# reset logging config after Typer() has wrapped stdout
dictConfig(logging_config_defaults())

if version:
typer.echo(idom.__version__)


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

@main.command()
def restore() -> None:
"""Return to a fresh install of Ithe client"""
"""Return to a fresh install of the client"""
manage_client.restore()
return None


@main.command()
def version(verbose: bool = False) -> None:
"""Show version information"""
if not verbose:
console.print(idom.__version__)
else:
table = Table()

table.add_column("Package")
table.add_column("Version")
table.add_column("Language")

table.add_row("idom", str(idom.__version__), "Python")

for js_pkg, js_ver in _private.build_dependencies().items():
table.add_row(js_pkg, js_ver, "Javascript")

console.print(table)


@main.command()
def options() -> None:
"""Show available global options and their current values"""
options = list(sorted(all_options(), key=lambda opt: opt.name))

table = Table()

table.add_column("Name", min_width=len(max([opt.name for opt in options])))
table.add_column("Value")
table.add_column("Default")
table.add_column("Mutable")

for opt in options:
value, default, mutable = list(
map(str, [opt.current, opt.default, opt.mutable])
)
table.add_row(opt.name, value, default, mutable)

console.print(table)
for opt in list(sorted(all_options(), key=lambda opt: opt.name)):
name = typer.style(opt.name, bold=True)
typer.echo(f"{name} = {opt.current}")
3 changes: 2 additions & 1 deletion src/idom/client/_private.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
def _run_build_dir_init_only_once() -> None: # pragma: no cover
"""Initialize the runtime build directory - this should only be called once"""
if not IDOM_CLIENT_BUILD_DIR.current.exists():
IDOM_CLIENT_BUILD_DIR.current.parent.mkdir(parents=True, exist_ok=True)
# populate the runtime build directory if it doesn't exist
shutil.copytree(BACKUP_BUILD_DIR, IDOM_CLIENT_BUILD_DIR.current, symlinks=True)
elif getmtime(BACKUP_BUILD_DIR) > getmtime(IDOM_CLIENT_BUILD_DIR.current):
Expand All @@ -28,7 +29,7 @@ def _run_build_dir_init_only_once() -> None: # pragma: no cover
shutil.copytree(BACKUP_BUILD_DIR, IDOM_CLIENT_BUILD_DIR.current, symlinks=True)


_run_build_dir_init_only_once() # this is only ever called once!
_run_build_dir_init_only_once() # this is only ever called once at runtime!


def get_user_packages_file(app_dir: Path) -> Path:
Expand Down
4 changes: 1 addition & 3 deletions src/idom/client/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
"snowpack": "^3.5.2"
},
"dependencies": {
"idom-client-react": "file:packages/idom-client-react",
"react": "^16.13.1",
"react-dom": "^16.13.1"
"idom-client-react": "file:packages/idom-client-react"
}
}
12 changes: 11 additions & 1 deletion src/idom/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
variables or, for those which allow it, a programatic interface.
"""

import shutil
from pathlib import Path
from typing import Any, List

from appdirs import user_data_dir

import idom

from ._option import ALL_OPTIONS as _ALL_OPTIONS
from ._option import Option as _Option

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

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

# TODO: remove this in 0.30.0
_DEPRECATED_BUILD_DIR = Path(__file__).parent / "client" / "build"
if _DEPRECATED_BUILD_DIR.exists(): # pragma: no cover
shutil.rmtree(_DEPRECATED_BUILD_DIR)

IDOM_FEATURE_INDEX_AS_DEFAULT_KEY = _Option(
"IDOM_FEATURE_INDEX_AS_DEFAULT_KEY",
default=False,
Expand Down
78 changes: 9 additions & 69 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,18 @@
from unittest.mock import patch

from rich.console import Console
from typer.testing import CliRunner

import idom
from idom.cli import main
from idom.client.manage import _private, web_module_exists
from idom.config import IDOM_CLIENT_BUILD_DIR, IDOM_DEBUG_MODE
from idom.client.manage import web_module_exists
from idom.config import all_options


cli_runner = CliRunner()


with_large_console = patch("idom.cli.console", Console(width=10000))


def assert_rich_table_equals(stdout, expected_header, expected_rows):
parsed_lines = []
for line in stdout.split("\n"):
maybe_row = list(
map(str.strip, filter(None, line.replace("┃", "│").split("│")))
)
if len(maybe_row) > 1:
parsed_lines.append(maybe_row)

actual_header, *actual_rows = parsed_lines

assert actual_header == list(map(str, expected_header))
assert actual_rows == [list(map(str, row)) for row in expected_rows]
def test_root():
assert idom.__version__ in cli_runner.invoke(main, ["--version"]).stdout


@with_large_console
def test_install():
cli_runner.invoke(main, ["restore"])
assert cli_runner.invoke(main, ["install", "jquery"]).exit_code == 0
Expand All @@ -44,54 +26,12 @@ def test_install():
assert web_module_exists("jquery")


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


@with_large_console
def test_show_version():
terse_result = cli_runner.invoke(main, ["version"])
assert idom.__version__ in terse_result.stdout

verbose_result = cli_runner.invoke(main, ["version", "--verbose"])

assert_rich_table_equals(
verbose_result.stdout,
["Package", "Version", "Language"],
(
[["idom", idom.__version__, "Python"]]
+ [
[js_pkg, js_ver, "Javascript"]
for js_pkg, js_ver in _private.build_dependencies().items()
]
),
)


@with_large_console
def test_show_options():
assert_rich_table_equals(
cli_runner.invoke(main, ["options"]).stdout,
["Name", "Value", "Default", "Mutable"],
[
[
"IDOM_CLIENT_BUILD_DIR",
IDOM_CLIENT_BUILD_DIR.current,
IDOM_CLIENT_BUILD_DIR.default,
"True",
],
[
"IDOM_DEBUG_MODE",
IDOM_DEBUG_MODE.current,
IDOM_DEBUG_MODE.default,
"False",
],
[
"IDOM_FEATURE_INDEX_AS_DEFAULT_KEY",
"False",
"False",
"False",
],
],
)
def test_options():
assert cli_runner.invoke(main, ["options"]).stdout.strip().split("\n") == [
f"{opt.name} = {opt.current}"
for opt in sorted(all_options(), key=lambda o: o.name)
]

0 comments on commit 0af69d2

Please sign in to comment.