Skip to content
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

sample scaffold subcommand, wip #1

Merged
merged 2 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 160 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,160 @@
*.egg-info
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
35 changes: 35 additions & 0 deletions examples/nebari_subcommand_scaffold/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# nebari-plugins

## Installation

1. Clone [nebari/extension-mechanism](https://github.com/nebari-dev/nebari/tree/extension-mechanism) and [nebari-plugin-examples](https://github.com/MetroStar/nebari-plugin-examples).

2. Install packages.
``` bash

cd nebari
pip install -e .[dev]
nebari --version # sample output: 2023.5.2.dev110+gb71cfb18
nebari --help # confirm scaffold and foobar are not in the list of available commands

cd ../nebari-plugin-examples/nebari_subcommand_scaffold
pip install -e .
pytest # 9 passing tests (as of 2023-06-29)
nebari --help # scaffold should now be in the list of available commands
nebari scaffold plugin subcommand foobar -o ../out -f # generate a subcommand type plugin named foobar in ../out/foobar and overwrite if already present

cd ../out/foobar
pip install -e .
pytest # 2 passing tests (as of 2023-06-29)
nebari --help # foobar should now be in the list of available commands
nebari foobar -n Sample # sample output: Hello, Sample

```

---
## Reference

- Nebari (dev build) - [extension-mechanism](https://github.com/nebari-dev/nebari/tree/extension-mechanism)
- Command Line - [Typer](https://typer.tiangolo.com/)
- Configuration and Schema Validation - [Pydantic](https://docs.pydantic.dev/latest/)
- Plugin Management - [Pluggy](https://pluggy.readthedocs.io/en/stable/)
18 changes: 18 additions & 0 deletions examples/nebari_subcommand_scaffold/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[build-system]
build-backend = 'setuptools.build_meta'
requires = [
'setuptools',
]

[project]
name = "nebari_subcommand_scaffold"
version = "0.0.1"
requires-python = ">=3.8"
dependencies = [
"nebari",
"cookiecutter",
"camel-converter",
]

[project.entry-points.nebari]
nebari-subcommand-scaffold = "scaffold.scaffold_cmd"
4 changes: 4 additions & 0 deletions examples/nebari_subcommand_scaffold/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
filterwarnings =
ignore::DeprecationWarning

Empty file.
14 changes: 14 additions & 0 deletions examples/nebari_subcommand_scaffold/src/scaffold/scaffold_cmd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# https://github.com/nebari-dev/nebari/pull/1833

import typer

from .subcommands.plugin import plugin_cmd as plugin

from nebari.hookspecs import hookimpl

@hookimpl
def nebari_subcommand(cli: typer.Typer):
app = typer.Typer()
cli.add_typer(app, name = 'scaffold', help = 'Generate standard file/folder content for various Nebari components.', rich_help_panel = 'Additional Commands')

app.add_typer(plugin.app, name = 'plugin', help = 'Generate standard project structure for a Nebari plugin.')
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import typer

from .stage import stage_runner as _stage
from .subcommand import subcommand_runner as _subcommand

app = typer.Typer()

@app.command()
def stage(
ctx: typer.Context,
name: str = typer.Argument(..., help = 'Name of the stage', show_default = False),
priority: int = typer.Argument(..., help = 'Priority of the stage', show_default = False),
# priority: int = typer.Option(..., '--priority', '-p', help = 'Priority of the stage', show_default = False, prompt = True),
out: str = typer.Option(..., '--out', '-o', help = 'Output directory', show_default = False),
type: _stage.StagePluginType = typer.Option(_stage.StagePluginType.default, '--type', '-t', help = 'Base type of the stage', case_sensitive = False),
):
"""
Nebari stage type plugin.
"""
_stage.run(name, priority, out, type)

@app.command()
def subcommand(
ctx: typer.Context,
name: str = typer.Argument(..., help = 'Name of the generated plugin.', show_default = False),
out: str = typer.Option(..., '--out', '-o', help = 'Output directory.', show_default = False),
force: bool = typer.Option(False, '--force', '-f', help = 'Overwrite output directory if exists.', show_default = True),
):
"""
Nebari subcommand type plugin.
"""
_subcommand.run(name, out, force)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
import rich

from enum import Enum

# TODO

class StagePluginType(str, Enum):
default = 'default'
terraform = 'terraform'
helm = 'helm'

def run(
name: str,
priority: int,
out: str,
type: StagePluginType,
):
out_path = os.path.abspath(out)
if type == StagePluginType.terraform:
_terraform(name, priority, out_path)
elif type == StagePluginType.helm:
_helm(name, priority, out_path)
else:
_default(name, priority, out_path)

def _default(
name: str,
priority: int,
out_path: str,
):
rich.print(f"Generic Stage plugin: Name - {name}, Priority - {priority}")

def _terraform(
name: str,
priority: int,
out_path: str,
):
rich.print(f"Terraform Stage plugin: Name - {name}, Priority - {priority}")

def _helm(
name: str,
priority: int,
out_path: str,
):
rich.print(f"Helm Stage plugin: Name - {name}, Priority - {priority}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os

from camel_converter import to_snake
from cookiecutter.main import cookiecutter

def run(name: str, out: str, force: bool):
project_name = to_snake(name.replace('-', '_'))
print(f"Attempting to generate Nebari subcommand scaffold {name}...")

templates_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates'))
out_path = os.path.abspath(out)

cookiecutter(
template = templates_path,
overwrite_if_exists = force,
output_dir = out_path,
no_input = True,
extra_context = {
'project_name': project_name
}
)

# https://github.com/cookiecutter/cookiecutter/issues/1601
strip_extension(out_path, '.j2')

print(f'Successfully generated Nebari {project_name} subcommand plugin scaffold to {out_path}.')

def strip_extension(p: str, ext: str):
for path, _, files in os.walk(p):
for f in files:
if f.endswith(ext):
old_name = os.path.join(path, f)
new_name = os.path.join(path, f.strip(ext))
os.rename(old_name, new_name)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"project_name": "placeholder"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[build-system]
build-backend = 'setuptools.build_meta'
requires = [
'setuptools',
]

[project]
name = "nebari_subcommand_{{ cookiecutter.project_name }}"
version = "0.0.1"
dependencies = [
"nebari"
]

[project.entry-points.nebari]
nebari-subcommand-{{ cookiecutter.project_name | replace("_", "-") }} = "{{ cookiecutter.project_name }}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
filterwarnings =
ignore::DeprecationWarning

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from nebari.hookspecs import hookimpl

import typer

@hookimpl
def nebari_subcommand(cli):
@cli.command()
def {{ cookiecutter.project_name }}(
name: str = typer.Option(
"World!", help = "This is a sample subcommand"
)
):
print(f"Hello, {name}")
Loading