diff --git a/.env b/.env
index 4a0a411..5e80cea 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
-PYTHONPATH=$PYTHONPATH:pype:example_pypes
+PYTHONPATH=$PYTHONPATH:pype
PYPE_CONFIG_FOLDER=.venv/.pype-cli
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d0c3ea8..be210a5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,12 +9,26 @@ on:
jobs:
build:
runs-on: ubuntu-latest
-
+ strategy:
+ matrix:
+ # https://www.python.org/downloads/
+ python-version: [
+ "3.9", # EOL: 2025-10
+ "3.10", # EOL: 2026-10
+ "3.11", # EOL: 2027-10
+ "3.12", # EOL: 2028-10
+ "3.13", # EOL: 2029-10
+ ]
+ name: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v2
- - uses: actions/setup-python@v1
+
+ - name: Setup python
+ uses: actions/setup-python@v3
+ with:
+ python-version: "${{ matrix.python-version }}"
- name: CI checks
run: |
- python3 -m pip install pipenv
+ python -m pip install pipenv
make
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 64598d7..a93031a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,7 +14,7 @@ jobs:
- name: Release
run: |
- python3 -m pip install pipenv
+ python -m pip install pipenv
TWINE_USERNAME="${{ secrets.TWINE_USERNAME }}" \
TWINE_PASSWORD="${{ secrets.TWINE_PASSWORD }}" \
make publish
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d2e2a17..a88dd2b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,7 +2,6 @@
"python.pythonPath": ".venv/bin/python",
"python.analysis.extraPaths": [
"pypes",
- "example_pypes",
".venv/bin/python"
],
// PEP8 settings (https://www.python.org/dev/peps/pep-0008/)
@@ -29,5 +28,6 @@
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
- "python.testing.pytestArgs": []
+ "python.testing.pytestArgs": [],
+ "makefile.configureOnOpen": false
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a0bd1a..71edebb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 0.8.0
+
+- Removed assumptions about `python` versus `python3` installation. This simplifies the code and gives full control into the user's hand to determine the correct `python` through the path or tools such as `pyenv`
+- Upgraded to Python 3.9..3.12
+- Using the minimal template for new pypes, e.g., `pype myplugin --create-pype my-pype --minimal` now creates a pype that can be executed independent from `pype-cli`, i.e., no built-in libraries are used
+- `pype pype.config shell-install` produces more robust completion scripts
+- Removal of all example pypes as they were outdated and misleading
+
## 0.7.0
- Rename PypeException to PypeError
diff --git a/Makefile b/Makefile
index e311bff..62334e8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,11 @@
# Required executables
-ifeq (, $(shell which python3))
- $(error "No python3 on PATH.")
+ifeq (, $(shell which python))
+ $(error "No python on PATH.")
endif
-ifeq (, $(shell which pipenv))
- $(error "No pipenv on PATH.")
+PIPENV_CMD := python -m pipenv
+PIP_CMD := python -m pip
+ifeq (, $(shell $(PIPENV_CMD) --version))
+ $(error "No $(PIPENV_CMD) on PATH.")
endif
# Suppress warning if pipenv is started inside .venv
@@ -20,23 +22,23 @@ export PYPE_CONFIG_FOLDER = $(shell pwd)/.venv/.pype-cli
# Process variables
LAST_VERSION := $(shell git tag | sort --version-sort -r | head -n1)
VERSION_HASH := $(shell git show-ref -s $(LAST_VERSION))
-PY_FILES := setup.py pype tests example_pypes
+PY_FILES := setup.py pype tests
all: prepare build
prepare: clean
@echo Preparing virtual environment
- pipenv install --dev
+ $(PIPENV_CMD) install --dev
mkdir -p $(PYPE_CONFIG_FOLDER)
echo "export PYPE_CONFIG_FOLDER=$(PYPE_CONFIG_FOLDER)" >> .venv/bin/activate
build: test mypy isort lint
@echo Run setup.py-based build process to package application
- pipenv run python setup.py bdist_wheel
+ $(PIPENV_CMD) run python setup.py bdist_wheel
shell:
@echo Initialize virtualenv and open a new shell using it
- pipenv shell
+ $(PIPENV_CMD) shell
clean:
@echo Clean project base
@@ -59,36 +61,42 @@ clean:
test:
@echo Run all tests in default virtualenv
- pipenv run py.test --verbose tests
+ $(PIPENV_CMD) run py.test --verbose tests
isort:
@echo Check for incorrectly sorted imports
- pipenv run isort --check-only $(PY_FILES)
+ $(PIPENV_CMD) run isort --check-only $(PY_FILES)
isort-apply:
@echo Check for incorrectly sorted imports
- pipenv run isort $(PY_FILES)
+ $(PIPENV_CMD) run isort $(PY_FILES)
lint:
@echo Run code formatting checks against source code base
- pipenv run flake8 $(PY_FILES)
+ $(PIPENV_CMD) run flake8 $(PY_FILES)
mypy:
@echo Run static code checks against source code base
- pipenv run mypy pype example_pypes tests
+ $(PIPENV_CMD) run mypy pype tests
sys-info:
@echo Print pype configuration within venv
- pipenv run pype pype.config system-info
+ $(PIPENV_CMD) run pype pype.config system-info
install-wheel: all
@echo Install from wheel
- pip3 install --force-reinstall dist/*.whl
+ $(PIP_CMD) install --force-reinstall dist/*.whl
+
+install-wheel-no-test:
+ @echo Install from wheel including rebuild but skipping tests
+ rm -rf dist/*.whl
+ $(PIPENV_CMD) run python setup.py bdist_wheel
+ $(PIP_CMD) install --force-reinstall dist/*.whl
publish: all
@echo Publish pype to pypi.org
TWINE_USERNAME=$(TWINE_USERNAME) TWINE_PASSWORD=$(TWINE_PASSWORD) \
- pipenv run twine upload dist/*
+ $(PIPENV_CMD) run twine upload dist/*
release:
@echo Commit release - requires NEXT_VERSION to be set
diff --git a/README.md b/README.md
index 47d44f2..b9f34dd 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,19 @@
# pype-cli
-> A command-line tool for command-line tools
-
+> A command-line tool for command-line tools 
-
-
-
+  
## In a nutshell
-__pype-cli__ is a command-line tool to manage sets of other command-line tools. It simplifies the creation, orchestration and access of Python scripts that you require for your development work, process automation, etc.
+**pype-cli** is a command-line tool to manage sets of other command-line tools. It simplifies the creation, orchestration and access of Python scripts that you require for your development work, process automation, etc.
-
+
## Quickstart
-- Install **pype-cli** via `pip3 install --user pype-cli`. This will install the command `pype` for the current user
-- To use an alternative name you need to install from source via `PYPE_CUSTOM_SHELL_COMMAND=my_cmd_name python3 setup.py install --user`
+- Install **pype-cli** via `python -m pip install --user pype-cli`. This will install the command `pype` for the current user
+- To use an alternative name you need to install from source via `PYPE_CUSTOM_SHELL_COMMAND=my_cmd_name python setup.py install --user`
- Run `pype pype.config shell-install` and open a new shell to activate shell completion
- Create a new **plugin** in your home folder: `pype pype.config plugin-register --create --name my-plugin --path ~/`
- Create a sample **pype** for your plugin: `pype my-plugin --create-pype my-pype`
@@ -86,7 +83,23 @@ If you have selected a **pype** from a **plugin** you can set **aliases** for it
### Global logging configuration
-**pype-cli** contains a built-in logger setup. To configure it use the **pype** `pype pype.config logger`. In your **pypes** you can use it right away [like in the provided example](example_pypes/basics/logger.py).
+**pype-cli** contains a built-in file logger setup. To configure it use the **pype** `pype pype.config logger`. In your **pypes** you can use it right away like this:
+
+```python
+import logging
+import click
+
+@click.command(name='my-pype', help=__doc__)
+def main() -> None:
+ # Name your logger. Note that this can be omitted but you will end up
+ # with the default 'root' logger.
+ logger = logging.getLogger(__name__)
+
+ # Log something to the global log file. Note that the output to the file
+ # depends on the logging configuration mentioned above.
+ logger.debug('Debug message')
+ logger.info('Info message')
+```
- Enable/disable global logging: `pype pype.config logger enable/disable`
- Print current configuration: `pype pype.config logger print-config`
@@ -98,21 +111,9 @@ If you have selected a **pype** from a **plugin** you can set **aliases** for it
If your **plugin** contains shared code over all **pypes** you can simply put it into a subpackage of your **plugin** or into a file prefixed with `__`, e.g., `__commons__.py`. **pype-cli** will only scan / consider top-level Python scripts without underscores as **pypes**.
-### Example recipes
-
-You can register a sample **plugin** called [**basics**](example_pypes/basics) that contains some useful recipes to get you started with your own pipes.
-
-- Register the [**basics**](example_pypes/basics) **plugin**: `pype pype.config plugin-register --name basics --path /example_pypes`
-- Navigate to `pype basics ` to see its content
-- Open a recipe in your edior, for example: `pype basics --open-pype hello-world-opt`
-
-For some basic information you can also refer to the built-in [template.py](pype/template.py) and [template_minimal.py](pype/template_minimal.py) that are used on creation of new **pypes**.
-
-Note that as long as you don't import some of the [convenience utilities](pype/__init__.py) of **pype-cli** directly, your **pype** will remain [an independent Python script](example_pypes/basics/non_pype_script.py) that can be used regardless of **pype_cli**.
-
### Best practises
-**pype-cli** has been built around the [Click-project ("Command Line Interface Creation Kit")](https://click.palletsprojects.com/) which is a Python package for creating beautiful command line interfaces. To fully utilize the capabilities of **pype-cli** it is highly recommended to get familiar with the project and use it in your **pypes** as well. Again you can refer to the [**basics**](example_pypes/basics) plugin for guidance.
+**pype-cli** has been built around the [Click-project ("Command Line Interface Creation Kit")](https://click.palletsprojects.com/) which is a Python package for creating beautiful command line interfaces. To fully utilize the capabilities of **pype-cli** it is highly recommended to get familiar with the project and use it in your **pypes** as well.
## pype-cli development
diff --git a/example_pypes/basics/__init__.py b/example_pypes/basics/__init__.py
deleted file mode 100644
index df4b507..0000000
--- a/example_pypes/basics/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Example pypes."""
diff --git a/example_pypes/basics/hello_world.py b/example_pypes/basics/hello_world.py
deleted file mode 100644
index bfb1f08..0000000
--- a/example_pypes/basics/hello_world.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-"""A classic hello world console feedback."""
-
-# Import the "Command Line Interface Creation Kit"
-#
-import click
-
-import pype
-
-# Create a click command https://click.palletsprojects.com/en/7.x/commands/
-
-
-@click.command(name=pype.fname_to_name(__file__), help=__doc__)
-def main() -> None:
- """Script's main entry point."""
- print('Hello World!')
diff --git a/example_pypes/basics/hello_world_opt.py b/example_pypes/basics/hello_world_opt.py
deleted file mode 100644
index 64dc6c9..0000000
--- a/example_pypes/basics/hello_world_opt.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-"""A classic hello world console feedback with click cli-options."""
-
-# Import the "Command Line Interface Creation Kit"
-#
-import click
-
-import pype
-
-# Create a click command https://click.palletsprojects.com/en/7.x/commands/
-
-
-@click.command(name=pype.fname_to_name(__file__), help=__doc__)
-# Add an option https://click.palletsprojects.com/en/7.x/options/
-@click.option('--message', '-m', default='Hello World!',
- metavar='MESSAGE', help='Alternative message')
-def main(message: str) -> None:
- """Script's main entry point."""
- print(message)
diff --git a/example_pypes/basics/logger.py b/example_pypes/basics/logger.py
deleted file mode 100644
index 75f572e..0000000
--- a/example_pypes/basics/logger.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-"""How to use the global logging subsystem."""
-
-import logging
-
-import click
-
-import pype
-
-
-@click.command(name=pype.fname_to_name(__file__), help=__doc__)
-def main() -> None:
- """Script's main entry point."""
- # For this to work it is required that you've set up global logging
- # via 'pype pype.config logger' before. See its help pages to get yourself
- # familiar with the options.
-
- # Name your logger. Note that this can be omitted but you will end up
- # with the default 'root' logger.
- logger = logging.getLogger(__name__)
-
- # Log something to the global log file. Note that the output to the file
- # depends on the logging configuration mentioned above.
- logger.debug('Debug message')
- logger.info('Info message')
diff --git a/example_pypes/basics/multi_command.py b/example_pypes/basics/multi_command.py
deleted file mode 100644
index 09e8c6e..0000000
--- a/example_pypes/basics/multi_command.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-"""A dynamic multi-command creator."""
-
-import json
-from os import path
-from typing import Any, List
-
-# Import the "Command Line Interface Creation Kit"
-#
-import click
-
-import pype
-
-# Store your dynamic commands along other pype configuration files
-commands_registry = path.join(
- pype.Config().get_dir_path(), 'example-multicommands')
-
-
-def _load_command_registry() -> List:
- """Load the available commands from a json file."""
- try:
- commands = json.load(open(commands_registry, 'r'))
- except (json.decoder.JSONDecodeError, FileNotFoundError):
- commands = [] # Fallback on first initialization
- return commands
-
-
-def _generate_multi_command() -> List[Any]:
- """Create the subcommand registry."""
- commands = _load_command_registry()
- return [{
- 'name': command,
- 'help': 'Execute ' + command
- } for command in commands]
-
-
-def _command_callback(command: str, context: click.Context) -> None:
- """Process the passed subcommand."""
- print('Executing ' + command)
- print('Command context: ' + str(context))
-
-
-# Create a new click command with dynamic sub commands
-@click.group(
- name=pype.fname_to_name(__file__), help=__doc__,
- # Initialize dynamic subcommands using the convenience function
- cls=pype.generate_dynamic_multicommand(
- # Add a function to create multi commands, in this case
- # based on the existing command registry.
- _generate_multi_command(),
- # Provide a callback to receive the command and the
- # click context object.
- _command_callback
- ),
- # This will allow to call the pype without a sub command
- invoke_without_command=True
-)
-# Add an option to extend the command registry
-@click.option('--add', '-a', metavar='COMMAND_NAME',
- help='Add a new subcommand')
-# Pass the context to be able to print the help text
-@click.pass_context
-def main(ctx: click.Context, add: str) -> None:
- """Script's main entry point."""
- # Load the current registry from a local json-file
- command_registry = _load_command_registry()
- # If add option was selected...
- if add:
- if add in command_registry:
- print(add + ' already registered.')
- exit(0)
- click.confirm(
- 'Add ' + add + ' to commands?', default=False, abort=True)
- # Register and save the new command
- command_registry.append(add)
- json.dump(command_registry, open(commands_registry, 'w+'))
- # ... else just print the help text.
- elif ctx.invoked_subcommand is None:
- print(ctx.get_help())
diff --git a/example_pypes/basics/non_pype_script.py b/example_pypes/basics/non_pype_script.py
deleted file mode 100644
index 0e0bbec..0000000
--- a/example_pypes/basics/non_pype_script.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Script interoperable with pype-cli omitting any pype-cli libraries."""
-
-# Import the "Command Line Interface Creation Kit"
-#
-import click
-
-# Create a click command https://click.palletsprojects.com/en/7.x/commands/
-# Notice that instead of using pype.fname_to_name(__file__) we just
-# hard-wired the command name.
-
-
-@click.command(name='non-pype-script', help=__doc__)
-def main() -> None:
- """Script's main entry point."""
- print('I am a pype-cli independent script!')
-
-
-if __name__ == '__main__':
- main()
diff --git a/pype/__main__.py b/pype/__main__.py
index a4b6b03..65bbfe6 100644
--- a/pype/__main__.py
+++ b/pype/__main__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""PYPE - A command-line tool for command-line tools."""
diff --git a/pype/config_model.py b/pype/config_model.py
index dc941a4..7717098 100644
--- a/pype/config_model.py
+++ b/pype/config_model.py
@@ -5,7 +5,7 @@
from dataclasses import dataclass, field
from enum import Enum
from json import dumps
-from typing import List, Optional
+from typing import Any, List, Optional
class ConfigResolverSource(str, Enum):
@@ -20,9 +20,10 @@ class ConfigResolverSource(str, Enum):
class BaseDataClass:
"""Base data class with asdict capability."""
- def asdict(self) -> dict:
+ def asdict(self) -> dict[str, Any]:
"""Convert to dictionary."""
- return dc_asdict(self)
+ return dc_asdict(self) # type: ignore
+ # https://github.com/python/mypy/issues/17550
def asjson(self) -> str:
"""Convert to JSON."""
diff --git a/pype/core.py b/pype/core.py
index 0d36f14..9c74859 100644
--- a/pype/core.py
+++ b/pype/core.py
@@ -6,7 +6,6 @@
from importlib import import_module
from os import environ, path, remove
from re import sub
-from shutil import copyfile
from typing import Any, List, Optional
from click import Context
@@ -220,7 +219,7 @@ def alias_unregister(self, alias: str) -> None:
def __write_init_file(self, init_file: str, aliases: List) -> None:
shell_command = path.basename(sys.argv[0])
cfg_dir = self.__config.get_dir_path()
- source_cmd = 'zsh_source' if init_file == 'zsh' else 'source'
+ source_cmd = 'zsh_source' if init_file == 'zsh' else 'bash_source'
target_file = resolve_path(
path.join(cfg_dir, self.SHELL_INIT_PREFIX + init_file)
)
@@ -239,7 +238,7 @@ def __write_init_file(self, init_file: str, aliases: List) -> None:
export PATH=$PATH:{console_script}
if [ ! -z "$( command -v {shell_command} )" ] # Only if installed
then
- if [ ! -f {complete_file} ]
+ if [ ! -s {complete_file} ]
then
_{shell_upper}_COMPLETE={source_cmd} {shell_command} > {complete_file}
fi
@@ -269,8 +268,15 @@ def create_pype_or_exit(
# Depending on user input create a documented or simple template
template_name = ('template_minimal.py' if minimal
else 'template.py')
- source_name = path.join(path.dirname(__file__), template_name)
- copyfile(source_name, target_file)
+ source_file = path.join(path.dirname(__file__), template_name)
+ source_handle = open(source_file, 'r', encoding='utf-8')
+ target_handle = open(target_file, 'w+', encoding='utf-8')
+ for line in source_handle.readlines():
+ if r'%%PYPE_NAME%%' in line:
+ line = sub(r'%%PYPE_NAME%%', pype_name, line)
+ target_handle.write(line)
+ source_handle.close()
+ target_handle.close()
print_success('Created new pype ' + target_file)
return target_file
diff --git a/pype/template.py b/pype/template.py
index 93838ff..f3d13fa 100644
--- a/pype/template.py
+++ b/pype/template.py
@@ -7,6 +7,7 @@
# For colored output pype includes the colorama library
from colorama import Fore, Style
+# Import pype directly for some utilities
import pype
diff --git a/pype/template_minimal.py b/pype/template_minimal.py
index fbccb54..58180e5 100644
--- a/pype/template_minimal.py
+++ b/pype/template_minimal.py
@@ -3,9 +3,7 @@
import click
-import pype
-
-@click.command(name=pype.fname_to_name(__file__), help=__doc__)
+@click.command(name=r'%%PYPE_NAME%%', help=__doc__)
def main() -> None: # noqa: D103
pass
diff --git a/setup.py b/setup.py
index 297887c..c98647f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,7 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Package installer script."""
-from io import open
from os import environ, path
from setuptools import find_packages, setup
@@ -19,10 +18,10 @@
setup(
# Basic project information
name='pype-cli',
- version='0.7.1',
+ version='0.8.0',
# Authorship and online reference
author='Basti Tee',
- author_email='basti.tee@posteo.de',
+ author_email='basti.tee@icloud.com',
url='https://github.com/BastiTee/pype',
# Detailed description
description='A command-line tool for command-line tools',
@@ -35,14 +34,16 @@
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9'
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
+ 'Programming Language :: Python :: 3.13',
],
# Package configuration
packages=find_packages(exclude=('tests',)),
include_package_data=True,
- python_requires='>=3.7',
+ python_requires='>=3.9',
install_requires=[
'click',
'jsonschema',
diff --git a/tests/__init__.py b/tests/__init__.py
index 77623e8..c40b298 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -52,7 +52,7 @@ class ConfigTypeForTest(Enum):
NONE = 2
-@ contextlib.contextmanager
+@contextlib.contextmanager
def create_test_env(
configuration: ConfigTypeForTest = ConfigTypeForTest.EMPTY
) -> Generator[
@@ -108,11 +108,9 @@ def create_test_env(
def invoke_runner(
component_under_test: Union[str, BaseCommand],
- arguments: list = None
+ arguments: list = []
) -> RunnerEnvironment:
"""Create and invoke a test runner."""
- if arguments is None:
- arguments = []
with create_test_env() as test_env:
return create_runner(test_env, component_under_test, arguments)
@@ -120,7 +118,7 @@ def invoke_runner(
def create_runner(
test_env: TestEnvironment,
component_under_test: Union[str, BaseCommand],
- arguments: list = None
+ arguments: list = []
) -> RunnerEnvironment:
"""Create a test runner with a provided test environment.
@@ -128,8 +126,6 @@ def create_runner(
variables correctly. That is why tests.invoke_runner gets called
with a string instead of a module.
"""
- if arguments is None:
- arguments = []
environ[ENV_CONFIG_FOLDER] = test_env.config_dir
runner = CliRunner(env=environ)
importlib.reload(__main__)
diff --git a/tests/integration/test_pype.py b/tests/test_pype.py
similarity index 100%
rename from tests/integration/test_pype.py
rename to tests/test_pype.py
diff --git a/tests/integration/test_pype_aliases.py b/tests/test_pype_aliases.py
similarity index 100%
rename from tests/integration/test_pype_aliases.py
rename to tests/test_pype_aliases.py
diff --git a/tests/integration/test_pype_pypes.py b/tests/test_pype_pypes.py
similarity index 100%
rename from tests/integration/test_pype_pypes.py
rename to tests/test_pype_pypes.py
diff --git a/tests/integration/test_pypeconfig_pluginregister.py b/tests/test_pypeconfig_pluginregister.py
similarity index 100%
rename from tests/integration/test_pypeconfig_pluginregister.py
rename to tests/test_pypeconfig_pluginregister.py