From 4e20fdc2d12e47ac3d048fc36255cda737fda76f Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Mon, 1 Nov 2021 20:21:30 +0000 Subject: [PATCH 1/9] adding latest to CLI --- tembo/{ => journal/cli}/cli.py | 114 ++++++++++++++++++--------------- tembo/journal/cli/new.py | 0 tembo/journal/pages.py | 11 +++- 3 files changed, 72 insertions(+), 53 deletions(-) rename tembo/{ => journal/cli}/cli.py (86%) create mode 100644 tembo/journal/cli/new.py diff --git a/tembo/cli.py b/tembo/journal/cli/cli.py similarity index 86% rename from tembo/cli.py rename to tembo/journal/cli/cli.py index ac42cb7..52561d1 100644 --- a/tembo/cli.py +++ b/tembo/journal/cli/cli.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import pathlib +from typing import Collection import click @@ -26,7 +29,7 @@ def run(): @click.command(options_metavar="", name="list") def list_all(): - """List all scopes defined in the config.yml""" + """List all scopes defined in the config.yml.""" _all_scopes = [user_scope["name"] for user_scope in tembo.CONFIG.scopes] tembo.logger.info( "%s names found in config.yml: '%s'", len(_all_scopes), "', '".join(_all_scopes) @@ -43,7 +46,7 @@ def list_all(): ) @click.option("--dry-run", is_flag=True, default=False) @click.option("--example", is_flag=True, default=False) -def new(scope, inputs, dry_run, example): +def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): r""" Create a new page. @@ -57,61 +60,20 @@ def new(scope, inputs, dry_run, example): Example: tembo new meeting my_presentation """ - # get the name from the tembo config.yml + # check that the name exists in the config.yml _cli_verify_name_exists(scope) - # get the scope information from the tembo config.yml + # get the scope configuration from the config.yml config_scope = _cli_get_config_scope(scope) - # print the example to the user - if example: - tembo.logger.info( - "Example for 'tembo new %s': %s", - config_scope["name"], - config_scope["example"] - if isinstance(config_scope["example"], str) - else "No example in config.yml", - ) - raise SystemExit(0) + # if --example flag, return the example to the user + _cli_show_example(example, config_scope) # if the name is in the config.yml, create the scoped page - page_creator_options = pages.PageCreatorOptions( - base_path=tembo.CONFIG.base_path, - page_path=config_scope["path"], - filename=config_scope["filename"], - extension=config_scope["extension"], - name=config_scope["name"], - example=config_scope["example"], - user_input=inputs, - template_filename=config_scope["template_filename"], - template_path=tembo.CONFIG.template_path, - ) - try: - scoped_page = pages.ScopedPageCreator(page_creator_options).create_page() - except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: - tembo.logger.critical(base_path_does_not_exist_error) - raise SystemExit(1) from base_path_does_not_exist_error - except exceptions.TemplateFileNotFoundError as template_file_not_found_error: - tembo.logger.critical(template_file_not_found_error.args[0]) - raise SystemExit(1) from template_file_not_found_error - except exceptions.MismatchedTokenError as mismatched_token_error: - if config_scope["example"] is not None: - tembo.logger.critical( - "Your tembo config.yml/template specifies %s input tokens, you gave %s. Example: %s", - mismatched_token_error.expected, - mismatched_token_error.given, - config_scope["example"], - ) - raise SystemExit(1) from mismatched_token_error - tembo.logger.critical( - "Your tembo config.yml/template specifies %s input tokens, you gave %s", - mismatched_token_error.expected, - mismatched_token_error.given, - ) - raise SystemExit(1) from mismatched_token_error + scoped_page = _cli_create_scoped_page(config_scope, inputs) if dry_run: - click.echo(cli_message(f"{scoped_page.path} will be created")) + cli_message(f"{scoped_page.path} will be created") raise SystemExit(0) try: @@ -123,9 +85,7 @@ def new(scope, inputs, dry_run, example): def _cli_verify_name_exists(scope: str) -> None: - _name_found = scope in [ - user_scope["name"] for user_scope in tembo.CONFIG.scopes - ] + _name_found = scope in [user_scope["name"] for user_scope in tembo.CONFIG.scopes] if _name_found: return if len(tembo.CONFIG.scopes) > 0: @@ -173,6 +133,56 @@ def _cli_get_config_scope(scope: str) -> dict: return config_scope +def _cli_show_example(example: bool, config_scope: dict) -> None: + if example: + tembo.logger.info( + "Example for 'tembo new %s': %s", + config_scope["name"], + config_scope["example"] + if isinstance(config_scope["example"], str) + else "No example in config.yml", + ) + raise SystemExit(0) + + +def _cli_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: + page_creator_options = pages.PageCreatorOptions( + base_path=tembo.CONFIG.base_path, + page_path=config_scope["path"], + filename=config_scope["filename"], + extension=config_scope["extension"], + name=config_scope["name"], + example=config_scope["example"], + user_input=inputs, + template_filename=config_scope["template_filename"], + template_path=tembo.CONFIG.template_path, + ) + try: + return pages.ScopedPageCreator(page_creator_options).create_page() + except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: + tembo.logger.critical(base_path_does_not_exist_error) + raise SystemExit(1) from base_path_does_not_exist_error + except exceptions.TemplateFileNotFoundError as template_file_not_found_error: + tembo.logger.critical(template_file_not_found_error.args[0]) + raise SystemExit(1) from template_file_not_found_error + except exceptions.MismatchedTokenError as mismatched_token_error: + if config_scope["example"] is not None: + tembo.logger.critical( + "Your tembo config.yml/template specifies %s input tokens, you gave %s. Example: %s", + mismatched_token_error.expected, + mismatched_token_error.given, + config_scope["example"], + ) + raise SystemExit(1) from mismatched_token_error + tembo.logger.critical( + "Your tembo config.yml/template specifies %s input tokens, you gave %s", + mismatched_token_error.expected, + mismatched_token_error.given, + ) + raise SystemExit(1) from mismatched_token_error + + + def cli_message(message: str) -> None: click.echo(f"[TEMBO] {message} 🐘") diff --git a/tembo/journal/cli/new.py b/tembo/journal/cli/new.py new file mode 100644 index 0000000..e69de29 diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index c7783f9..536b124 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -216,6 +216,11 @@ class Page(metaclass=ABCMeta): def __init__(self, path: pathlib.Path, page_content: str) -> None: raise NotImplementedError + @property + @abstractmethod + def path(self) -> pathlib.Path: + raise NotImplementedError + @abstractmethod def save_to_disk(self) -> None: raise NotImplementedError @@ -236,12 +241,16 @@ class ScopedPage(Page): path (pathlib.Path): a `pathlib.Path` object of the page's filepath. page_content (str): the content of the page from the template. """ - self.path = path + self._path = path self.page_content = page_content def __str__(self) -> str: return f"ScopedPage({self.path})" + @property + def path(self) -> pathlib.Path: + return self._path + def save_to_disk(self) -> None: """Save the scoped page to disk and write the `page_content`. From 779e99434f1a913cb51ce8180543d2566214a2e2 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Mon, 1 Nov 2021 20:36:16 +0000 Subject: [PATCH 2/9] adding latest to CLI --- pyproject.toml | 2 +- tembo/__init__.py | 29 +--------------------- tembo/cli/__init__.py | 30 +++++++++++++++++++++++ tembo/{journal => }/cli/cli.py | 41 ++++++++++++++++---------------- tembo/journal/cli/new.py | 0 tembo/journal/pages.py | 2 +- tests/test_journal/test_pages.py | 2 +- 7 files changed, 54 insertions(+), 52 deletions(-) create mode 100644 tembo/cli/__init__.py rename tembo/{journal => }/cli/cli.py (85%) delete mode 100644 tembo/journal/cli/new.py diff --git a/pyproject.toml b/pyproject.toml index c3e2c86..c2626b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,4 +24,4 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -"tembo" = "tembo.cli:run" +"tembo" = "tembo.cli.cli:run" diff --git a/tembo/__init__.py b/tembo/__init__.py index c688a8d..4cd6b98 100644 --- a/tembo/__init__.py +++ b/tembo/__init__.py @@ -1,30 +1,3 @@ -import os - -import panaetius -from panaetius.exceptions import LoggingDirectoryDoesNotExistException +from .journal.pages import ScopedPageCreator, PageCreatorOptions __version__ = "0.1.0" - -if (config_path := os.environ.get("TEMBO_CONFIG")) is not None: - CONFIG = panaetius.Config("tembo", config_path) -else: - CONFIG = panaetius.Config("tembo", "~/tembo/.config", skip_header_init=True) - - -panaetius.set_config(CONFIG, "base_path", "~/tembo") -panaetius.set_config(CONFIG, "template_path", "~/tembo/.templates") -panaetius.set_config(CONFIG, "scopes", {}) -panaetius.set_config(CONFIG, "logging.level", "DEBUG") -panaetius.set_config(CONFIG, "logging.path") - -try: - logger = panaetius.set_logger( - CONFIG, panaetius.SimpleLogger(logging_level=CONFIG.logging_level) - ) -except LoggingDirectoryDoesNotExistException: - _LOGGING_PATH = CONFIG.logging_path - CONFIG.logging_path = "" - logger = panaetius.set_logger( - CONFIG, panaetius.SimpleLogger(logging_level=CONFIG.logging_level) - ) - logger.warning("Logging directory %s does not exist", _LOGGING_PATH) diff --git a/tembo/cli/__init__.py b/tembo/cli/__init__.py new file mode 100644 index 0000000..c688a8d --- /dev/null +++ b/tembo/cli/__init__.py @@ -0,0 +1,30 @@ +import os + +import panaetius +from panaetius.exceptions import LoggingDirectoryDoesNotExistException + +__version__ = "0.1.0" + +if (config_path := os.environ.get("TEMBO_CONFIG")) is not None: + CONFIG = panaetius.Config("tembo", config_path) +else: + CONFIG = panaetius.Config("tembo", "~/tembo/.config", skip_header_init=True) + + +panaetius.set_config(CONFIG, "base_path", "~/tembo") +panaetius.set_config(CONFIG, "template_path", "~/tembo/.templates") +panaetius.set_config(CONFIG, "scopes", {}) +panaetius.set_config(CONFIG, "logging.level", "DEBUG") +panaetius.set_config(CONFIG, "logging.path") + +try: + logger = panaetius.set_logger( + CONFIG, panaetius.SimpleLogger(logging_level=CONFIG.logging_level) + ) +except LoggingDirectoryDoesNotExistException: + _LOGGING_PATH = CONFIG.logging_path + CONFIG.logging_path = "" + logger = panaetius.set_logger( + CONFIG, panaetius.SimpleLogger(logging_level=CONFIG.logging_level) + ) + logger.warning("Logging directory %s does not exist", _LOGGING_PATH) diff --git a/tembo/journal/cli/cli.py b/tembo/cli/cli.py similarity index 85% rename from tembo/journal/cli/cli.py rename to tembo/cli/cli.py index 52561d1..8a59c5d 100644 --- a/tembo/journal/cli/cli.py +++ b/tembo/cli/cli.py @@ -5,7 +5,7 @@ from typing import Collection import click -import tembo +import tembo.cli from tembo.journal import pages from tembo import exceptions @@ -30,8 +30,8 @@ def run(): @click.command(options_metavar="", name="list") def list_all(): """List all scopes defined in the config.yml.""" - _all_scopes = [user_scope["name"] for user_scope in tembo.CONFIG.scopes] - tembo.logger.info( + _all_scopes = [user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes] + tembo.cli.logger.info( "%s names found in config.yml: '%s'", len(_all_scopes), "', '".join(_all_scopes) ) raise SystemExit(0) @@ -85,21 +85,21 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): def _cli_verify_name_exists(scope: str) -> None: - _name_found = scope in [user_scope["name"] for user_scope in tembo.CONFIG.scopes] + _name_found = scope in [user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes] if _name_found: return - if len(tembo.CONFIG.scopes) > 0: + if len(tembo.cli.CONFIG.scopes) > 0: # if the name is missing in the config.yml, raise error cli_message(f"Command {scope} not found in config.yml.") raise SystemExit(0) # raise error if no config.yml found - if pathlib.Path(tembo.CONFIG.config_path).exists(): - tembo.logger.critical( - "Config.yml found in %s is empty - exiting", tembo.CONFIG.config_path + if pathlib.Path(tembo.cli.CONFIG.config_path).exists(): + tembo.cli.logger.critical( + "Config.yml found in %s is empty - exiting", tembo.cli.CONFIG.config_path ) else: - tembo.logger.critical( - "No config.yml found in %s - exiting", tembo.CONFIG.config_path + tembo.cli.logger.critical( + "No config.yml found in %s - exiting", tembo.cli.CONFIG.config_path ) raise SystemExit(1) @@ -118,7 +118,7 @@ def _cli_get_config_scope(scope: str) -> dict: config_scope.update( { option: str(user_scope[option]) - for user_scope in tembo.CONFIG.scopes + for user_scope in tembo.cli.CONFIG.scopes if user_scope["name"] == scope } ) @@ -126,7 +126,7 @@ def _cli_get_config_scope(scope: str) -> dict: if key_error.args[0] in ["example", "template_filename"]: config_scope.update({key_error.args[0]: None}) continue - tembo.logger.critical( + tembo.cli.logger.critical( "Key %s not found in config. yml - exiting", key_error ) raise SystemExit(1) from key_error @@ -135,7 +135,7 @@ def _cli_get_config_scope(scope: str) -> dict: def _cli_show_example(example: bool, config_scope: dict) -> None: if example: - tembo.logger.info( + tembo.cli.logger.info( "Example for 'tembo new %s': %s", config_scope["name"], config_scope["example"] @@ -147,7 +147,7 @@ def _cli_show_example(example: bool, config_scope: dict) -> None: def _cli_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: page_creator_options = pages.PageCreatorOptions( - base_path=tembo.CONFIG.base_path, + base_path=tembo.cli.CONFIG.base_path, page_path=config_scope["path"], filename=config_scope["filename"], extension=config_scope["extension"], @@ -155,26 +155,26 @@ def _cli_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> page example=config_scope["example"], user_input=inputs, template_filename=config_scope["template_filename"], - template_path=tembo.CONFIG.template_path, + template_path=tembo.cli.CONFIG.template_path, ) try: return pages.ScopedPageCreator(page_creator_options).create_page() except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: - tembo.logger.critical(base_path_does_not_exist_error) + tembo.cli.logger.critical(base_path_does_not_exist_error) raise SystemExit(1) from base_path_does_not_exist_error except exceptions.TemplateFileNotFoundError as template_file_not_found_error: - tembo.logger.critical(template_file_not_found_error.args[0]) + tembo.cli.logger.critical(template_file_not_found_error.args[0]) raise SystemExit(1) from template_file_not_found_error except exceptions.MismatchedTokenError as mismatched_token_error: if config_scope["example"] is not None: - tembo.logger.critical( + tembo.cli.logger.critical( "Your tembo config.yml/template specifies %s input tokens, you gave %s. Example: %s", mismatched_token_error.expected, mismatched_token_error.given, config_scope["example"], ) raise SystemExit(1) from mismatched_token_error - tembo.logger.critical( + tembo.cli.logger.critical( "Your tembo config.yml/template specifies %s input tokens, you gave %s", mismatched_token_error.expected, mismatched_token_error.given, @@ -182,7 +182,6 @@ def _cli_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> page raise SystemExit(1) from mismatched_token_error - def cli_message(message: str) -> None: click.echo(f"[TEMBO] {message} 🐘") @@ -193,7 +192,7 @@ run.add_command(list_all) if __name__ == "__main__": # new(["meeting", "robs presentation", "meeting on gcp"]) - new(["meeting", "a", "b", "c", "d"]) + new(["meeting", "a", "b", "c", "d"]) # noqa # new(["meeting", "robs presentation"]) # pyinstaller diff --git a/tembo/journal/cli/new.py b/tembo/journal/cli/new.py deleted file mode 100644 index e69de29..0000000 diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index 536b124..417f969 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -278,4 +278,4 @@ class ScopedPage(Page): with scoped_page_file.open("w", encoding="utf-8") as scoped_page: scoped_page.write(self.page_content) # TODO: pass this back somehow - tembo.logger.info("Saved %s to disk", self.path) + tembo.cli.cli.logger.info("Saved %s to disk", self.path) diff --git a/tests/test_journal/test_pages.py b/tests/test_journal/test_pages.py index 7cf101a..7164fcc 100644 --- a/tests/test_journal/test_pages.py +++ b/tests/test_journal/test_pages.py @@ -3,7 +3,7 @@ import pathlib import pytest -from tembo.journal.pages import PageCreatorOptions, ScopedPageCreator +from tembo import PageCreatorOptions, ScopedPageCreator from tembo import exceptions From 1cc68834f0d85c91a302200024a5288586c5ace2 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Wed, 3 Nov 2021 13:46:58 +0000 Subject: [PATCH 3/9] adding latest --- tembo/cli/__init__.py | 2 +- tembo/cli/cli.py | 65 ++++++++++++------- tembo/exceptions.py | 16 +++++ tembo/journal/pages.py | 11 ++-- tembo/utils/__init__.py | 12 ++++ tests/test_cli/__init__.py | 0 tests/test_cli/data/config/success/config.yml | 7 ++ tests/test_cli/test_cli.py | 50 ++++++++++++++ tests/test_journal/test_pages.py | 43 +++++++----- 9 files changed, 156 insertions(+), 50 deletions(-) create mode 100644 tembo/utils/__init__.py create mode 100644 tests/test_cli/__init__.py create mode 100644 tests/test_cli/data/config/success/config.yml create mode 100644 tests/test_cli/test_cli.py diff --git a/tembo/cli/__init__.py b/tembo/cli/__init__.py index c688a8d..f4076cb 100644 --- a/tembo/cli/__init__.py +++ b/tembo/cli/__init__.py @@ -6,7 +6,7 @@ from panaetius.exceptions import LoggingDirectoryDoesNotExistException __version__ = "0.1.0" if (config_path := os.environ.get("TEMBO_CONFIG")) is not None: - CONFIG = panaetius.Config("tembo", config_path) + CONFIG = panaetius.Config("tembo", config_path, skip_header_init=True) else: CONFIG = panaetius.Config("tembo", "~/tembo/.config", skip_header_init=True) diff --git a/tembo/cli/cli.py b/tembo/cli/cli.py index 8a59c5d..2520823 100644 --- a/tembo/cli/cli.py +++ b/tembo/cli/cli.py @@ -7,6 +7,7 @@ import click import tembo.cli from tembo.journal import pages +from tembo.utils import Success from tembo import exceptions @@ -61,57 +62,71 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): """ # check that the name exists in the config.yml - _cli_verify_name_exists(scope) + try: + _new_verify_name_exists(scope) + except ( + exceptions.ScopeNotFound, + exceptions.EmptyConfigYML, + exceptions.MissingConfigYML, + ) as tembo_exception: + cli_message(tembo_exception.args[0]) + raise SystemExit(0) from tembo_exception # get the scope configuration from the config.yml - config_scope = _cli_get_config_scope(scope) + try: + config_scope = _new_get_config_scope(scope) + except exceptions.MandatoryKeyNotFound as mandatory_key_not_found: + cli_message(mandatory_key_not_found.args[0]) + raise SystemExit(1) from mandatory_key_not_found # if --example flag, return the example to the user - _cli_show_example(example, config_scope) + _new_show_example(example, config_scope) # if the name is in the config.yml, create the scoped page - scoped_page = _cli_create_scoped_page(config_scope, inputs) + scoped_page = _new_create_scoped_page(config_scope, inputs) if dry_run: cli_message(f"{scoped_page.path} will be created") raise SystemExit(0) try: - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() + if isinstance(result, Success): + cli_message(f"Saved {result.message} to disk") raise SystemExit(0) except exceptions.ScopedPageAlreadyExists as scoped_page_already_exists: cli_message(f"File {scoped_page_already_exists}") raise SystemExit(0) from scoped_page_already_exists -def _cli_verify_name_exists(scope: str) -> None: - _name_found = scope in [user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes] +def _new_verify_name_exists(scope: str) -> None: + _name_found = scope in [ + user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes + ] if _name_found: return if len(tembo.cli.CONFIG.scopes) > 0: # if the name is missing in the config.yml, raise error - cli_message(f"Command {scope} not found in config.yml.") - raise SystemExit(0) + raise exceptions.ScopeNotFound(f"Command {scope} not found in config.yml") # raise error if no config.yml found if pathlib.Path(tembo.cli.CONFIG.config_path).exists(): - tembo.cli.logger.critical( - "Config.yml found in %s is empty - exiting", tembo.cli.CONFIG.config_path + raise exceptions.EmptyConfigYML( + f"Config.yml found in {tembo.cli.CONFIG.config_path} is empty" ) - else: - tembo.cli.logger.critical( - "No config.yml found in %s - exiting", tembo.cli.CONFIG.config_path - ) - raise SystemExit(1) + raise exceptions.MissingConfigYML( + f"No config.yml found in {tembo.cli.CONFIG.config_path}" + ) -def _cli_get_config_scope(scope: str) -> dict: +def _new_get_config_scope(scope: str) -> dict: config_scope = {} + optional_keys = ["example", "template_filename"] for option in [ "name", - "example", "path", "filename", "extension", + "example", "template_filename", ]: try: @@ -123,17 +138,19 @@ def _cli_get_config_scope(scope: str) -> dict: } ) except KeyError as key_error: - if key_error.args[0] in ["example", "template_filename"]: + if key_error.args[0] in optional_keys: config_scope.update({key_error.args[0]: None}) continue tembo.cli.logger.critical( "Key %s not found in config. yml - exiting", key_error ) - raise SystemExit(1) from key_error + raise exceptions.MandatoryKeyNotFound( + f"Key {key_error} not found in config.yml" + ) return config_scope -def _cli_show_example(example: bool, config_scope: dict) -> None: +def _new_show_example(example: bool, config_scope: dict) -> None: if example: tembo.cli.logger.info( "Example for 'tembo new %s': %s", @@ -145,9 +162,10 @@ def _cli_show_example(example: bool, config_scope: dict) -> None: raise SystemExit(0) -def _cli_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: +def _new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: page_creator_options = pages.PageCreatorOptions( base_path=tembo.cli.CONFIG.base_path, + template_path=tembo.cli.CONFIG.template_path, page_path=config_scope["path"], filename=config_scope["filename"], extension=config_scope["extension"], @@ -155,7 +173,6 @@ def _cli_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> page example=config_scope["example"], user_input=inputs, template_filename=config_scope["template_filename"], - template_path=tembo.cli.CONFIG.template_path, ) try: return pages.ScopedPageCreator(page_creator_options).create_page() @@ -191,9 +208,7 @@ run.add_command(list_all) if __name__ == "__main__": - # new(["meeting", "robs presentation", "meeting on gcp"]) new(["meeting", "a", "b", "c", "d"]) # noqa - # new(["meeting", "robs presentation"]) # pyinstaller # if getattr(sys, "frozen", False): diff --git a/tembo/exceptions.py b/tembo/exceptions.py index 581fcb0..df6a885 100644 --- a/tembo/exceptions.py +++ b/tembo/exceptions.py @@ -18,3 +18,19 @@ class TemplateFileNotFoundError(Exception): class ScopedPageAlreadyExists(Exception): """Raised if the scoped page file already exists.""" + + +class MissingConfigYML(Exception): + """Raised if the config.yml file is missing.""" + + +class EmptyConfigYML(Exception): + """Raised if the config.yml file is empty.""" + + +class ScopeNotFound(Exception): + """Raised if the scope does not exist in the config.yml.""" + + +class MandatoryKeyNotFound(Exception): + """Raised if a mandatory key is not found in the config.yml.""" diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index 417f969..4810cdf 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -12,6 +12,7 @@ from jinja2.exceptions import TemplateNotFound import tembo from tembo import exceptions +import tembo.utils # TODO: flesh this out with details for the optional args @@ -222,7 +223,7 @@ class Page(metaclass=ABCMeta): raise NotImplementedError @abstractmethod - def save_to_disk(self) -> None: + def save_to_disk(self) -> tembo.utils.Success: raise NotImplementedError @@ -245,13 +246,13 @@ class ScopedPage(Page): self.page_content = page_content def __str__(self) -> str: - return f"ScopedPage({self.path})" + return f"ScopedPage(\"{self.path}\")" @property def path(self) -> pathlib.Path: return self._path - def save_to_disk(self) -> None: + def save_to_disk(self) -> tembo.utils.Success: """Save the scoped page to disk and write the `page_content`. If the page already exists a message will be logged to stdout and no file @@ -274,8 +275,6 @@ class ScopedPage(Page): scoped_page_file.parents[0].mkdir(parents=True, exist_ok=True) if scoped_page_file.exists(): raise exceptions.ScopedPageAlreadyExists(f"{self.path} already exists") - with scoped_page_file.open("w", encoding="utf-8") as scoped_page: scoped_page.write(self.page_content) - # TODO: pass this back somehow - tembo.cli.cli.logger.info("Saved %s to disk", self.path) + return tembo.utils.Success(str(self.path)) diff --git a/tembo/utils/__init__.py b/tembo/utils/__init__.py new file mode 100644 index 0000000..2ab70fe --- /dev/null +++ b/tembo/utils/__init__.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass + + +@dataclass +class Success: + """Success message. + + Attributes: + message (str): A success message. + """ + + message: str diff --git a/tests/test_cli/__init__.py b/tests/test_cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cli/data/config/success/config.yml b/tests/test_cli/data/config/success/config.yml new file mode 100644 index 0000000..8830957 --- /dev/null +++ b/tests/test_cli/data/config/success/config.yml @@ -0,0 +1,7 @@ +tembo: + scopes: + - name: some_scope + example: tembo new some_scope + path: "some_scope" + filename: "{name}" + extension: md diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py new file mode 100644 index 0000000..0eb5fd9 --- /dev/null +++ b/tests/test_cli/test_cli.py @@ -0,0 +1,50 @@ +import os + +import pytest + +import tembo.exceptions + + +def test_cli_page_is_saved_success(): + pass + + +def test_new_verify_name_exists_success(shared_datadir): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + import tembo.cli + from tembo.cli.cli import _new_verify_name_exists + + c = tembo.cli.CONFIG + + # act + verified_name = _new_verify_name_exists("some_scope") + + # assert + assert verified_name is None + + +def test_new_verify_name_exists_scope_not_found(shared_datadir): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + import tembo.cli + from tembo.cli.cli import _new_verify_name_exists + + c = tembo.cli.CONFIG + + # act + with pytest.raises(tembo.exceptions.ScopeNotFound) as scope_not_found: + _new_verify_name_exists("some_missing_scope") + + # assert + assert str(scope_not_found.value) == "Command some_missing_scope not found in config.yml" + + +def test_new_get_config_scope(shared_datadir): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + import tembo.cli + + # act + + # assert diff --git a/tests/test_journal/test_pages.py b/tests/test_journal/test_pages.py index 7164fcc..399c6c8 100644 --- a/tests/test_journal/test_pages.py +++ b/tests/test_journal/test_pages.py @@ -5,6 +5,7 @@ import pytest from tembo import PageCreatorOptions, ScopedPageCreator from tembo import exceptions +from tembo.utils import Success DATE_TODAY = date.today().strftime("%d-%m-%Y") @@ -90,7 +91,7 @@ def test_create_page_already_exists(datadir): # act scoped_page = ScopedPageCreator(options).create_page() with pytest.raises(exceptions.ScopedPageAlreadyExists) as page_already_exists: - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() @@ -99,7 +100,7 @@ def test_create_page_already_exists(datadir): assert scoped_page_contents.readlines() == ["this file already exists\n"] -def test_create_page_without_template(tmpdir, caplog): +def test_create_page_without_template(tmpdir): # arrange options = PageCreatorOptions( base_path=str(tmpdir), @@ -112,18 +113,18 @@ def test_create_page_without_template(tmpdir, caplog): template_filename=None, template_path=None, ) - # TODO: copy this pattern creation into the other tests scoped_page_file = ( pathlib.Path(options.base_path) / options.page_path / options.filename ).with_suffix(f".{options.extension}") # act scoped_page = ScopedPageCreator(options).create_page() - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() - assert caplog.records[0].message == f"Saved {scoped_page_file} to disk" + assert isinstance(result, Success) + assert result.message == str(scoped_page_file) with scoped_page_file.open("r", encoding="utf-8") as scoped_page_contents: assert scoped_page_contents.readlines() == [] @@ -147,11 +148,12 @@ def test_create_page_with_template(datadir, caplog): # act scoped_page = ScopedPageCreator(options).create_page() - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() - assert caplog.records[0].message == f"Saved {scoped_page_file} to disk" + assert isinstance(result, Success) + assert result.message == str(scoped_page_file) with scoped_page_file.open("r", encoding="utf-8") as scoped_page_contents: assert scoped_page_contents.readlines() == [ "scoped page file\n", @@ -193,11 +195,12 @@ def test_create_tokened_page_tokens_in_template( # act scoped_page = ScopedPageCreator(options).create_page() - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() - assert caplog.records[0].message == f"Saved {scoped_page_file} to disk" + assert isinstance(result, Success) + assert result.message == str(scoped_page_file) with scoped_page_file.open("r", encoding="utf-8") as scoped_page_contents: assert scoped_page_contents.readline() == page_contents @@ -236,11 +239,12 @@ def test_create_tokened_page_tokens_in_filename( # act scoped_page = ScopedPageCreator(options).create_page() - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() - assert caplog.records[0].message == f"Saved {scoped_page_file} to disk" + assert isinstance(result, Success) + assert result.message == str(scoped_page_file) def test_create_tokened_page_input_tokens_preserve_order(datadir, caplog): @@ -263,11 +267,12 @@ def test_create_tokened_page_input_tokens_preserve_order(datadir, caplog): # act scoped_page = ScopedPageCreator(options).create_page() - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() - assert caplog.records[0].message == f"Saved {scoped_page_file} to disk" + assert isinstance(result, Success) + assert result.message == str(scoped_page_file) with scoped_page_file.open(mode="r", encoding="utf-8") as scoped_page_contents: assert scoped_page_contents.readline() == "third_input second_input" @@ -324,11 +329,12 @@ def test_create_page_spaces_in_path(tmpdir, caplog): # act scoped_page = ScopedPageCreator(options).create_page() - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() - assert caplog.records[0].message == f"Saved {scoped_page_file} to disk" + assert isinstance(result, Success) + assert result.message == str(scoped_page_file) def test_create_page_dot_in_extension(tmpdir, caplog): @@ -350,11 +356,12 @@ def test_create_page_dot_in_extension(tmpdir, caplog): # act scoped_page = ScopedPageCreator(options).create_page() - scoped_page.save_to_disk() + result = scoped_page.save_to_disk() # assert assert scoped_page_file.exists() - assert caplog.records[0].message == f"Saved {scoped_page_file} to disk" + assert isinstance(result, Success) + assert result.message == str(scoped_page_file) def test_create_page_str_representation(tmpdir): @@ -378,4 +385,4 @@ def test_create_page_str_representation(tmpdir): scoped_page = ScopedPageCreator(options).create_page() # assert - assert str(scoped_page) == f"ScopedPage({scoped_page_file})" + assert str(scoped_page) == f"ScopedPage(\"{scoped_page_file}\")" From bfb4d7fd8f83a4e05013e7bce1af8948c2d79913 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Wed, 3 Nov 2021 14:01:48 +0000 Subject: [PATCH 4/9] adding latest --- tembo/__init__.py | 1 + tests/test_cli/data/config/empty/config.yml | 1 + tests/test_cli/test_cli.py | 59 ++++++++++++++++++--- 3 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 tests/test_cli/data/config/empty/config.yml diff --git a/tembo/__init__.py b/tembo/__init__.py index 4cd6b98..d15ffc9 100644 --- a/tembo/__init__.py +++ b/tembo/__init__.py @@ -1,3 +1,4 @@ from .journal.pages import ScopedPageCreator, PageCreatorOptions +from . import exceptions __version__ = "0.1.0" diff --git a/tests/test_cli/data/config/empty/config.yml b/tests/test_cli/data/config/empty/config.yml new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/tests/test_cli/data/config/empty/config.yml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index 0eb5fd9..ba4539f 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -15,14 +15,15 @@ def test_new_verify_name_exists_success(shared_datadir): import tembo.cli from tembo.cli.cli import _new_verify_name_exists - c = tembo.cli.CONFIG - # act verified_name = _new_verify_name_exists("some_scope") # assert assert verified_name is None + # cleanup + del os.environ["TEMBO_CONFIG"] + def test_new_verify_name_exists_scope_not_found(shared_datadir): # arrange @@ -30,21 +31,65 @@ def test_new_verify_name_exists_scope_not_found(shared_datadir): import tembo.cli from tembo.cli.cli import _new_verify_name_exists - c = tembo.cli.CONFIG - # act with pytest.raises(tembo.exceptions.ScopeNotFound) as scope_not_found: _new_verify_name_exists("some_missing_scope") # assert - assert str(scope_not_found.value) == "Command some_missing_scope not found in config.yml" + assert ( + str(scope_not_found.value) + == "Command some_missing_scope not found in config.yml" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] -def test_new_get_config_scope(shared_datadir): +def test_new_verify_name_exists_empty_config(shared_datadir): # arrange - os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "empty") import tembo.cli + from tembo.cli.cli import _new_verify_name_exists # act + with pytest.raises(tembo.exceptions.EmptyConfigYML) as empty_config_yml: + _new_verify_name_exists("some_missing_scope") # assert + assert ( + str(empty_config_yml.value) + == f'Config.yml found in {os.environ["TEMBO_CONFIG"]} is empty' + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + + +def test_new_verify_name_exists_missing_config(shared_datadir): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing") + import tembo.cli + from tembo.cli.cli import _new_verify_name_exists + + # act + with pytest.raises(tembo.exceptions.MissingConfigYML) as missing_config_yml: + _new_verify_name_exists("some_missing_scope") + + # assert + assert ( + str(missing_config_yml.value) + == f'No config.yml found in {os.environ["TEMBO_CONFIG"]}' + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + + +# def test_new_get_config_scope(shared_datadir): +# # arrange +# os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") +# import tembo.cli + +# # act + +# # assert From 544c90eeed94ca7a1aef97b73b9a15b3fe0fc006 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Wed, 3 Nov 2021 20:48:40 +0000 Subject: [PATCH 5/9] adding latest --- TODO.todo | 7 +++ tembo/cli/cli.py | 3 -- .../data/config/missing_keys/config.yml | 5 ++ .../data/config/optional_keys/config.yml | 6 +++ tests/test_cli/test_cli.py | 51 +++++++++++++++---- 5 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 tests/test_cli/data/config/missing_keys/config.yml create mode 100644 tests/test_cli/data/config/optional_keys/config.yml diff --git a/TODO.todo b/TODO.todo index e6f1e79..1ec3271 100644 --- a/TODO.todo +++ b/TODO.todo @@ -46,6 +46,13 @@ Documentation: ☐ Can prospector ignore tests dir? document this in the gist if so ☐ Redo the documentation on a CLI, reorganise and inocropoate all the new tembo layouts + Testing: + ☐ Document importing in inidivudal tests using `importlib.reload` + Globally import the module + Use `importlib.reload(module)` in each test instead of explicitly importing the module. + This is because the import is cached. + + Functionality: ☐ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages? Define a format: [TEMBO:$datetime] $message 🐘 - document this in general python for CLI diff --git a/tembo/cli/cli.py b/tembo/cli/cli.py index 2520823..2ce4c1d 100644 --- a/tembo/cli/cli.py +++ b/tembo/cli/cli.py @@ -141,9 +141,6 @@ def _new_get_config_scope(scope: str) -> dict: if key_error.args[0] in optional_keys: config_scope.update({key_error.args[0]: None}) continue - tembo.cli.logger.critical( - "Key %s not found in config. yml - exiting", key_error - ) raise exceptions.MandatoryKeyNotFound( f"Key {key_error} not found in config.yml" ) diff --git a/tests/test_cli/data/config/missing_keys/config.yml b/tests/test_cli/data/config/missing_keys/config.yml new file mode 100644 index 0000000..6f6a2f8 --- /dev/null +++ b/tests/test_cli/data/config/missing_keys/config.yml @@ -0,0 +1,5 @@ +tembo: + scopes: + - name: some_scope + path: "some_scope" + extension: md diff --git a/tests/test_cli/data/config/optional_keys/config.yml b/tests/test_cli/data/config/optional_keys/config.yml new file mode 100644 index 0000000..2af3099 --- /dev/null +++ b/tests/test_cli/data/config/optional_keys/config.yml @@ -0,0 +1,6 @@ +tembo: + scopes: + - name: some_scope + path: "some_scope" + filename: "{name}" + extension: md diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index ba4539f..2d56814 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -1,8 +1,11 @@ +import importlib import os import pytest import tembo.exceptions +import tembo.cli +from tembo.cli.cli import _new_verify_name_exists, _new_get_config_scope def test_cli_page_is_saved_success(): @@ -12,8 +15,7 @@ def test_cli_page_is_saved_success(): def test_new_verify_name_exists_success(shared_datadir): # arrange os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") - import tembo.cli - from tembo.cli.cli import _new_verify_name_exists + importlib.reload(tembo.cli) # act verified_name = _new_verify_name_exists("some_scope") @@ -28,7 +30,7 @@ def test_new_verify_name_exists_success(shared_datadir): def test_new_verify_name_exists_scope_not_found(shared_datadir): # arrange os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") - import tembo.cli + importlib.reload(tembo.cli) from tembo.cli.cli import _new_verify_name_exists # act @@ -48,7 +50,7 @@ def test_new_verify_name_exists_scope_not_found(shared_datadir): def test_new_verify_name_exists_empty_config(shared_datadir): # arrange os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "empty") - import tembo.cli + importlib.reload(tembo.cli) from tembo.cli.cli import _new_verify_name_exists # act @@ -68,7 +70,7 @@ def test_new_verify_name_exists_empty_config(shared_datadir): def test_new_verify_name_exists_missing_config(shared_datadir): # arrange os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing") - import tembo.cli + importlib.reload(tembo.cli) from tembo.cli.cli import _new_verify_name_exists # act @@ -85,11 +87,38 @@ def test_new_verify_name_exists_missing_config(shared_datadir): del os.environ["TEMBO_CONFIG"] -# def test_new_get_config_scope(shared_datadir): -# # arrange -# os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") -# import tembo.cli +def test_new_get_config_scope_success(shared_datadir): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "optional_keys") + importlib.reload(tembo.cli) -# # act + # act + config_scope = _new_get_config_scope("some_scope") -# # assert + # assert + assert config_scope == { + "name": "some_scope", + "path": "some_scope", + "filename": "{name}", + "extension": "md", + "example": None, + "template_filename": None, + } + + +def test_new_get_config_scope_key_not_found(shared_datadir): + # arrange + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing_keys") + importlib.reload(tembo.cli) + + # act + with pytest.raises( + tembo.exceptions.MandatoryKeyNotFound + ) as mandatory_key_not_found: + config_scope = _new_get_config_scope("some_scope") + + # assert + assert ( + str(mandatory_key_not_found.value) == "Key 'filename' not found in config.yml" + ) From 83db380fecfddf29b6e8635c88112ed2ef21d62c Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Thu, 4 Nov 2021 00:01:38 +0000 Subject: [PATCH 6/9] adding latest --- TODO.todo | 7 +++++++ tembo/cli/cli.py | 13 ++++++------- tests/test_cli/test_cli.py | 29 +++++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/TODO.todo b/TODO.todo index 1ec3271..05ddf5f 100644 --- a/TODO.todo +++ b/TODO.todo @@ -42,6 +42,11 @@ Documentation: Overwrite `__init__`, access them in pytest with `.value.$args` Access them in a try,except with `raise $excpetion as $name; $name.$arg` + ☐ Document capturing stdout + Use `capsys` + `assert capsys.readouterr().out` + A new line may be inserted if using `click.echo()` + ☐ Document using datadir with a module rather than a shared one. Link to tembo as an example. ☐ Can prospector ignore tests dir? document this in the gist if so ☐ Redo the documentation on a CLI, reorganise and inocropoate all the new tembo layouts @@ -68,6 +73,8 @@ Functionality: ☐ Update poetry ☐ Build docs ☐ Document using Duty + ☐ Duty for auto insert version from `poetry version`. + Need to decide what file to place `__version__` in. Logging: ☐ Make all internal tembo logs be debug diff --git a/tembo/cli/cli.py b/tembo/cli/cli.py index 2ce4c1d..b2e47a8 100644 --- a/tembo/cli/cli.py +++ b/tembo/cli/cli.py @@ -149,13 +149,12 @@ def _new_get_config_scope(scope: str) -> dict: def _new_show_example(example: bool, config_scope: dict) -> None: if example: - tembo.cli.logger.info( - "Example for 'tembo new %s': %s", - config_scope["name"], - config_scope["example"] - if isinstance(config_scope["example"], str) - else "No example in config.yml", - ) + if isinstance(config_scope["example"], str): + cli_message( + f'Example for {config_scope["name"]}: {config_scope["example"]}' + ) + else: + cli_message("No example in config.yml") raise SystemExit(0) diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index 2d56814..039e12f 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -5,7 +5,11 @@ import pytest import tembo.exceptions import tembo.cli -from tembo.cli.cli import _new_verify_name_exists, _new_get_config_scope +from tembo.cli.cli import ( + _new_verify_name_exists, + _new_get_config_scope, + _new_show_example, +) def test_cli_page_is_saved_success(): @@ -107,7 +111,6 @@ def test_new_get_config_scope_success(shared_datadir): def test_new_get_config_scope_key_not_found(shared_datadir): - # arrange # arrange os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing_keys") importlib.reload(tembo.cli) @@ -122,3 +125,25 @@ def test_new_get_config_scope_key_not_found(shared_datadir): assert ( str(mandatory_key_not_found.value) == "Key 'filename' not found in config.yml" ) + + +@pytest.mark.parametrize( + "path,message", + [ + ("success", "[TEMBO] Example for some_scope: tembo new some_scope 🐘\n"), + ("optional_keys", "[TEMBO] No example in config.yml 🐘\n"), + ], +) +def test_new_show_example(path, message, shared_datadir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / path) + importlib.reload(tembo.cli) + config_scope = _new_get_config_scope("some_scope") + + # act + with pytest.raises(SystemExit) as system_exit: + _new_show_example(True, config_scope) + + # assert + assert capsys.readouterr().out == message + assert system_exit.value.code == 0 From 37657563a09581cb5af79166634de94b796882e5 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Thu, 4 Nov 2021 08:39:00 +0000 Subject: [PATCH 7/9] adding latest --- TODO.todo | 1 + tembo/cli/cli.py | 18 +++---- tests/test_cli/test_cli.py | 96 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 11 deletions(-) diff --git a/TODO.todo b/TODO.todo index 05ddf5f..2ad76f5 100644 --- a/TODO.todo +++ b/TODO.todo @@ -46,6 +46,7 @@ Documentation: Use `capsys` `assert capsys.readouterr().out` A new line may be inserted if using `click.echo()` + ☐ Document using datadir with a module rather than a shared one. Link to tembo as an example. ☐ Can prospector ignore tests dir? document this in the gist if so diff --git a/tembo/cli/cli.py b/tembo/cli/cli.py index b2e47a8..0014fea 100644 --- a/tembo/cli/cli.py +++ b/tembo/cli/cli.py @@ -173,25 +173,21 @@ def _new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> page try: return pages.ScopedPageCreator(page_creator_options).create_page() except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: - tembo.cli.logger.critical(base_path_does_not_exist_error) + cli_message(base_path_does_not_exist_error.args[0]) raise SystemExit(1) from base_path_does_not_exist_error except exceptions.TemplateFileNotFoundError as template_file_not_found_error: - tembo.cli.logger.critical(template_file_not_found_error.args[0]) + cli_message(template_file_not_found_error.args[0]) raise SystemExit(1) from template_file_not_found_error except exceptions.MismatchedTokenError as mismatched_token_error: if config_scope["example"] is not None: - tembo.cli.logger.critical( - "Your tembo config.yml/template specifies %s input tokens, you gave %s. Example: %s", - mismatched_token_error.expected, - mismatched_token_error.given, - config_scope["example"], + cli_message( + f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}. Example: {config_scope["example"]}' ) raise SystemExit(1) from mismatched_token_error - tembo.cli.logger.critical( - "Your tembo config.yml/template specifies %s input tokens, you gave %s", - mismatched_token_error.expected, - mismatched_token_error.given, + cli_message( + f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}' ) + raise SystemExit(1) from mismatched_token_error diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index 039e12f..de08919 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -1,5 +1,6 @@ import importlib import os +import pathlib import pytest @@ -9,6 +10,7 @@ from tembo.cli.cli import ( _new_verify_name_exists, _new_get_config_scope, _new_show_example, + _new_create_scoped_page, ) @@ -147,3 +149,97 @@ def test_new_show_example(path, message, shared_datadir, capsys): # assert assert capsys.readouterr().out == message assert system_exit.value.code == 0 + + +def test_new_create_scoped_page_success(shared_datadir, tmpdir): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + config_scope = _new_get_config_scope("some_scope") + inputs = () + scoped_page_file = pathlib.Path(tmpdir / "some_scope" / "some_scope").with_suffix( + ".md" + ) + + # act + scoped_page = _new_create_scoped_page(config_scope, inputs) + + # assert + assert scoped_page.path == scoped_page_file + assert scoped_page.page_content == "" + + +def test_new_create_scoped_page_base_path_does_not_exist( + shared_datadir, tmpdir, capsys +): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir / "nonexistent" / "path") + importlib.reload(tembo.cli) + + config_scope = _new_get_config_scope("some_scope") + inputs = () + + # act + with pytest.raises(SystemExit) as system_exit: + _new_create_scoped_page(config_scope, inputs) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f'[TEMBO] Tembo base path of {os.environ["TEMBO_BASE_PATH"]} does not exist. 🐘\n' + ) + + +def test_new_create_scoped_page_template_file_does_not_exist( + shared_datadir, tmpdir, capsys +): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + os.environ["TEMBO_TEMPLATE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + config_scope = _new_get_config_scope("some_scope") + config_scope["template_filename"] = "some_nonexistent_template.md.tpl" + inputs = () + + # act + with pytest.raises(SystemExit) as system_exit: + _new_create_scoped_page(config_scope, inputs) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f'[TEMBO] Template file {os.environ["TEMBO_TEMPLATE_PATH"]}/{config_scope["template_filename"]} does not exist. 🐘\n' + ) + + +@pytest.mark.parametrize("example", [(True,), (False,)]) +def test_new_create_scoped_page_mismatched_token( + example, shared_datadir, tmpdir, capsys +): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + config_scope = _new_get_config_scope("some_scope") + inputs = ("some_input",) + if not example: + config_scope["example"] = None + + # act + with pytest.raises(SystemExit) as system_exit: + _new_create_scoped_page(config_scope, inputs) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1. Example: tembo new some_scope 🐘\n' + ) From 240ba6f9737674c1306308ca140f6011fabfc3a9 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Thu, 4 Nov 2021 09:08:05 +0000 Subject: [PATCH 8/9] adding latest --- tests/test_cli/test_cli.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index de08919..b4f14a9 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -230,7 +230,7 @@ def test_new_create_scoped_page_mismatched_token( config_scope = _new_get_config_scope("some_scope") inputs = ("some_input",) - if not example: + if not example[0]: config_scope["example"] = None # act @@ -239,7 +239,13 @@ def test_new_create_scoped_page_mismatched_token( # assert assert system_exit.value.code == 1 - assert ( - capsys.readouterr().out - == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1. Example: tembo new some_scope 🐘\n' - ) + if not example[0]: + assert ( + capsys.readouterr().out + == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1 🐘\n' + ) + else: + assert ( + capsys.readouterr().out + == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1. Example: tembo new some_scope 🐘\n' + ) From 70a62ab70a3dbc0b7e94c98d4cd9ef2aeb6c1d80 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Thu, 4 Nov 2021 22:33:27 +0000 Subject: [PATCH 9/9] adding latest tests + refactored cli --- tembo/cli/cli.py | 86 +++---- tests/test_cli/data/config/success/config.yml | 5 + tests/test_cli/data/some_scope/some_scope.md | 1 + tests/test_cli/test_cli.py | 236 +++++++++++++++++- 4 files changed, 267 insertions(+), 61 deletions(-) create mode 100644 tests/test_cli/data/some_scope/some_scope.md diff --git a/tembo/cli/cli.py b/tembo/cli/cli.py index 0014fea..e0df85b 100644 --- a/tembo/cli/cli.py +++ b/tembo/cli/cli.py @@ -32,9 +32,8 @@ def run(): def list_all(): """List all scopes defined in the config.yml.""" _all_scopes = [user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes] - tembo.cli.logger.info( - "%s names found in config.yml: '%s'", len(_all_scopes), "', '".join(_all_scopes) - ) + _all_scopes_joined = "', '".join(_all_scopes) + cli_message(f"{len(_all_scopes)} names found in config.yml: '{_all_scopes_joined}'") raise SystemExit(0) @@ -70,7 +69,7 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): exceptions.MissingConfigYML, ) as tembo_exception: cli_message(tembo_exception.args[0]) - raise SystemExit(0) from tembo_exception + raise SystemExit(1) from tembo_exception # get the scope configuration from the config.yml try: @@ -83,7 +82,7 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): _new_show_example(example, config_scope) # if the name is in the config.yml, create the scoped page - scoped_page = _new_create_scoped_page(config_scope, inputs) + scoped_page = new_create_scoped_page(config_scope, inputs) if dry_run: cli_message(f"{scoped_page.path} will be created") @@ -99,6 +98,39 @@ def new(scope: str, inputs: Collection[str], dry_run: bool, example: bool): raise SystemExit(0) from scoped_page_already_exists +def new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: + page_creator_options = pages.PageCreatorOptions( + base_path=tembo.cli.CONFIG.base_path, + template_path=tembo.cli.CONFIG.template_path, + page_path=config_scope["path"], + filename=config_scope["filename"], + extension=config_scope["extension"], + name=config_scope["name"], + example=config_scope["example"], + user_input=inputs, + template_filename=config_scope["template_filename"], + ) + try: + return pages.ScopedPageCreator(page_creator_options).create_page() + except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: + cli_message(base_path_does_not_exist_error.args[0]) + raise SystemExit(1) from base_path_does_not_exist_error + except exceptions.TemplateFileNotFoundError as template_file_not_found_error: + cli_message(template_file_not_found_error.args[0]) + raise SystemExit(1) from template_file_not_found_error + except exceptions.MismatchedTokenError as mismatched_token_error: + if config_scope["example"] is not None: + cli_message( + f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}. Example: {config_scope["example"]}' + ) + raise SystemExit(1) from mismatched_token_error + cli_message( + f"Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}" + ) + + raise SystemExit(1) from mismatched_token_error + + def _new_verify_name_exists(scope: str) -> None: _name_found = scope in [ user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes @@ -107,7 +139,7 @@ def _new_verify_name_exists(scope: str) -> None: return if len(tembo.cli.CONFIG.scopes) > 0: # if the name is missing in the config.yml, raise error - raise exceptions.ScopeNotFound(f"Command {scope} not found in config.yml") + raise exceptions.ScopeNotFound(f"Scope {scope} not found in config.yml") # raise error if no config.yml found if pathlib.Path(tembo.cli.CONFIG.config_path).exists(): raise exceptions.EmptyConfigYML( @@ -158,51 +190,9 @@ def _new_show_example(example: bool, config_scope: dict) -> None: raise SystemExit(0) -def _new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages.Page: - page_creator_options = pages.PageCreatorOptions( - base_path=tembo.cli.CONFIG.base_path, - template_path=tembo.cli.CONFIG.template_path, - page_path=config_scope["path"], - filename=config_scope["filename"], - extension=config_scope["extension"], - name=config_scope["name"], - example=config_scope["example"], - user_input=inputs, - template_filename=config_scope["template_filename"], - ) - try: - return pages.ScopedPageCreator(page_creator_options).create_page() - except exceptions.BasePathDoesNotExistError as base_path_does_not_exist_error: - cli_message(base_path_does_not_exist_error.args[0]) - raise SystemExit(1) from base_path_does_not_exist_error - except exceptions.TemplateFileNotFoundError as template_file_not_found_error: - cli_message(template_file_not_found_error.args[0]) - raise SystemExit(1) from template_file_not_found_error - except exceptions.MismatchedTokenError as mismatched_token_error: - if config_scope["example"] is not None: - cli_message( - f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}. Example: {config_scope["example"]}' - ) - raise SystemExit(1) from mismatched_token_error - cli_message( - f'Your tembo config.yml/template specifies {mismatched_token_error.expected} input tokens, you gave {mismatched_token_error.given}' - ) - - raise SystemExit(1) from mismatched_token_error - - def cli_message(message: str) -> None: click.echo(f"[TEMBO] {message} 🐘") run.add_command(new) run.add_command(list_all) - - -if __name__ == "__main__": - new(["meeting", "a", "b", "c", "d"]) # noqa - - # pyinstaller - # if getattr(sys, "frozen", False): - # run(sys.argv[1:]) - # run(sys.argv[1:]) diff --git a/tests/test_cli/data/config/success/config.yml b/tests/test_cli/data/config/success/config.yml index 8830957..0550bbe 100644 --- a/tests/test_cli/data/config/success/config.yml +++ b/tests/test_cli/data/config/success/config.yml @@ -5,3 +5,8 @@ path: "some_scope" filename: "{name}" extension: md + - name: another_some_scope + example: tembo new another_some_scope + path: "another_some_scope" + filename: "{name}" + extension: md diff --git a/tests/test_cli/data/some_scope/some_scope.md b/tests/test_cli/data/some_scope/some_scope.md new file mode 100644 index 0000000..ce7e948 --- /dev/null +++ b/tests/test_cli/data/some_scope/some_scope.md @@ -0,0 +1 @@ +already exists diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index b4f14a9..a89346a 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -10,14 +10,12 @@ from tembo.cli.cli import ( _new_verify_name_exists, _new_get_config_scope, _new_show_example, - _new_create_scoped_page, + new_create_scoped_page, + new, + list_all, ) -def test_cli_page_is_saved_success(): - pass - - def test_new_verify_name_exists_success(shared_datadir): # arrange os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") @@ -45,8 +43,7 @@ def test_new_verify_name_exists_scope_not_found(shared_datadir): # assert assert ( - str(scope_not_found.value) - == "Command some_missing_scope not found in config.yml" + str(scope_not_found.value) == "Scope some_missing_scope not found in config.yml" ) # cleanup @@ -111,6 +108,9 @@ def test_new_get_config_scope_success(shared_datadir): "template_filename": None, } + # cleanup + del os.environ["TEMBO_CONFIG"] + def test_new_get_config_scope_key_not_found(shared_datadir): # arrange @@ -128,6 +128,9 @@ def test_new_get_config_scope_key_not_found(shared_datadir): str(mandatory_key_not_found.value) == "Key 'filename' not found in config.yml" ) + # cleanup + del os.environ["TEMBO_CONFIG"] + @pytest.mark.parametrize( "path,message", @@ -150,6 +153,9 @@ def test_new_show_example(path, message, shared_datadir, capsys): assert capsys.readouterr().out == message assert system_exit.value.code == 0 + # cleanup + del os.environ["TEMBO_CONFIG"] + def test_new_create_scoped_page_success(shared_datadir, tmpdir): # arrange @@ -164,12 +170,16 @@ def test_new_create_scoped_page_success(shared_datadir, tmpdir): ) # act - scoped_page = _new_create_scoped_page(config_scope, inputs) + scoped_page = new_create_scoped_page(config_scope, inputs) # assert assert scoped_page.path == scoped_page_file assert scoped_page.page_content == "" + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + def test_new_create_scoped_page_base_path_does_not_exist( shared_datadir, tmpdir, capsys @@ -184,7 +194,7 @@ def test_new_create_scoped_page_base_path_does_not_exist( # act with pytest.raises(SystemExit) as system_exit: - _new_create_scoped_page(config_scope, inputs) + new_create_scoped_page(config_scope, inputs) # assert assert system_exit.value.code == 1 @@ -193,6 +203,10 @@ def test_new_create_scoped_page_base_path_does_not_exist( == f'[TEMBO] Tembo base path of {os.environ["TEMBO_BASE_PATH"]} does not exist. 🐘\n' ) + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + def test_new_create_scoped_page_template_file_does_not_exist( shared_datadir, tmpdir, capsys @@ -209,7 +223,7 @@ def test_new_create_scoped_page_template_file_does_not_exist( # act with pytest.raises(SystemExit) as system_exit: - _new_create_scoped_page(config_scope, inputs) + new_create_scoped_page(config_scope, inputs) # assert assert system_exit.value.code == 1 @@ -218,6 +232,10 @@ def test_new_create_scoped_page_template_file_does_not_exist( == f'[TEMBO] Template file {os.environ["TEMBO_TEMPLATE_PATH"]}/{config_scope["template_filename"]} does not exist. 🐘\n' ) + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + @pytest.mark.parametrize("example", [(True,), (False,)]) def test_new_create_scoped_page_mismatched_token( @@ -235,17 +253,209 @@ def test_new_create_scoped_page_mismatched_token( # act with pytest.raises(SystemExit) as system_exit: - _new_create_scoped_page(config_scope, inputs) + new_create_scoped_page(config_scope, inputs) # assert assert system_exit.value.code == 1 if not example[0]: assert ( capsys.readouterr().out - == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1 🐘\n' + == f"[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1 🐘\n" ) else: assert ( capsys.readouterr().out - == f'[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1. Example: tembo new some_scope 🐘\n' + == f"[TEMBO] Your tembo config.yml/template specifies 0 input tokens, you gave 1. Example: tembo new some_scope 🐘\n" ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_dry_run(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scope = "some_scope" + dry_run = "--dry-run" + + # act + with pytest.raises(SystemExit) as system_exit: + new([scope, dry_run]) + + # assert + assert system_exit.value.code == 0 + assert ( + capsys.readouterr().out + == f"[TEMBO] {tmpdir}/some_scope/some_scope.md will be created 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_success(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path(tmpdir / "some_scope" / "some_scope").with_suffix( + ".md" + ) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_scope"]) + + # assert + assert scoped_page_file.exists() + assert system_exit.value.code == 0 + assert capsys.readouterr().out == f"[TEMBO] Saved {scoped_page_file} to disk 🐘\n" + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_success_already_exists(shared_datadir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(shared_datadir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path( + shared_datadir / "some_scope" / "some_scope" + ).with_suffix(".md") + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_scope"]) + + # assert + assert scoped_page_file.exists() + assert system_exit.value.code == 0 + assert ( + capsys.readouterr().out == f"[TEMBO] File {scoped_page_file} already exists 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_scope_not_found(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path(tmpdir / "some_scope" / "some_scope").with_suffix( + ".md" + ) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_nonexistent_scope"]) + + # assert + assert not scoped_page_file.exists() + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f"[TEMBO] Scope some_nonexistent_scope not found in config.yml 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_empty_config(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "empty") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_nonexistent_scope"]) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f"[TEMBO] Config.yml found in {shared_datadir}/config/empty is empty 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_missing_config(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_nonexistent_scope"]) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out + == f"[TEMBO] No config.yml found in {shared_datadir}/config/missing 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_new_missing_mandatory_key(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "missing_keys") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + + # act + with pytest.raises(SystemExit) as system_exit: + new(["some_scope"]) + + # assert + assert system_exit.value.code == 1 + assert ( + capsys.readouterr().out == f"[TEMBO] Key 'filename' not found in config.yml 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"] + + +def test_list_all_success(shared_datadir, tmpdir, capsys): + # arrange + os.environ["TEMBO_CONFIG"] = str(shared_datadir / "config" / "success") + os.environ["TEMBO_BASE_PATH"] = str(tmpdir) + importlib.reload(tembo.cli) + scoped_page_file = pathlib.Path(tmpdir / "some_scope" / "some_scope").with_suffix( + ".md" + ) + + # act + with pytest.raises(SystemExit) as system_exit: + list_all([]) + + # assert + assert system_exit.value.code == 0 + assert ( + capsys.readouterr().out + == f"[TEMBO] 2 names found in config.yml: 'some_scope', 'another_some_scope' 🐘\n" + ) + + # cleanup + del os.environ["TEMBO_CONFIG"] + del os.environ["TEMBO_BASE_PATH"]