mirror of
https://git.zavage.net/Zavage-Software/app_skellington.git
synced 2025-04-19 17:09:21 -06:00
Compare commits
No commits in common. "main" and "v0.2.0" have entirely different histories.
@ -17,10 +17,10 @@ repos:
|
|||||||
args: ["--profile", "black", "--filter-files"]
|
args: ["--profile", "black", "--filter-files"]
|
||||||
|
|
||||||
# Flake8
|
# Flake8
|
||||||
- repo: https://github.com/pycqa/flake8
|
#- repo: https://github.com/pycqa/flake8
|
||||||
rev: '7.0.0'
|
# rev: '7.0.0'
|
||||||
hooks:
|
# hooks:
|
||||||
- id: flake8
|
# - id: flake8
|
||||||
|
|
||||||
# Black
|
# Black
|
||||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
||||||
|
@ -1,2 +1 @@
|
|||||||
3.8.20
|
3.8.19
|
||||||
3.8.8
|
|
||||||
|
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,22 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## [0.2.2] (2020-07-19)
|
|
||||||
|
|
||||||
Second release is focused on cleanup, documentation.
|
|
||||||
|
|
||||||
* flake8, black, isort - all warnings and errors fixed.
|
|
||||||
* Documentation improved.
|
|
||||||
* Build and deploy process documented for developers.
|
|
||||||
* Poetry removed, replaced with modern pyproject.toml and setuptools.build_meta
|
|
||||||
for automatic version management.
|
|
||||||
|
|
||||||
## [0.1.0] (2020-07-19)
|
|
||||||
|
|
||||||
First release to PyPi.
|
|
||||||
|
|
||||||
* Basic functionality. Probably not the best code.
|
|
||||||
* Sub-menus supported, multi-level through CLI.
|
|
||||||
* Config through ConfigObj ini. Define spec file and input config.ini
|
|
||||||
* Colored Logging
|
|
||||||
* Services can be defined and provided to classes
|
|
||||||
|
|
57
README.md
57
README.md
@ -1,9 +1,5 @@
|
|||||||
---
|
app_skellington
|
||||||
gitea: none
|
===============
|
||||||
include_toc: true
|
|
||||||
---
|
|
||||||
|
|
||||||
**app_skellington**
|
|
||||||
|
|
||||||
Application framework for Python, features include:
|
Application framework for Python, features include:
|
||||||
- Pain-free multi-level command menu: Expose public class methods as commands available to user.
|
- Pain-free multi-level command menu: Expose public class methods as commands available to user.
|
||||||
@ -18,26 +14,10 @@ Principles:
|
|||||||
- Compatible with Linux, Windows, and Mac. Try to be compatible as possible otherwise.
|
- Compatible with Linux, Windows, and Mac. Try to be compatible as possible otherwise.
|
||||||
- Try to be compatible with alternate Python runtimes such as PyPy and older python environments. \*WIP
|
- Try to be compatible with alternate Python runtimes such as PyPy and older python environments. \*WIP
|
||||||
|
|
||||||
# Python Package Index (PyPi) - Installation Instruction
|
# PyPi Hosted Link
|
||||||
|
|
||||||
This is the project page for PyPi: The Python Package Index for community third-party libraries
|
|
||||||
|
|
||||||
https://pypi.org/project/app-skellington/
|
https://pypi.org/project/app-skellington/
|
||||||
|
|
||||||
```
|
|
||||||
pip install app_skellington # install
|
|
||||||
pip uninstall app_skellington # uninstall
|
|
||||||
pip download app_skellington # download .whl wheel files for redistributable install
|
|
||||||
pip install -U app_skellington # upgrade
|
|
||||||
pip list # list install packages in environment
|
|
||||||
pip index version app_skellington # enumerate available distributions in pypi for package
|
|
||||||
```
|
|
||||||
|
|
||||||
# Description
|
|
||||||
|
|
||||||
This is a library. There is nothing to run by itself. It would be helpful to have a sample application that uses
|
|
||||||
it or something, but I don't have that ready at the moment.
|
|
||||||
|
|
||||||
# Application Configuration
|
# Application Configuration
|
||||||
|
|
||||||
Site configurations are supported through ConfigObj. There is a config.spec
|
Site configurations are supported through ConfigObj. There is a config.spec
|
||||||
@ -82,7 +62,7 @@ or
|
|||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
|
|
||||||
Tests are a WIP and not fully built out yet. Recommendation is to run 'pytest' in the 'tests' directory.
|
Tests are a WIP. Recommendation is to run 'pytest' in the 'tests' directory.
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
|
|
||||||
@ -101,11 +81,10 @@ eval "$(pyenv init -)"
|
|||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
* reference https://github.com/pyenv/pyenv
|
* reference https://github.com/pyenv/pyenv
|
||||||
* Use pyenv to install desired python version, and/or create any virtual environments you desire
|
|
||||||
|
|
||||||
Clone the repo:
|
Clone the repo:
|
||||||
```commandline
|
```commandline
|
||||||
git clone https://git-repos.zavage.net/zavage-software/app_skellington.git
|
git clone https://git-mirror.zavage.net/zavage-software/app_skellington.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Install pre-commit hooks:
|
Install pre-commit hooks:
|
||||||
@ -131,24 +110,6 @@ isort app_skellington
|
|||||||
flake8 app_skellington
|
flake8 app_skellington
|
||||||
```
|
```
|
||||||
|
|
||||||
Publish:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Push latest commit, or on commit ready to publish:
|
|
||||||
git push
|
|
||||||
|
|
||||||
# Create a tag with the desired version number and push:
|
|
||||||
git tag -a v0.2.0 -m "0.2.0 provides modern pyproject.toml build with setuptools, versioning, and publishing"
|
|
||||||
git push origin v0.2.0
|
|
||||||
|
|
||||||
# Build the wheel:
|
|
||||||
python -m build
|
|
||||||
|
|
||||||
# Publish to pypi:
|
|
||||||
twine check dist/*
|
|
||||||
twine upload dist/*
|
|
||||||
```
|
|
||||||
* Reference https://packaging.python.org/en/latest/overview/
|
|
||||||
|
|
||||||
|
|
||||||
# Version
|
# Version
|
||||||
@ -156,14 +117,8 @@ twine upload dist/*
|
|||||||
setuptools_scm will infer the version based on the latest tag in your Git history.
|
setuptools_scm will infer the version based on the latest tag in your Git history.
|
||||||
Ensure you are tagging your commits with meaningful version numbers like v1.0.0, v1.1.0, etc.
|
Ensure you are tagging your commits with meaningful version numbers like v1.0.0, v1.1.0, etc.
|
||||||
|
|
||||||
You can view the current version number with the command:
|
|
||||||
|
|
||||||
python -m setuptools_scm
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
See [license](LICENSE.txt)
|
|
||||||
|
|
||||||
MIT no attribution required - https://opensource.org/license/mit-0
|
MIT no attribution required - https://opensource.org/license/mit-0
|
||||||
|
|
||||||
* Allows commercial use.
|
* Allows commercial use.
|
||||||
@ -174,7 +129,7 @@ MIT no attribution required - https://opensource.org/license/mit-0
|
|||||||
|
|
||||||
* Project page: https://zavage-software.com/portfolio/app_skellington
|
* Project page: https://zavage-software.com/portfolio/app_skellington
|
||||||
* Please report bugs, improvements, or feedback!
|
* Please report bugs, improvements, or feedback!
|
||||||
* Contact: mat@zavage.net
|
* Contact: mathew@zavage.net
|
||||||
|
|
||||||
* Packing and distribution conforms to PEP 621 https://peps.python.org/pep-0621/
|
* Packing and distribution conforms to PEP 621 https://peps.python.org/pep-0621/
|
||||||
* Reference https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/
|
* Reference https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
from .cfg import Config, EnvironmentVariables
|
|
||||||
|
|
||||||
from .app_container import (
|
from .app_container import (
|
||||||
DEFAULT_APP_AUTHOR,
|
DEFAULT_APP_AUTHOR,
|
||||||
DEFAULT_APP_NAME,
|
DEFAULT_APP_NAME,
|
||||||
ApplicationContainer,
|
ApplicationContainer,
|
||||||
ApplicationContext,
|
ApplicationContext,
|
||||||
|
NoCommandSpecified,
|
||||||
|
ServiceNotFound,
|
||||||
)
|
)
|
||||||
from ._util import ServiceNotFound, NoCommandSpecified
|
from .cfg import Config, EnvironmentVariables
|
||||||
from .cli import (
|
from .cli import (
|
||||||
EXPLICIT_FAIL_ON_UNKNOWN_ARGS,
|
EXPLICIT_FAIL_ON_UNKNOWN_ARGS,
|
||||||
CommandEntry,
|
CommandEntry,
|
||||||
|
@ -124,15 +124,3 @@ def create_func(constructor, cls_method):
|
|||||||
return cls_method(cmd_class_instance, *args, **kwargs)
|
return cls_method(cmd_class_instance, *args, **kwargs)
|
||||||
|
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
class ServiceNotFound(Exception):
|
|
||||||
"""
|
|
||||||
Application framework error: unable to find and inject dependency.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoCommandSpecified(Exception):
|
|
||||||
pass
|
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
# file generated by setuptools-scm
|
|
||||||
# don't change, don't track in version control
|
|
||||||
|
|
||||||
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from typing import Tuple, Union
|
|
||||||
|
|
||||||
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
||||||
else:
|
|
||||||
VERSION_TUPLE = object
|
|
||||||
|
|
||||||
version: str
|
|
||||||
__version__: str
|
|
||||||
__version_tuple__: VERSION_TUPLE
|
|
||||||
version_tuple: VERSION_TUPLE
|
|
||||||
|
|
||||||
__version__ = version = "0.3.0.dev7+gd155176.d20250223"
|
|
||||||
__version_tuple__ = version_tuple = (0, 3, 0, "dev7", "gd155176.d20250223")
|
|
@ -4,10 +4,7 @@ import os
|
|||||||
|
|
||||||
import appdirs
|
import appdirs
|
||||||
|
|
||||||
from ._util import ServiceNotFound, NoCommandSpecified, get_root_asset
|
from . import _util, cfg, cli, log
|
||||||
from . import log
|
|
||||||
from .cfg import Config
|
|
||||||
from .cli import CommandTree
|
|
||||||
|
|
||||||
# Application scaffolding:
|
# Application scaffolding:
|
||||||
from ._bootstrap import _bootstrap_logger
|
from ._bootstrap import _bootstrap_logger
|
||||||
@ -52,7 +49,7 @@ class ApplicationContainer:
|
|||||||
# global state, configuration, loggers, and runtime args.
|
# global state, configuration, loggers, and runtime args.
|
||||||
self._dependencies = {}
|
self._dependencies = {}
|
||||||
|
|
||||||
config = Config(configspec_filepath, configini_filepath)
|
config = cfg.Config(configspec_filepath, configini_filepath)
|
||||||
|
|
||||||
logger = log.LoggingLayer(self.appname, self.appauthor)
|
logger = log.LoggingLayer(self.appname, self.appauthor)
|
||||||
# Try and load logging configuration if provided
|
# Try and load logging configuration if provided
|
||||||
@ -70,7 +67,7 @@ class ApplicationContainer:
|
|||||||
# Reference to context service avail. in root_app
|
# Reference to context service avail. in root_app
|
||||||
self["ctx"] = lambda: self.ctx
|
self["ctx"] = lambda: self.ctx
|
||||||
|
|
||||||
self.cli = CommandTree() # Command-line interface
|
self.cli = cli.CommandTree() # Command-line interface
|
||||||
|
|
||||||
# Run methods if subclass implemented them:
|
# Run methods if subclass implemented them:
|
||||||
if callable(getattr(self, "_cli_options", None)):
|
if callable(getattr(self, "_cli_options", None)):
|
||||||
@ -147,7 +144,7 @@ class ApplicationContainer:
|
|||||||
"""
|
"""
|
||||||
Attempt to find config.spec inside the installed package directory.
|
Attempt to find config.spec inside the installed package directory.
|
||||||
"""
|
"""
|
||||||
return get_root_asset(configspec_filename)
|
return _util.get_root_asset(configspec_filename)
|
||||||
|
|
||||||
def _inject_service_dependencies(self, constructor):
|
def _inject_service_dependencies(self, constructor):
|
||||||
"""
|
"""
|
||||||
@ -183,6 +180,9 @@ class ApplicationContainer:
|
|||||||
except NoCommandSpecified:
|
except NoCommandSpecified:
|
||||||
print("Failure: No command specified.")
|
print("Failure: No command specified.")
|
||||||
|
|
||||||
|
def interactive_shell(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def invoke_from_cli(self):
|
def invoke_from_cli(self):
|
||||||
self.invoke_command()
|
self.invoke_command()
|
||||||
|
|
||||||
@ -191,3 +191,13 @@ class ApplicationContainer:
|
|||||||
# Applications need a default usage
|
# Applications need a default usage
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceNotFound(Exception):
|
||||||
|
"""
|
||||||
|
Application framework error: unable to find and inject dependency.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoCommandSpecified(Exception):
|
||||||
|
pass
|
||||||
|
@ -3,7 +3,7 @@ import inspect
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ._util import NoCommandSpecified
|
from . import app_container
|
||||||
from ._bootstrap import _bootstrap_logger
|
from ._bootstrap import _bootstrap_logger
|
||||||
|
|
||||||
# If explicit fail is enabled, any command with at least one unknown
|
# If explicit fail is enabled, any command with at least one unknown
|
||||||
@ -41,12 +41,12 @@ class CommandTree:
|
|||||||
|
|
||||||
./scriptname --option="value" [submenu] [command]
|
./scriptname --option="value" [submenu] [command]
|
||||||
|
|
||||||
is different from
|
is different than
|
||||||
|
|
||||||
./scriptname [submenu] [command] --option="value"
|
./scriptname [submenu] [command] --option="value"
|
||||||
|
|
||||||
in that option is being applied to the application in the first example and
|
in that option is being applied to the application in the first example and
|
||||||
applied to the command (under the submenu command group) in
|
applied to the refresh_datasets command (under the nhsn command group) in
|
||||||
the second. In the same way the -h, --help options print different docs
|
the second. In the same way the -h, --help options print different docs
|
||||||
depending on where the help option was passed.
|
depending on where the help option was passed.
|
||||||
"""
|
"""
|
||||||
@ -82,7 +82,17 @@ class CommandTree:
|
|||||||
Creates a root-level submenu with no entries. SubMenu node is
|
Creates a root-level submenu with no entries. SubMenu node is
|
||||||
returned which can have submenus and commands attached to it.
|
returned which can have submenus and commands attached to it.
|
||||||
"""
|
"""
|
||||||
|
# NOTE(MG) Fix for Python>=3.7,
|
||||||
|
# argparse.ArgumentParser added 'required' argument.
|
||||||
|
# Must also be written into SubMenu.create_submenu.
|
||||||
func_args = {"dest": param_name, "metavar": param_name, "required": is_required}
|
func_args = {"dest": param_name, "metavar": param_name, "required": is_required}
|
||||||
|
if sys.version_info.major == 3 and sys.version_info.minor < 7:
|
||||||
|
if is_required:
|
||||||
|
_bootstrap_logger.warn(
|
||||||
|
"Unable to enforce required submenu: Requires >= Python 3.7"
|
||||||
|
)
|
||||||
|
del func_args["required"]
|
||||||
|
# END fix for Python<3.7
|
||||||
|
|
||||||
# Creates an argument as a slot in the underlying argparse.
|
# Creates an argument as a slot in the underlying argparse.
|
||||||
subparsers = self.root_parser.add_subparsers(**func_args)
|
subparsers = self.root_parser.add_subparsers(**func_args)
|
||||||
@ -166,7 +176,7 @@ class CommandTree:
|
|||||||
|
|
||||||
registered_name = cmd_name
|
registered_name = cmd_name
|
||||||
_bootstrap_logger.info("registered command: %s", registered_name)
|
_bootstrap_logger.info("registered command: %s", registered_name)
|
||||||
# end copy-paste then edited from SubMenu.register_command
|
# end copy-paste then editted from SubMenu.register_command
|
||||||
|
|
||||||
self._cmd_tree_is_single_command = True
|
self._cmd_tree_is_single_command = True
|
||||||
self._single_command = cmd
|
self._single_command = cmd
|
||||||
@ -201,12 +211,6 @@ class CommandTree:
|
|||||||
return None, None, False
|
return None, None, False
|
||||||
|
|
||||||
def run_command(self, args=None):
|
def run_command(self, args=None):
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
args (list[str]): Direct from STDIN
|
|
||||||
Raises:
|
|
||||||
NoCommandSpecified for invalid command.
|
|
||||||
"""
|
|
||||||
args, unk, success = self.parse(args)
|
args, unk, success = self.parse(args)
|
||||||
if not success:
|
if not success:
|
||||||
_bootstrap_logger.info("cli - SystemExit: Perhaps user invoked --help")
|
_bootstrap_logger.info("cli - SystemExit: Perhaps user invoked --help")
|
||||||
@ -222,7 +226,7 @@ class CommandTree:
|
|||||||
cmd = self._lookup_command(args)
|
cmd = self._lookup_command(args)
|
||||||
if cmd is None:
|
if cmd is None:
|
||||||
_bootstrap_logger.critical("cli - Failed to find command.")
|
_bootstrap_logger.critical("cli - Failed to find command.")
|
||||||
raise NoCommandSpecified
|
return False
|
||||||
|
|
||||||
return self._invoke_command(cmd, args)
|
return self._invoke_command(cmd, args)
|
||||||
|
|
||||||
@ -280,7 +284,7 @@ class CommandTree:
|
|||||||
# return self._invoke_command(lookup, args)
|
# return self._invoke_command(lookup, args)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise NoCommandSpecified("No command specified.")
|
raise app_container.NoCommandSpecified("No command specified.")
|
||||||
|
|
||||||
def _invoke_command(self, cmd, args):
|
def _invoke_command(self, cmd, args):
|
||||||
command_to_be_invoked = cmd.callback
|
command_to_be_invoked = cmd.callback
|
||||||
|
@ -40,6 +40,5 @@ dev = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools_scm]
|
[tool.setuptools_scm]
|
||||||
version_file = "app_skellington/_version.py"
|
version_scheme = "guess-next-dev"
|
||||||
version_scheme = "release-branch-semver"
|
|
||||||
local_scheme = "node-and-date"
|
local_scheme = "node-and-date"
|
||||||
|
Loading…
Reference in New Issue
Block a user