-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Central Hierarchy + Environment design populated by Actions #9
Conversation
We decided to use pep517 pyproject.toml with flit, we decided to use pre-commit to automate linting and stuff. We decided to use flit over setuptools, poetry and pdm because it is mostly pep517 and pep621 compatible (unless setuptools) and it just does the job of building and publishing the package (unless poetry and pdm). We decided to use pre-commit with a large range of linters to ease the development between contributors. Among the "stuff" we enabled are: file sanitizers (line endind, useless whitespaces, line line at end of file), file checker (python, toml, yaml), linter and formatter (python) and some security watchdogs (don't commit those private keys!). We decided to use pytest over unittest, we just want to give that framework a try. We decided to use the boring sphinx because nothing else seems funny enough to us. Closes #3
DISCLAIMER: Implementations of Action are here just for illustration, they have to be refine. This commit just demonstrates an idea for the overall architecture. An instance of Hierarchy stores all information about configurations files and templated files to write for the new project. Then each tool is modeled by Action instances, they populate a hierarchy instance and define pre and post methods to run before and after the creation of the project files. E.g. Git Action pre method runs git init, while post method runs git add --all and git commit. Definitions of configurations and templates can be done with environment variables, like {NAME}. All those variables are stored in an Environment instance, it is initialized with environment variables system and can be modified by any Action using pre or post methods. Then values are used when needed and asked to user if missing. When an Action is created, it can also register itself a hook, e.g. Git Action register a VCSIgnore hook that defines how a pattern is ignored by git. Then any Action can used those hooks, e.g. Venv Action use VCSIgnore to make VCS system aware of ignoring .env folder.
Note about ECS: I did some tests where Finally, imo, it's not really useful as each component need its own system, so the decoupling is mainly artificial. |
If you want to test import logging
logging.basicConfig(level="INFO")
import subprocess
import incipyt
env = incipyt.Environment()
env.push("NAME", "its-a_test")
incipyt.process_actions(
"./test_project",
env,
[incipyt.actions.Git(), incipyt.actions.Venv(), incipyt.actions.Setuptools()],
) |
I have no time to do an extensive review but from a diagonal reading, it looks great ! Thank you ! |
Use click for requestion missing environment variables and choosing between multiple values.
49117fe
to
6ae25a0
Compare
I add files for CLI entrypoints Lines 10 to 18 in 6ae25a0
Then you can test directly with |
Wouldn't it be better to indicate the folder like this |
Actually I just setup something basic with Project name (as any other missing variables used to setup tools) is ask latter (**), but of course we can add something to give it directly in the command line. I don't think this PR should go further about the CLI (commands and options syntaxe, default values, help messages, etc). (*) I literally just adapted the first example of import click
@click.command()
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name",
help="The person to greet.")
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for _ in range(count):
click.echo("Hello, %s!" % name)
if __name__ == '__main__':
hello() (**) If you try to run |
incipyt/actions/venv.py
Outdated
environment.run( | ||
[ | ||
utils.Requires("{PYTHON_CMD}"), | ||
"-m", | ||
"venv", | ||
str(workon.joinpath(".env")), | ||
] | ||
) | ||
environment.push( | ||
"PYTHON_CMD", str(workon.joinpath(".env/bin/python")), update=True | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
environment.run( | |
[ | |
utils.Requires("{PYTHON_CMD}"), | |
"-m", | |
"venv", | |
str(workon.joinpath(".env")), | |
] | |
) | |
environment.push( | |
"PYTHON_CMD", str(workon.joinpath(".env/bin/python")), update=True | |
) | |
bin_dir = "Scripts" if sys.platform == "win32" else "bin" | |
env_path = workon / ".env" | |
python_path = env_path / bin_dir / "python" | |
environment.run([utils.Requires("{PYTHON_CMD}"), "-m", "venv", str(env_path)]) | |
environment.push("PYTHON_CMD", str(python_path), update=True) |
On Windows platform, python executable lies in ./venv/Scripts/python
(and so do other scripts or binaries)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- sys.platform.startswith("win")
+ sys.platform == "win32"
Environment variables should be confirmed even if already registered, except if explicitly push as `confirmed`. Configuration files are also purged of `None`/ empty list values before being written. Then registered but unconfirmed values are used as default values for `click.prompt`.
def __call__(self, url_kind, url_value): # noqa: D102 | ||
if self._hooks: | ||
logger.info( | ||
f"{len(self._hooks)} {self} hooks called with '{url_kind}: {url_value}'" | ||
) | ||
|
||
for hook in self._hooks: | ||
hook(self._hierarchy, url_kind, url_value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid repeating those idiom in each hook register
, __call__
and __init__
method, maybe hooks should inherit from a Hook
superclass, e.g:
Note Hook
in not an ABC because among other things it provides concrete implementation of __call__
.
class Hook:
_hooks = []
@classmethod
def register(cls, hook):
cls._hooks.append(hook)
def __init__(self, hierarchy):
self._hierarchy = hierarchy
def __str__(self):
return self.__class__.__name__
def __call__(self, *args, **kwargs):
if self._hooks:
logger.info(
f"len(self._hooks)} {self} hooks called with parameters {args}, {kwargs}"
# Maybe we can think of a nice way for child classes to redefine this logging message
)
for hook in self._hooks:
hook(self._hierarchy, *args, **kwargs)
class ProjectURL(Hook):
def __str__(self):
# not needed, will default to class name according to superclass
return "project-url"
def __call__(self, url_kind, url_value):
"""Here we can provide docs and relevant hook call signature"""
super().__call__(url_kind, url_value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, can you try to implement something in that sense ? You can directly commit on the branch.
raise click.BadArgumentUsage(f"FOLDER {folder.resolve()} is not empty.") | ||
env.push("PROJECT_NAME", folder.resolve().name) | ||
else: | ||
if folder.is_absolute() and folder.is_dir() and any(folder.iterdir()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my machine (win platform), for some reason folder
is a bytes
object and therefore an exception is raised here
The design is based on two central classes:
Hierarchy
with two attributes_configurations
and_templates
incipyt/incipyt/system.py
Lines 127 to 143 in dc3fdde
incipyt/_internal/dumpers
-- path-like classes with a specific method to write them, e.g. for Tomlincipyt/incipyt/_internal/dumpers.py
Lines 71 to 74 in dc3fdde
_templates
, values are just Jinja template._configurations
is dedicated to all configuration files based on key-value syntax (toml, cfg, ini, yaml, json, etc), values of this dictionary are then nested dictionary describing this configuration file.Envrionment
is basically a wrapper around a dictionary initiated with environment variables,incipyt/incipyt/system.py
Lines 13 to 65 in dc3fdde
{NAME}
or{AUTHOR}
that should be asked to the user. To do so,incipyt/incipyt/system.py
Lines 67 to 74 in dc3fdde
Hierarchy._configuration
incipyt/incipyt/system.py
Lines 97 to 108 in dc3fdde
callable
instead ofstr
, if so thosecallable
are call and the value substituted. Typically, it can beutils.Requires("{NAME}")
withincipyt/incipyt/_internal/utils.py
Lines 12 to 28 in dc3fdde
Environment
for{NAME}
:environment.pull(key)
.There are similar substitution mechanisms for
_template
and also for usingsubprocess.run
incipyt/incipyt/system.py
Lines 110 to 124 in dc3fdde
The main workflow is then to initialize a
Hierarchy
and let tool specific classes populate_configurations
and_template
incipyt/incipyt/system.py
Lines 242 to 245 in dc3fdde
An instance of Hierarchy stores all information about configurations files and templated files to write for the new project.
Then each tool is modeled by Action instances, they populate a hierarchy instance and define pre and post methods to run before and after the creation of the project files
incipyt/incipyt/system.py
Lines 250 to 259 in dc3fdde
Definitions of configurations and templates can be done with environment variables, like {NAME}. All those variables are stored in an Environment instance, it is initialized with environment variables system and can be modified by any Action using pre or post methods. Then values are used when needed and asked to user if missing.
When an Action is created, it can also register itself a hook, e.g. Git Action register a VCSIgnore hook that defines how a pattern is ignored by git. Then any Action can used those hooks, e.g. Venv Action use VCSIgnore to make VCS system aware of ignoring .env folder
incipyt/incipyt/actions/git.py
Lines 6 to 24 in dc3fdde