Skip to content

Drop Python 3.8, 3.9 support to match napari and add precommit #35

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

Merged
merged 11 commits into from
Mar 7, 2025
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ updates:
groups:
python-packages:
patterns:
- "*"
- "*"
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down
35 changes: 35 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
exclude: _vendor|vendored
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.9
hooks:
- id: ruff-format
exclude: examples
args: [--config, format.quote-style = 'single']
- id: ruff
# args:
# - "--unsafe-fixes"
- repo: https://github.com/seddonym/import-linter
rev: v2.2
hooks:
- id: import-linter
stages: [manual]
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.31.2
hooks:
- id: check-github-workflows
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
# .py files are skipped cause already checked by other hooks
hooks:
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
exclude: .*\.py
- id: end-of-file-fixer
exclude: .*\.py|.*\.jinja
- id: trailing-whitespace
# trailing whitespace has meaning in markdown https://www.markdownguide.org/hacks/#indent-tab
exclude: .*\.py|.*\.md
- id: mixed-line-ending
exclude: .*\.py
144 changes: 77 additions & 67 deletions _tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,93 +9,99 @@

def module_name_pep8_compliance(module_name):
"""Validate that the plugin module name is PEP8 compliant."""
if not re.match(r"^[a-z][_a-z0-9]+$", module_name):
link = "https://www.python.org/dev/peps/pep-0008/#package-and-module-names"
logger.error("Module name should be pep-8 compliant.")
logger.error(f" More info: {link}")
if not re.match(r'^[a-z][_a-z0-9]+$', module_name):
link = 'https://www.python.org/dev/peps/pep-0008/#package-and-module-names'
logger.error('Module name should be pep-8 compliant.')
logger.error(f' More info: {link}')
sys.exit(1)


def pypi_package_name_compliance(plugin_name):
"""Check there are no underscores in the plugin name"""
if re.search(r"_", plugin_name):
logger.error("PyPI.org and pip discourage package names with underscores.")
if re.search(r'_', plugin_name):
logger.error('PyPI.org and pip discourage package names with underscores.')
sys.exit(1)


def validate_manifest(module_name, project_directory):
"""Validate the new plugin repository against napari requirements."""
try:
from npe2 import PluginManifest
except ImportError as e:
logger.error("npe2 is not installed. Skipping manifest validation.")
except ImportError:
logger.error('npe2 is not installed. Skipping manifest validation.')
return True

current_directory = Path('.').absolute()
if (current_directory.match(project_directory) and not Path(project_directory).is_absolute()):
if (
current_directory.match(project_directory)
and not Path(project_directory).is_absolute()
):
project_directory = current_directory

path=Path(project_directory) / "src" / Path(module_name) / "napari.yaml"
path = Path(project_directory) / 'src' / Path(module_name) / 'napari.yaml'

valid = False
try:
pm = PluginManifest.from_file(path)
msg = f"✔ Manifest for {(pm.display_name or pm.name)!r} valid!"
msg = f'✔ Manifest for {(pm.display_name or pm.name)!r} valid!'
valid = True
except PluginManifest.ValidationError as err:
msg = f"🅇 Invalid! {err}"
logger.error(msg.encode("utf-8"))
msg = f'🅇 Invalid! {err}'
logger.error(msg.encode('utf-8'))
sys.exit(1)
except Exception as err:
msg = f"🅇 Failed to read {path!r}. {type(err).__name__}: {err}"
logger.error(msg.encode("utf-8"))
msg = f'🅇 Failed to read {path!r}. {type(err).__name__}: {err}'
logger.error(msg.encode('utf-8'))
sys.exit(1)
else:
logger.info(msg.encode("utf-8"))
logger.info(msg.encode('utf-8'))
return valid


def initialize_new_repository(
install_precommit=False,
plugin_name="napari-foobar",
github_repository_url="provide later",
github_username_or_organization="githubuser",
):
install_precommit=False,
plugin_name='napari-foobar',
github_repository_url='provide later',
github_username_or_organization='githubuser',
):
"""Initialize new plugin repository with git, and optionally pre-commit."""

msg = ""
msg = ''

# Configure git line ending settings
# https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration
if os.name == 'nt': # if on Windows, configure git line ending characters
subprocess.run(["git", "config", "--global", "core.autocrlf", "true"])
subprocess.run(['git', 'config', '--global', 'core.autocrlf', 'true'])
else: # for Linux and Mac
subprocess.run(["git", "config", "--global", "core.autocrlf", "input"])
subprocess.run(['git', 'config', '--global', 'core.autocrlf', 'input'])

# try to run git init
try:
subprocess.run(["git", "init", "-q"])
subprocess.run(["git", "checkout", "-b", "main"])
subprocess.run(['git', 'init', '-q'])
subprocess.run(['git', 'checkout', '-b', 'main'])
except Exception:
logger.error("Error in git initialization.")
logger.error('Error in git initialization.')

