From c62d395df2761f8f54e155a71f8a29fe760aa7a1 Mon Sep 17 00:00:00 2001 From: Daniel Tomlinson Date: Tue, 9 Nov 2021 22:46:45 +0000 Subject: [PATCH] adding latest --- TODO.todo | 33 +++++++------ dev/notes/custom_exceptions.md | 39 +++++++++++++++ dev/notes/test.md | 39 --------------- poetry.lock | 90 +++++++++++++++++----------------- pyproject.toml | 5 +- tembo/__main__.py | 7 +++ tembo/cli/__init__.py | 1 + tembo/cli/cli.py | 29 ++++------- tembo/journal/pages.py | 2 + 9 files changed, 125 insertions(+), 120 deletions(-) create mode 100644 dev/notes/custom_exceptions.md delete mode 100644 dev/notes/test.md create mode 100644 tembo/__main__.py diff --git a/TODO.todo b/TODO.todo index 2ad76f5..cffc990 100644 --- a/TODO.todo +++ b/TODO.todo @@ -1,16 +1,19 @@ Priority: - ☐ Write the tests + ✔ Write the tests @done(21-11-07 15:36) ☐ test logs: document this ☐ Docstrings + ☐ Update trilium with latest docstrings (documenting __init__ at class level etc) + Make sure the gist is updated for prospector with the right ignores Documentation: + ☐ documented poetry with extras (panaetius `pyproject.toml`) Docstrings: ☐ Use Duty to write module docstrings ☐ Use Duty to add Class docstrings ☐ Document these in Trilium and rewrite the docstrings notes ☐ Add the comment on Reddit (artie buco?) about imports in a module - ☐ Document using `__main__.py` and `cli.py` + ✔ Document using `__main__.py` and `cli.py` @done(21-11-07 15:21) Use Duty as an example ☐ Document regex usage @@ -31,14 +34,14 @@ Documentation: reading `.value.code` reading `str(.value)` - ☐ Document working with exceptions - ☐ General pattern - raise exceptions in codebase, catch them in the CLI. + ✔ Document working with exceptions @done(21-11-09 22:17) + ✔ General pattern - raise exceptions in codebase, catch them in the CLI. @done(21-11-09 22:16) Allows people to use via an API and handle the exceptions themselves. You can use python builtins but custom exceptions are better for internal control - ☐ Capturing exceptions in the CLI. + ✔ Capturing exceptions in the CLI. @done(21-11-09 22:16) Access the message of the exception with `.args[0]`. use `raise SystemExit(1) from exception` in order to gracefully exit - ☐ Adding custom args to an exception + ✔ Adding custom args to an exception @done(21-11-09 22:17) Overwrite `__init__`, access them in pytest with `.value.$args` Access them in a try,except with `raise $excpetion as $name; $name.$arg` @@ -60,13 +63,13 @@ Documentation: Functionality: - ☐ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages? + ✔ Replace loggers with `click.echo` for command outputs. Keep logging messages for actual logging messages? @done(21-11-09 22:17) Define a format: [TEMBO:$datetime] $message 🐘 - document this in general python for CLI - ☐ Refactor the tembo new command so the cli is split out into manageable methods - ☐ Use the complicated CLI example so the tembo new has its own module to define functions in - ☐ Replace all logger errors with exceptions, move logger messages to the cli. - ☐ How to pass a successful save notification back to the CLI? Return a bool? Or is there some other way? - ☐ Replace pendulum with datetime + ✔ Refactor the tembo new command so the cli is split out into manageable methods @done(21-11-07 15:35) + ✘ Use the complicated CLI example so the tembo new has its own module to define functions in @cancelled(21-11-07 15:35) + ✔ Replace all logger errors with exceptions, move logger messages to the cli. @done(21-11-07 15:35) + ✔ How to pass a successful save notification back to the CLI? Return a bool? Or is there some other way? @done(21-11-07 15:35) + ✘ Replace pendulum with datetime @cancelled(21-11-07 15:35) ✔ Make options a property on the class, add to abstract @done(21-10-30 19:31) ☐ Use the python runner Duty @@ -78,8 +81,8 @@ Functionality: Need to decide what file to place `__version__` in. Logging: - ☐ Make all internal tembo logs be debug - ☐ User can enable them with the config + ✘ Make all internal tembo logs be debug @cancelled(21-11-09 22:20) + ✘ User can enable them with the config @cancelled(21-11-09 22:20) VSCode: PyInstaller: @@ -92,7 +95,7 @@ VSCode: walrus := Tests: - ☐ Write tests! @2d + ✔ Write tests! @2d @done(21-11-07 15:36) Use coverage as going along to make sure all bases are covered in the testing VSCode: diff --git a/dev/notes/custom_exceptions.md b/dev/notes/custom_exceptions.md new file mode 100644 index 0000000..8869e23 --- /dev/null +++ b/dev/notes/custom_exceptions.md @@ -0,0 +1,39 @@ +Custom exceptions + +Create an `./exceptions.py`. + +Define any custom exceptions. Can append `Error` to the exception name if needed, otherwise be descriptive. + +E.g `MismatchedTokenError`, `ScopedPageAlreadyExists`. + +(If it would raise a `SystemExit(1)` use an Error). + + +Can set custom attributes on the Exception: + +``` +class MismatchedTokenError(Exception): + def __init__(self, expected: int, given: int) -> None: + self.expected = expected + self.given = given + super().__init__() +``` + +Can refer to these when capturing the exception: + +``` +except exceptions.MismatchedTokenError as exec_info: + click.echo(exec_info.expected, exec_info.given) + # error handling +``` + +Alternatively you can pass arbitrary `*args` into the exceptions and capture them with `args[0]`: + +``` +raise exceptions.TemplateNotFoundError("some message") +``` + +``` +except exceptions.TemplateFileNotFoundError as exec_info: + cli_message(exec_info.args[0]) +``` diff --git a/dev/notes/test.md b/dev/notes/test.md deleted file mode 100644 index 7566c82..0000000 --- a/dev/notes/test.md +++ /dev/null @@ -1,39 +0,0 @@ -# testing notes - -## options - -optional: - -- user_input -- example -- template_filename -- template_path - -required: - -- base_path -- page_path -- filename -- extension -- name - -## tests to write - -- user input does not match number of input tokens - - no user input - - mismatched user input - - with/without example - -- dry run - -## tests done - -- path/page filenames can contain spaces and they are converted -- user input is None -- page using/not using input tokens -- page using/not using date tokens -- page using/not using name tokens -- page with/without a template -- the given base path does not exist -- the given template file does not exist -- page already exists diff --git a/poetry.lock b/poetry.lock index e9a558f..6d9ee43 100644 --- a/poetry.lock +++ b/poetry.lock @@ -25,7 +25,7 @@ tests = ["tox (>=2.6.0)", "pytest (>=3.0.3)", "pytest-cov (>=2.3.1)"] name = "astroid" version = "2.8.4" description = "An abstract syntax tree for Python with inference support." -category = "dev" +category = "main" optional = false python-versions = "~=3.6" @@ -38,7 +38,7 @@ wrapt = ">=1.11,<1.14" name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -46,7 +46,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "attrs" version = "21.2.0" description = "Classes Without Boilerplate" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -60,7 +60,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> name = "bandit" version = "1.7.0" description = "Security oriented static analyser for python code." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -105,7 +105,7 @@ toml = ["tomli"] name = "dodgy" version = "0.2.1" description = "Dodgy: Searches for dodgy looking lines in Python code" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -137,7 +137,7 @@ ptyprocess = {version = ">=0.6,<1.0", markers = "sys_platform != \"win32\""} name = "flake8" version = "2.3.0" description = "the modular source code checker: pep8, pyflakes and co" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -150,7 +150,7 @@ pyflakes = ">=0.8.1" name = "flake8-polyfill" version = "1.0.2" description = "Polyfill package for Flake8 plugins" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -169,7 +169,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "gitdb" version = "4.0.9" description = "Git Object Database" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -180,7 +180,7 @@ smmap = ">=3.0.1,<6" name = "gitpython" version = "3.1.24" description = "GitPython is a python library used to interact with Git repositories" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -192,15 +192,15 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\" name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" +category = "dev" optional = false python-versions = "*" [[package]] name = "isort" -version = "5.9.3" +version = "5.10.0" description = "A Python utility / library to sort Python imports." -category = "dev" +category = "main" optional = false python-versions = ">=3.6.1,<4.0" @@ -228,7 +228,7 @@ i18n = ["Babel (>=2.7)"] name = "lazy-object-proxy" version = "1.6.0" description = "A fast and thorough lazy object proxy." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" @@ -255,7 +255,7 @@ python-versions = ">=3.6" name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -263,7 +263,7 @@ python-versions = "*" name = "mypy" version = "0.910" description = "Optional static typing for Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -280,7 +280,7 @@ python2 = ["typed-ast (>=1.4.0,<1.5.0)"] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" +category = "main" optional = false python-versions = "*" @@ -288,7 +288,7 @@ python-versions = "*" name = "packaging" version = "21.2" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -316,7 +316,7 @@ url = "../panaetius" name = "pbr" version = "5.6.0" description = "Python Build Reasonableness" -category = "dev" +category = "main" optional = false python-versions = ">=2.6" @@ -347,7 +347,7 @@ pytzdata = ">=2020.1" name = "pep8" version = "1.7.1" description = "Python style guide checker" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -355,7 +355,7 @@ python-versions = "*" name = "pep8-naming" version = "0.10.0" description = "Check PEP-8 naming conventions, plugin for flake8" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -366,7 +366,7 @@ flake8-polyfill = ">=1.0.2,<2" name = "platformdirs" version = "2.4.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -378,7 +378,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -390,7 +390,7 @@ testing = ["pytest", "pytest-benchmark"] name = "prospector" version = "1.5.1" description = "" -category = "dev" +category = "main" optional = false python-versions = ">=3.6.1,<4.0" @@ -433,7 +433,7 @@ python-versions = "*" name = "py" version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -441,7 +441,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "pycodestyle" version = "2.8.0" description = "Python style guide checker" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -449,7 +449,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "pydocstyle" version = "6.1.1" description = "Python docstring style checker" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -463,7 +463,7 @@ toml = ["toml"] name = "pyflakes" version = "2.3.1" description = "passive checker of Python programs" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -498,7 +498,7 @@ python-versions = "*" name = "pylint" version = "2.11.1" description = "python code static checker" -category = "dev" +category = "main" optional = false python-versions = "~=3.6" @@ -515,7 +515,7 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" name = "pylint-celery" version = "0.3" description = "pylint-celery is a Pylint plugin to aid Pylint in recognising and understandingerrors caused when using the Celery library" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -528,7 +528,7 @@ pylint-plugin-utils = ">=0.2.1" name = "pylint-django" version = "2.4.4" description = "A Pylint plugin to help Pylint understand the Django web framework" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -544,7 +544,7 @@ with_django = ["django"] name = "pylint-flask" version = "0.6" description = "pylint-flask is a Pylint plugin to aid Pylint in recognizing and understanding errors caused when using Flask" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -555,7 +555,7 @@ pylint-plugin-utils = ">=0.2.1" name = "pylint-plugin-utils" version = "0.6" description = "Utilities and helpers for writing Pylint plugins" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -566,7 +566,7 @@ pylint = ">=1.7" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -574,7 +574,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "pytest" version = "6.2.5" description = "pytest: simple powerful testing with Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -595,7 +595,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm name = "pytest-datadir" version = "1.3.1" description = "pytest plugin for test data directories and files" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -641,7 +641,7 @@ python-versions = ">=3.6" name = "requirements-detector" version = "0.7" description = "Python tool to find and list requirements of a Python project" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -652,7 +652,7 @@ astroid = ">=1.4" name = "setoptconf-tmp" version = "0.3.1" description = "A module for retrieving program settings from various sources in a consistant method." -category = "dev" +category = "main" optional = false python-versions = "*" @@ -671,7 +671,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -679,7 +679,7 @@ python-versions = ">=3.6" name = "snowballstemmer" version = "2.1.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" +category = "main" optional = false python-versions = "*" @@ -687,7 +687,7 @@ python-versions = "*" name = "stevedore" version = "3.5.0" description = "Manage dynamic plugins for Python applications" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -706,7 +706,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "typing-extensions" version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -714,14 +714,14 @@ python-versions = "*" name = "wrapt" version = "1.13.2" description = "Module for decorators, wrappers and monkey patching." -category = "dev" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "a44f31719364047d4a223e74f8b31a8cb20ec7940bb82428e038648d70c71e0e" +content-hash = "f0d074e8db541f39e39949bd345cff134f8580756a39a25362680af14028071d" [metadata.files] altgraph = [ @@ -827,8 +827,8 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] isort = [ - {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, - {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, + {file = "isort-5.10.0-py3-none-any.whl", hash = "sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f"}, + {file = "isort-5.10.0.tar.gz", hash = "sha256:e52ff6d38012b131628cf0f26c51e7bd3a7c81592eefe3ac71411e692f1b9345"}, ] jinja2 = [ {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"}, diff --git a/pyproject.toml b/pyproject.toml index c2626b3..3c83e09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,18 +10,19 @@ click = "^8.0.3" pendulum = "^2.1.2" Jinja2 = "^3.0.2" panaetius = { path = "../panaetius", develop = true } -pytest-datadir = "^1.3.1" [tool.poetry.dev-dependencies] pytest = "^6.2.5" prospector = { extras = ["with_bandit", "with_mypy"], version = "^1.5.1" } +pytest-datadir = "^1.3.1" duty = "^0.7.0" pyinstaller = "^4.5.1" coverage = "^6.0.2" +isort = "^5.10.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -"tembo" = "tembo.cli.cli:run" +"tembo" = "tembo.cli.cli:main" diff --git a/tembo/__main__.py b/tembo/__main__.py new file mode 100644 index 0000000..9278c65 --- /dev/null +++ b/tembo/__main__.py @@ -0,0 +1,7 @@ +import sys + +from tembo.cli.cli import main + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tembo/cli/__init__.py b/tembo/cli/__init__.py index f4076cb..0135417 100644 --- a/tembo/cli/__init__.py +++ b/tembo/cli/__init__.py @@ -3,6 +3,7 @@ import os import panaetius from panaetius.exceptions import LoggingDirectoryDoesNotExistException + __version__ = "0.1.0" if (config_path := os.environ.get("TEMBO_CONFIG")) is not None: diff --git a/tembo/cli/cli.py b/tembo/cli/cli.py index e0df85b..9e69552 100644 --- a/tembo/cli/cli.py +++ b/tembo/cli/cli.py @@ -6,10 +6,9 @@ from typing import Collection import click import tembo.cli +from tembo import exceptions from tembo.journal import pages from tembo.utils import Success -from tembo import exceptions - CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @@ -22,7 +21,7 @@ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) prog_name="Tembo", message=f"Tembo v{tembo.__version__} 🐘", ) -def run(): +def main(): """ Tembo - an organiser for work notes. """ @@ -82,7 +81,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") @@ -98,7 +97,7 @@ 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: +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, @@ -132,9 +131,7 @@ def new_create_scoped_page(config_scope: dict, inputs: Collection[str]) -> pages def _new_verify_name_exists(scope: str) -> None: - _name_found = scope in [ - user_scope["name"] for user_scope in tembo.cli.CONFIG.scopes - ] + _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: @@ -145,9 +142,7 @@ def _new_verify_name_exists(scope: str) -> None: raise exceptions.EmptyConfigYML( f"Config.yml found in {tembo.cli.CONFIG.config_path} is empty" ) - raise exceptions.MissingConfigYML( - f"No config.yml found in {tembo.cli.CONFIG.config_path}" - ) + raise exceptions.MissingConfigYML(f"No config.yml found in {tembo.cli.CONFIG.config_path}") def _new_get_config_scope(scope: str) -> dict: @@ -173,18 +168,14 @@ 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 - raise exceptions.MandatoryKeyNotFound( - f"Key {key_error} not found in config.yml" - ) + raise exceptions.MandatoryKeyNotFound(f"Key {key_error} not found in config.yml") return config_scope def _new_show_example(example: bool, config_scope: dict) -> None: if example: if isinstance(config_scope["example"], str): - cli_message( - f'Example for {config_scope["name"]}: {config_scope["example"]}' - ) + cli_message(f'Example for {config_scope["name"]}: {config_scope["example"]}') else: cli_message("No example in config.yml") raise SystemExit(0) @@ -194,5 +185,5 @@ def cli_message(message: str) -> None: click.echo(f"[TEMBO] {message} 🐘") -run.add_command(new) -run.add_command(list_all) +main.add_command(new) +main.add_command(list_all) diff --git a/tembo/journal/pages.py b/tembo/journal/pages.py index 4810cdf..348529d 100644 --- a/tembo/journal/pages.py +++ b/tembo/journal/pages.py @@ -1,3 +1,5 @@ +"""Module that contains the factories to create Tembo pages.""" + from __future__ import annotations from abc import ABCMeta, abstractmethod