mirror of
https://git.zavage.net/Zavage-Software/app_skellington.git
synced 2025-04-19 09:19:21 -06:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3e8ad8fbb3 | ||
|
d15517623d | ||
10e873d2d6 | |||
b743beb53a | |||
b2e082b7c6 | |||
f0fb18da71 | |||
afd2b5eb42 | |||
470514ba4b | |||
55a9796806 | |||
7647713498 | |||
f0a4bdbced | |||
adbae0074c | |||
fb0ef3d8f6 | |||
881a2db9dc |
@ -17,10 +17,10 @@ repos:
|
||||
args: ["--profile", "black", "--filter-files"]
|
||||
|
||||
# Flake8
|
||||
#- repo: https://github.com/pycqa/flake8
|
||||
# rev: '7.0.0'
|
||||
# hooks:
|
||||
# - id: flake8
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: '7.0.0'
|
||||
hooks:
|
||||
- id: flake8
|
||||
|
||||
# Black
|
||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
||||
|
@ -1 +1,2 @@
|
||||
3.8.19
|
||||
3.8.20
|
||||
3.8.8
|
||||
|
22
CHANGELOG.md
Normal file
22
CHANGELOG.md
Normal file
@ -0,0 +1,22 @@
|
||||
# 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,5 +1,9 @@
|
||||
app_skellington
|
||||
===============
|
||||
---
|
||||
gitea: none
|
||||
include_toc: true
|
||||
---
|
||||
|
||||
**app_skellington**
|
||||
|
||||
Application framework for Python, features include:
|
||||
- Pain-free multi-level command menu: Expose public class methods as commands available to user.
|
||||
@ -14,10 +18,26 @@ Principles:
|
||||
- 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
|
||||
|
||||
# PyPi Hosted Link
|
||||
# Python Package Index (PyPi) - Installation Instruction
|
||||
|
||||
This is the project page for PyPi: The Python Package Index for community third-party libraries
|
||||
|
||||
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
|
||||
|
||||
Site configurations are supported through ConfigObj. There is a config.spec
|
||||
@ -62,7 +82,7 @@ or
|
||||
|
||||
# Tests
|
||||
|
||||
Tests are a WIP. Recommendation is to run 'pytest' in the 'tests' directory.
|
||||
Tests are a WIP and not fully built out yet. Recommendation is to run 'pytest' in the 'tests' directory.
|
||||
|
||||
# Development
|
||||
|
||||
@ -81,10 +101,11 @@ eval "$(pyenv init -)"
|
||||
EOF
|
||||
```
|
||||
* reference https://github.com/pyenv/pyenv
|
||||
* Use pyenv to install desired python version, and/or create any virtual environments you desire
|
||||
|
||||
Clone the repo:
|
||||
```commandline
|
||||
git clone https://git-mirror.zavage.net/zavage-software/app_skellington.git
|
||||
git clone https://git-repos.zavage.net/zavage-software/app_skellington.git
|
||||
```
|
||||
|
||||
Install pre-commit hooks:
|
||||
@ -110,6 +131,24 @@ isort 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
|
||||
@ -117,8 +156,14 @@ flake8 app_skellington
|
||||
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.
|
||||
|
||||
You can view the current version number with the command:
|
||||
|
||||
python -m setuptools_scm
|
||||
|
||||
# License
|
||||
|
||||
See [license](LICENSE.txt)
|
||||
|
||||
MIT no attribution required - https://opensource.org/license/mit-0
|
||||
|
||||
* Allows commercial use.
|
||||
@ -129,7 +174,7 @@ MIT no attribution required - https://opensource.org/license/mit-0
|
||||
|
||||
* Project page: https://zavage-software.com/portfolio/app_skellington
|
||||
* Please report bugs, improvements, or feedback!
|
||||
* Contact: mathew@zavage.net
|
||||
* Contact: mat@zavage.net
|
||||
|
||||
* 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/
|
||||
|
@ -1,14 +1,14 @@
|
||||
# flake8: noqa
|
||||
|
||||
from .cfg import Config, EnvironmentVariables
|
||||
|
||||
from .app_container import (
|
||||
DEFAULT_APP_AUTHOR,
|
||||
DEFAULT_APP_NAME,
|
||||
ApplicationContainer,
|
||||
ApplicationContext,
|
||||
NoCommandSpecified,
|
||||
ServiceNotFound,
|
||||
)
|
||||
from .cfg import Config, EnvironmentVariables
|
||||
from ._util import ServiceNotFound, NoCommandSpecified
|
||||
from .cli import (
|
||||
EXPLICIT_FAIL_ON_UNKNOWN_ARGS,
|
||||
CommandEntry,
|
||||
|
@ -124,3 +124,15 @@ def create_func(constructor, cls_method):
|
||||
return cls_method(cmd_class_instance, *args, **kwargs)
|
||||
|
||||
return func
|
||||
|
||||
|
||||
class ServiceNotFound(Exception):
|
||||
"""
|
||||
Application framework error: unable to find and inject dependency.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NoCommandSpecified(Exception):
|
||||
pass
|
||||
|
20
app_skellington/_version.py
Normal file
20
app_skellington/_version.py
Normal file
@ -0,0 +1,20 @@
|
||||
# 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,7 +4,10 @@ import os
|
||||
|
||||
import appdirs
|
||||
|
||||
from . import _util, cfg, cli, log
|
||||
from ._util import ServiceNotFound, NoCommandSpecified, get_root_asset
|
||||
from . import log
|
||||
from .cfg import Config
|
||||
from .cli import CommandTree
|
||||
|
||||
# Application scaffolding:
|
||||
from ._bootstrap import _bootstrap_logger
|
||||
@ -49,7 +52,7 @@ class ApplicationContainer:
|
||||
# global state, configuration, loggers, and runtime args.
|
||||
self._dependencies = {}
|
||||
|
||||
config = cfg.Config(configspec_filepath, configini_filepath)
|
||||
config = Config(configspec_filepath, configini_filepath)
|
||||
|
||||
logger = log.LoggingLayer(self.appname, self.appauthor)
|
||||
# Try and load logging configuration if provided
|
||||
@ -67,7 +70,7 @@ class ApplicationContainer:
|
||||
# Reference to context service avail. in root_app
|
||||
self["ctx"] = lambda: self.ctx
|
||||
|
||||
self.cli = cli.CommandTree() # Command-line interface
|
||||
self.cli = CommandTree() # Command-line interface
|
||||
|
||||
# Run methods if subclass implemented them:
|
||||
if callable(getattr(self, "_cli_options", None)):
|
||||
@ -144,7 +147,7 @@ class ApplicationContainer:
|
||||
"""
|
||||
Attempt to find config.spec inside the installed package directory.
|
||||
"""
|
||||
return _util.get_root_asset(configspec_filename)
|
||||
return get_root_asset(configspec_filename)
|
||||
|
||||
def _inject_service_dependencies(self, constructor):
|
||||
"""
|
||||
@ -180,9 +183,6 @@ class ApplicationContainer:
|
||||
except NoCommandSpecified:
|
||||
print("Failure: No command specified.")
|
||||
|
||||
def interactive_shell(self):
|
||||
pass
|
||||
|
||||
def invoke_from_cli(self):
|
||||
self.invoke_command()
|
||||
|
||||
@ -191,13 +191,3 @@ class ApplicationContainer:
|
||||
# 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 sys
|
||||
|
||||
from . import app_container
|
||||
from ._util import NoCommandSpecified
|
||||
from ._bootstrap import _bootstrap_logger
|
||||
|
||||
# If explicit fail is enabled, any command with at least one unknown
|
||||
@ -41,12 +41,12 @@ class CommandTree:
|
||||
|
||||
./scriptname --option="value" [submenu] [command]
|
||||
|
||||
is different than
|
||||
is different from
|
||||
|
||||
./scriptname [submenu] [command] --option="value"
|
||||
|
||||
in that option is being applied to the application in the first example and
|
||||
applied to the refresh_datasets command (under the nhsn command group) in
|
||||
applied to the command (under the submenu command group) in
|
||||
the second. In the same way the -h, --help options print different docs
|
||||
depending on where the help option was passed.
|
||||
"""
|
||||
@ -82,17 +82,7 @@ class CommandTree:
|
||||
Creates a root-level submenu with no entries. SubMenu node is
|
||||
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}
|
||||
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.
|
||||
subparsers = self.root_parser.add_subparsers(**func_args)
|
||||
@ -176,7 +166,7 @@ class CommandTree:
|
||||
|
||||
registered_name = cmd_name
|
||||
_bootstrap_logger.info("registered command: %s", registered_name)
|
||||
# end copy-paste then editted from SubMenu.register_command
|
||||
# end copy-paste then edited from SubMenu.register_command
|
||||
|
||||
self._cmd_tree_is_single_command = True
|
||||
self._single_command = cmd
|
||||
@ -211,6 +201,12 @@ class CommandTree:
|
||||
return None, None, False
|
||||
|
||||
def run_command(self, args=None):
|
||||
"""
|
||||
Args:
|
||||
args (list[str]): Direct from STDIN
|
||||
Raises:
|
||||
NoCommandSpecified for invalid command.
|
||||
"""
|
||||
args, unk, success = self.parse(args)
|
||||
if not success:
|
||||
_bootstrap_logger.info("cli - SystemExit: Perhaps user invoked --help")
|
||||
@ -226,7 +222,7 @@ class CommandTree:
|
||||
cmd = self._lookup_command(args)
|
||||
if cmd is None:
|
||||
_bootstrap_logger.critical("cli - Failed to find command.")
|
||||
return False
|
||||
raise NoCommandSpecified
|
||||
|
||||
return self._invoke_command(cmd, args)
|
||||
|
||||
@ -284,7 +280,7 @@ class CommandTree:
|
||||
# return self._invoke_command(lookup, args)
|
||||
|
||||
else:
|
||||
raise app_container.NoCommandSpecified("No command specified.")
|
||||
raise NoCommandSpecified("No command specified.")
|
||||
|
||||
def _invoke_command(self, cmd, args):
|
||||
command_to_be_invoked = cmd.callback
|
||||
|
@ -40,5 +40,6 @@ dev = [
|
||||
]
|
||||
|
||||
[tool.setuptools_scm]
|
||||
version_scheme = "guess-next-dev"
|
||||
version_file = "app_skellington/_version.py"
|
||||
version_scheme = "release-branch-semver"
|
||||
local_scheme = "node-and-date"
|
||||
|
Loading…
Reference in New Issue
Block a user