if install_precommit is True:
# try to install and update pre-commit
try:
print("install pre-commit ...")
subprocess.run(["python", "-m", "pip", "install", "pre-commit"], stdout=subprocess.DEVNULL)
print("updating pre-commit...")
subprocess.run(["pre-commit", "autoupdate"], stdout=subprocess.DEVNULL)
subprocess.run(["git", "add", "."])
subprocess.run(["pre-commit", "run", "black", "-a"], capture_output=True)
print('install pre-commit ...')
subprocess.run(
['python', '-m', 'pip', 'install', 'pre-commit'],
stdout=subprocess.DEVNULL,
)
print('updating pre-commit...')
subprocess.run(['pre-commit', 'autoupdate'], stdout=subprocess.DEVNULL)
subprocess.run(['git', 'add', '.'])
subprocess.run(['pre-commit', 'run', 'black', '-a'], capture_output=True)
except Exception:
logger.error("Error pip installing then running pre-commit.")
logger.error('Error pip installing then running pre-commit.')

try:
subprocess.run(["git", "add", "."])
subprocess.run(["git", "commit", "-q", "-m", "initial commit"])
subprocess.run(['git', 'add', '.'])
subprocess.run(['git', 'commit', '-q', '-m', 'initial commit'])
except Exception:
logger.error("Error creating initial git commit.")
logger.error('Error creating initial git commit.')
msg += f"""
Your plugin template is ready! Next steps:
1. `cd` into your new directory and initialize a git repo
Expand All @@ -108,7 +114,7 @@ def initialize_new_repository(
pip install -e .
"""
else:
msg +=f"""
msg += f"""
Your plugin template is ready! Next steps:
1. `cd` into your new directory
cd {plugin_name}
Expand All @@ -118,16 +124,16 @@ def initialize_new_repository(
# Ensure full reqd/write/execute permissions for .git files
if os.name == 'nt': # if on Windows OS
# Avoid permission denied errors on Github Actions CI
subprocess.run(["attrib", "-h", "rr", ".git", "/s", "/d"])
subprocess.run(['attrib', '-h', 'rr', '.git', '/s', '/d'])

if install_precommit is True:
# try to install and update pre-commit
# installing after commit to avoid problem with comments in setup.cfg.
try:
print("install pre-commit hook...")
subprocess.run(["pre-commit", "install"])
print('install pre-commit hook...')
subprocess.run(['pre-commit', 'install'])
except Exception:
logger.error("Error at pre-commit install, skipping pre-commit")
logger.error('Error at pre-commit install, skipping pre-commit')

if github_repository_url != 'provide later':
msg += f"""
Expand Down Expand Up @@ -172,35 +178,39 @@ def initialize_new_repository(
return msg


if __name__=="__main__":
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("pre_gen_project")
logger = logging.getLogger('pre_gen_project')
parser = ArgumentParser()
parser.add_argument("--plugin_name",
dest="plugin_name",
help="The name of your plugin")
parser.add_argument("--module_name",
dest="module_name",
help="Plugin module name")
parser.add_argument("--project_directory",
dest="project_directory",
help="Project directory")
parser.add_argument("--install_precommit",
dest="install_precommit",
help="Install pre-commit",
default="False")
parser.add_argument("--github_repository_url",
dest="github_repository_url",
help="Github repository URL",
default='provide later')
parser.add_argument("--github_username_or_organization",
dest="github_username_or_organization",
help="Github user or organisation name",
default='githubuser')
parser.add_argument(
'--plugin_name', dest='plugin_name', help='The name of your plugin'
)
parser.add_argument('--module_name', dest='module_name', help='Plugin module name')
parser.add_argument(
'--project_directory', dest='project_directory', help='Project directory'
)
parser.add_argument(
'--install_precommit',
dest='install_precommit',
help='Install pre-commit',
default='False',
)
parser.add_argument(
'--github_repository_url',
dest='github_repository_url',
help='Github repository URL',
default='provide later',
)
parser.add_argument(
'--github_username_or_organization',
dest='github_username_or_organization',
help='Github user or organisation name',
default='githubuser',
)
args = parser.parse_args()

# Since bool("False") returns True, we need to check the actual string value
if str(args.install_precommit).lower() == "true":
if str(args.install_precommit).lower() == 'true':
install_precommit = True
else:
install_precommit = False
Expand All @@ -213,4 +223,4 @@ def initialize_new_repository(
github_repository_url=args.github_repository_url,
github_username_or_organization=args.github_username_or_organization,
)
print(msg)
print(msg)
2 changes: 1 addition & 1 deletion copier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,4 @@ _tasks:
"--install_precommit={{ install_precommit }}",
"--github_repository_url={{ github_repository_url }}",
"--github_username_or_organization={{ github_username_or_organization }}",
]
]
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ jinja2-time
npe2
pytest
pytest-copie
tox
tox
2 changes: 1 addition & 1 deletion template/.github/workflows/test_and_deploy.yml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down
8 changes: 4 additions & 4 deletions template/pyproject.toml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"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",
"Topic :: Scientific/Engineering :: Image Processing",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
dependencies = [
"numpy",
{% if include_widget_plugin %} "magicgui",
Expand Down Expand Up @@ -91,7 +91,7 @@ version = {attr = "{{module_name}}.__init__.__version__"}

[tool.black]
line-length = 79
target-version = ['py38', 'py39', 'py310']
target-version = ['py310', 'py311', 'py312', 'py313']

[tool.ruff]
line-length = 79
Expand Down Expand Up @@ -136,5 +136,5 @@ exclude = [
"*_vendor*",
]

target-version = "py38"
target-version = "py310"
fix = true
Loading