pluginify 1.0.0__tar.gz → 1.0.0.post11.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. pluginify-1.0.0.post11.dev0/.github/workflows/integ_test.yaml +25 -0
  2. pluginify-1.0.0.post11.dev0/.github/workflows/mypy.yaml +39 -0
  3. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/.github/workflows/status-check.yaml +2 -0
  4. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/PKG-INFO +2 -1
  5. pluginify-1.0.0.post11.dev0/docs/source/releases/latest/atomic-registry-writes.yaml +22 -0
  6. pluginify-1.0.0.post11.dev0/docs/source/releases/latest/ignore-files-in-utils-directories.yaml +17 -0
  7. pluginify-1.0.0.post11.dev0/docs/source/releases/latest/use-geoips-ci.yaml +32 -0
  8. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/_version.py +2 -2
  9. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/create_plugin_registries.py +26 -7
  10. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pyproject.toml +1 -1
  11. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/tests/unit_tests/plugin_registries/test_plugin_registries.py +29 -0
  12. pluginify-1.0.0/.github/workflows/code-quality.yaml +0 -87
  13. pluginify-1.0.0/.github/workflows/generate-badges.yaml +0 -216
  14. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/.github/workflows/deploy-docs.yaml +0 -0
  15. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/.github/workflows/package-and-publish.yaml +0 -0
  16. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/.github/workflows/tag-and-release.yaml +0 -0
  17. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/LICENSE +0 -0
  18. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/README.md +0 -0
  19. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/1.0.0-no-bug-fixes-required-on-this-release.yaml +0 -0
  20. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/1.19.0-internal-release.yaml +0 -0
  21. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/1.19.0-open-source-upload.yaml +0 -0
  22. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/1.19.0-patch-internal-use-vars-runner.yaml +0 -0
  23. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/ci-fix-perms.yaml +0 -0
  24. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/copy-geoips-functionality.yaml +0 -0
  25. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/ignore-dot-files-and-dirs.yaml +0 -0
  26. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/module-to-class-updates.yaml +0 -0
  27. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/obp-plugin-registry-updates.yaml +0 -0
  28. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/pluginify-updates-jeremy-comments.yaml +0 -0
  29. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/pypi-publish-bug-fix.yaml +0 -0
  30. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/remove-all-geoips-references.yaml +0 -0
  31. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/replace-geoips-functionality.yaml +0 -0
  32. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/update-black-formatting.yaml +0 -0
  33. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/docs/source/releases/1.0.0/version-update.yaml +0 -0
  34. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/__init__.py +0 -0
  35. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/commandline_typer.py +0 -0
  36. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/config.py +0 -0
  37. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/errors.py +0 -0
  38. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/interfaces/__init__.py +0 -0
  39. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/interfaces/base.py +0 -0
  40. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/interfaces/class_based/data_modifiers.py +0 -0
  41. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/interfaces/class_based_plugin.py +0 -0
  42. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/interfaces/yaml_based/configs.py +0 -0
  43. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/plugin_registry.py +0 -0
  44. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/plugins/classes/data_modifiers/cuboid.py +0 -0
  45. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/plugins/yaml/configs/stucco.yaml +0 -0
  46. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/pydantic_models/v1/configs.py +0 -0
  47. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/utils/__init__.py +0 -0
  48. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/utils/context_managers.py +0 -0
  49. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/pluginify/utils/validators.py +0 -0
  50. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/tests/conftest.py +0 -0
  51. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/tests/unit_tests/config/test_config.py +0 -0
  52. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/tests/unit_tests/plugin_registries/files/bad/invalid_interfaces.yaml +0 -0
  53. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/tests/unit_tests/plugin_registries/files/bad/missing_lowest_keys.yaml +0 -0
  54. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/tests/unit_tests/plugin_registries/files/bad/missing_plugin_types.yaml +0 -0
  55. {pluginify-1.0.0 → pluginify-1.0.0.post11.dev0}/tests/unit_tests/plugin_registries/files/good/pluginify.yaml +0 -0
@@ -0,0 +1,25 @@
1
+ name: Integration tests
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened, ready_for_review]
6
+ workflow_dispatch:
7
+
8
+ concurrency:
9
+ group: integ-test-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ integ-test:
14
+ if: github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false
15
+ uses: NRLMMD-GEOIPS/geoips_ci/.github/workflows/reusable-ci.yaml
16
+ # reusable-ci's job requests these; a called workflow cannot exceed the caller.
17
+ permissions:
18
+ contents: read
19
+ packages: write
20
+ with:
21
+ geoips_image: "ghcr.io/nrlmmd-geoips/geoips:latest"
22
+ run_core_tests: true
23
+ secrets:
24
+ token: ${{ secrets.GITHUB_TOKEN }}
25
+ docker_token: ${{ secrets.DOCKER_TOKEN }}
@@ -0,0 +1,39 @@
1
+ name: mypy
2
+
3
+ # Lint, unit tests and the geoips unit tests are in integ_test.yaml (geoips_ci's
4
+ # reusable-ci). It does not type check, so mypy stays here.
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ branches: [main]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ concurrency:
16
+ group: ${{ github.workflow }}-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ env:
20
+ UV_SYSTEM_PYTHON: true
21
+
22
+ jobs:
23
+ mypy:
24
+ continue-on-error: true
25
+ runs-on: ${{ vars.RUNNER }}
26
+ steps:
27
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
28
+
29
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
30
+ with:
31
+ python-version: "3.13"
32
+
33
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
34
+
35
+ - name: Install package
36
+ run: uv pip install -e ".[doc]" mypy
37
+
38
+ - name: Run mypy
39
+ run: mypy ./pluginify
@@ -2,6 +2,7 @@ name: Require up-to-date branch
2
2
 
3
3
  on:
4
4
  pull_request:
5
+ types: [opened, synchronize, reopened, ready_for_review]
5
6
  push:
6
7
  branches:
7
8
  - main
@@ -11,6 +12,7 @@ permissions:
11
12
 
12
13
  jobs:
13
14
  status-check:
15
+ if: github.event_name == 'push' || github.event.pull_request.draft == false
14
16
  runs-on: ${{ vars.RUNNER }}
15
17
  steps:
16
18
  - name: No-op status check
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pluginify
3
- Version: 1.0.0
3
+ Version: 1.0.0.post11.dev0
4
4
  Summary: Pluginify package
5
5
  License: LICENSE
6
6
  License-File: LICENSE
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
15
16
  Provides-Extra: doc
16
17
  Requires-Dist: docstring_parser
17
18
  Requires-Dist: jsonschema (>4.18.0)
@@ -0,0 +1,22 @@
1
+ bug fix:
2
+ - title: 'Write plugin registries atomically'
3
+ description: |
4
+ Plugin registries were rewritten in place, so another process reading a
5
+ registry at the same time (for example parallel pytest-xdist workers, one of
6
+ them rebuilding the registries) could read an empty or partial file and fail
7
+ with ``JSONDecodeError``. Registries are now written to a temporary file in
8
+ the same directory and moved into place with ``os.replace``.
9
+ files:
10
+ deleted:
11
+ - ''
12
+ modified:
13
+ - 'pluginify/create_plugin_registries.py'
14
+ - 'tests/unit_tests/plugin_registries/test_plugin_registries.py'
15
+ added:
16
+ - ''
17
+ related-issue:
18
+ number: null
19
+ repo_url: 'https://github.com/NRLMMD-GEOIPS/pluginify'
20
+ date:
21
+ start: 2026-10-01
22
+ finish: 2026-10-01
@@ -0,0 +1,17 @@
1
+ bug fix:
2
+ - title: 'Force ignore files in directories named */utils/'
3
+ description: 'Force ignore files in directories named */utils/'
4
+ files:
5
+ deleted:
6
+ - ''
7
+ modified:
8
+ - 'pluginify/create_plugin_registries.py'
9
+ - 'tests/unit_tests/plugin_registries/test_plugin_registries.py'
10
+ added:
11
+ - ''
12
+ related-issue:
13
+ number: null
14
+ repo_url: ''
15
+ date:
16
+ start: 2026-07-12
17
+ finish: 2026-07-12
@@ -0,0 +1,32 @@
1
+ continuous integration:
2
+ - title: 'Use the standard GeoIPS CI from geoips_ci'
3
+ description: |
4
+ Run geoips_ci's reusable-ci on pull requests, like geoips and the plugin
5
+ repositories: geoips' lint (flake8, black, bandit), pluginify's unit tests, and
6
+ geoips' unit tests against the pluginify branch, in the geoips:latest image.
7
+ Remove the code-quality and nightly badge workflows and the unused badges; mypy
8
+ keeps its own workflow, since reusable-ci does not type check. Skip draft pull
9
+ requests in the status check, like geoips.
10
+ files:
11
+ deleted:
12
+ - '.github/badges/coverage-badge.svg'
13
+ - '.github/badges/cspell-badge.svg'
14
+ - '.github/badges/install-badge.svg'
15
+ - '.github/badges/mypy-badge.svg'
16
+ - '.github/badges/ruff-badge.svg'
17
+ - '.github/badges/tests-badge.svg'
18
+ - '.github/workflows/code-quality.yaml'
19
+ - '.github/workflows/generate-badges.yaml'
20
+ modified:
21
+ - '.github/workflows/status-check.yaml'
22
+ - 'docs/source/releases/latest/ignore-files-in-utils-directories.yaml'
23
+ added:
24
+ - '.github/workflows/integ_test.yaml'
25
+ - '.github/workflows/mypy.yaml'
26
+ - 'docs/source/releases/latest/use-geoips-ci.yaml'
27
+ related-issue:
28
+ number: null
29
+ repo_url: ''
30
+ date:
31
+ start: 2026-10-20
32
+ finish: 2026-10-20
@@ -3,5 +3,5 @@
3
3
 
4
4
  # DO NOT EDIT
5
5
  # managed by poetry-dynamic-versioning
6
- __version__ = "1.0.0"
7
- __version_tuple__ = (1, 0, 0)
6
+ __version__ = "1.0.0.post11.dev0"
7
+ __version_tuple__ = (1, 0, 0, "post11", "dev0")
@@ -17,7 +17,7 @@ import logging
17
17
  from importlib import metadata, resources, util, import_module
18
18
  from inspect import signature
19
19
  import json
20
- from os import makedirs, remove
20
+ from os import getpid, makedirs, remove, replace
21
21
  from os.path import (
22
22
  basename,
23
23
  dirname,
@@ -317,14 +317,24 @@ def write_registered_plugins(pkg_dir, plugins, save_type):
317
317
  """
318
318
  if save_type == "yaml":
319
319
  reg_plug_abspath = os_join(pkg_dir, "registered_plugins.yaml")
320
- with open(reg_plug_abspath, "w") as plugin_registry:
321
- LOG.info("Writing %s", reg_plug_abspath)
322
- yaml.safe_dump(plugins, plugin_registry, default_flow_style=False)
323
320
  else:
324
321
  reg_plug_abspath = os_join(pkg_dir, "registered_plugins.json")
325
- with open(reg_plug_abspath, "w") as plugin_registry:
326
- LOG.info("Writing %s", reg_plug_abspath)
327
- json.dump(plugins, plugin_registry, indent=4)
322
+ LOG.info("Writing %s", reg_plug_abspath)
323
+ # Write to a temporary file and move it into place, so that other processes
324
+ # reading the registry (for example parallel test workers) never see a
325
+ # partially written file.
326
+ tmp_abspath = f"{reg_plug_abspath}.{getpid()}.tmp"
327
+ try:
328
+ with open(tmp_abspath, "w") as plugin_registry:
329
+ if save_type == "yaml":
330
+ yaml.safe_dump(plugins, plugin_registry, default_flow_style=False)
331
+ else:
332
+ json.dump(plugins, plugin_registry, indent=4)
333
+ replace(tmp_abspath, reg_plug_abspath)
334
+ except BaseException:
335
+ if exists(tmp_abspath):
336
+ remove(tmp_abspath)
337
+ raise
328
338
 
329
339
 
330
340
  def create_plugin_registries(plugin_packages, save_type, namespace):
@@ -934,6 +944,15 @@ def add_class_plugin(package, relpath, plugins):
934
944
  # __version.py, etc.
935
945
  return error_message
936
946
 
947
+ if "utils" in Path(relpath).parts:
948
+ # Ignore helper modules that live under plugin trees. These files are not
949
+ # plugins and should not be imported or validated for PLUGIN_CLASS.
950
+ LOG.info(
951
+ f"Skipping module at relpath {relpath} from {package}, "
952
+ "found under a utils directory"
953
+ )
954
+ return error_message
955
+
937
956
  module_name = splitext(basename(relpath))[0]
938
957
  # We need the full path to the module in order
939
958
  # for relative imports to work within modules.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pluginify" # package available at the root of the project
3
- version = "1.0.0"
3
+ version = "1.0.0.post11.dev0"
4
4
  description = "Pluginify package"
5
5
  authors = ["Evan Rose", "evrose54@colostate.edu"]
6
6
  readme = "README.md"
@@ -4,6 +4,7 @@
4
4
  """TestPluginRegistry Class used for Unit Testing the Plugin Registries."""
5
5
 
6
6
  from glob import glob
7
+ import json
7
8
  import logging
8
9
  from os.path import basename, splitext
9
10
 
@@ -11,6 +12,10 @@ import pytest
11
12
  import yaml
12
13
 
13
14
  from pluginify.errors import PluginError, PluginRegistryError
15
+ from pluginify.create_plugin_registries import (
16
+ add_class_plugin,
17
+ write_registered_plugins,
18
+ )
14
19
  from pluginify.interfaces import configs
15
20
  from pluginify.interfaces import data_modifiers
16
21
  from pluginify.utils.validators import PluginRegistryValidator
@@ -18,6 +23,21 @@ from pluginify.utils.validators import PluginRegistryValidator
18
23
  LOG = logging.getLogger(__name__)
19
24
 
20
25
 
26
+ @pytest.mark.parametrize("save_type", ["json", "yaml"])
27
+ def test_write_registered_plugins_replaces_file(tmp_path, save_type):
28
+ """Write a registry over an existing one, leaving no temporary files behind."""
29
+ reg_path = tmp_path / f"registered_plugins.{save_type}"
30
+ reg_path.write_text("old")
31
+ plugins = {"algorithms": {"fake_plugin": {"package": "fake"}}}
32
+
33
+ write_registered_plugins(str(tmp_path), plugins, save_type)
34
+
35
+ with open(reg_path) as fo:
36
+ written = json.load(fo) if save_type == "json" else yaml.safe_load(fo)
37
+ assert written == plugins
38
+ assert [p.name for p in tmp_path.iterdir()] == [reg_path.name]
39
+
40
+
21
41
  class FakeInterface:
22
42
  """Dummy fake interface used to cause appropriate errors from the PluginRegistry."""
23
43
 
@@ -124,6 +144,15 @@ class TestPluginRegistry:
124
144
  )
125
145
  assert self.real_reg_validator.registered_plugins
126
146
 
147
+ def test_utils_modules_are_skipped(self):
148
+ """Ensure utility modules under plugin trees are not registered as plugins."""
149
+ plugins = {}
150
+ error_message = add_class_plugin(
151
+ "pluginify", "plugins/classes/data_modifiers/utils/helper.py", plugins
152
+ )
153
+ assert error_message == ""
154
+ assert plugins == {}
155
+
127
156
  def test_disabled_registry_creation(self):
128
157
  """Assert that the registries are not automatically rebuilt.
129
158
 
@@ -1,87 +0,0 @@
1
- name: CodeQuality
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- permissions:
10
- contents: read
11
-
12
- concurrency:
13
- group: ${{ github.workflow }}-${{ github.ref }}
14
- cancel-in-progress: true
15
-
16
- env:
17
- UV_SYSTEM_PYTHON: true
18
-
19
- jobs:
20
- lint:
21
- continue-on-error: true
22
- runs-on: ${{ vars.RUNNER }}
23
- steps:
24
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
25
-
26
- - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
27
- with:
28
- python-version: "3.13"
29
-
30
- - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
31
- with:
32
- go-version: stable
33
- cache: false
34
-
35
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
36
-
37
- - name: Install linters
38
- run: |
39
- uv pip install ruff blacken-docs mdformat validate-pyproject
40
- go install github.com/rhysd/actionlint/cmd/actionlint@latest
41
- go install github.com/google/yamlfmt/cmd/yamlfmt@latest
42
- echo "$HOME/go/bin" >> "$GITHUB_PATH"
43
-
44
- - name: Ruff (lint)
45
- run: ruff check ./pluginify
46
-
47
- - name: Ruff (format)
48
- if: success() || failure()
49
- run: ruff format --check ./pluginify
50
-
51
- - name: Blacken-docs
52
- if: success() || failure()
53
- run: git ls-files '*.rst' '*.md' | xargs -r blacken-docs --check
54
-
55
- - name: mdformat
56
- if: success() || failure()
57
- run: mdformat --check .
58
-
59
- - name: actionlint
60
- if: success() || failure()
61
- run: actionlint
62
-
63
- - name: validate-pyproject
64
- if: success() || failure()
65
- run: validate-pyproject pyproject.toml
66
-
67
- - name: yamlfmt
68
- if: success() || failure()
69
- run: yamlfmt -lint
70
-
71
- mypy:
72
- continue-on-error: true
73
- runs-on: ${{ vars.RUNNER }}
74
- steps:
75
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
76
-
77
- - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
78
- with:
79
- python-version: "3.13"
80
-
81
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
82
-
83
- - name: Install package with all extras
84
- run: uv pip install -e ".[doc,lint,test]" mypy
85
-
86
- - name: Run mypy
87
- run: mypy ./pluginify
@@ -1,216 +0,0 @@
1
- name: Nightly Badge Generation
2
-
3
- on:
4
- schedule:
5
- - cron: '0 2 * * *'
6
- workflow_dispatch:
7
-
8
- permissions:
9
- contents: write
10
-
11
- concurrency:
12
- group: ${{ github.workflow }}-${{ github.ref }}
13
- cancel-in-progress: true
14
-
15
- env:
16
- UV_SYSTEM_PYTHON: true
17
-
18
- jobs:
19
- check-commits:
20
- runs-on: ${{ vars.RUNNER }}
21
- outputs:
22
- has_new_commits: ${{ steps.check.outputs.has_new_commits }}
23
- steps:
24
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
25
- with:
26
- fetch-depth: 0
27
-
28
- - name: Check for new commits in last 24 hours
29
- id: check
30
- run: |
31
- COMMITS=$(git log --oneline --since="25 hours ago" --no-merges | grep -c -v "\[skip ci\]")
32
- if [ "$COMMITS" -gt 0 ]; then
33
- echo "has_new_commits=true" >> "$GITHUB_OUTPUT"
34
- echo "Found $COMMITS new commits in the last 25 hours"
35
- else
36
- echo "has_new_commits=false" >> "$GITHUB_OUTPUT"
37
- echo "No new commits in the last 25 hours"
38
- fi
39
-
40
- generate-badges:
41
- needs: check-commits
42
- if: needs.check-commits.outputs.has_new_commits == 'true'
43
- runs-on: ${{ vars.RUNNER }}
44
-
45
- steps:
46
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
47
- with:
48
- fetch-depth: 0
49
-
50
- - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
51
- with:
52
- python-version: "3.13"
53
-
54
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
55
-
56
- - name: Install dependencies
57
- run: |
58
- uv pip install -e ".[doc,lint,test]"
59
- uv pip install pytest mypy ruff commitizen coverage "genbadge[all]"
60
-
61
- - name: Create directories
62
- run: |
63
- mkdir -p .github/badges
64
- mkdir -p reports
65
-
66
- - name: Run pytest with coverage
67
- id: pytest
68
- continue-on-error: true
69
- run: coverage run -m pytest --junitxml=reports/junit.xml
70
-
71
- - name: Generate pytest badge
72
- run: |
73
- if [ -f reports/junit.xml ]; then
74
- genbadge tests -i reports/junit.xml -o .github/badges/tests-badge.svg -n "tests"
75
- else
76
- python -c "
77
- from genbadge import Badge
78
- b = Badge(left_txt='tests', right_txt='error', color='red')
79
- b.write_to('.github/badges/tests-badge.svg', use_shields=False)
80
- "
81
- fi
82
-
83
- - name: Generate coverage badge
84
- run: |
85
- if [ -f coverage.xml ]; then
86
- genbadge coverage -i coverage.xml -o .github/badges/coverage-badge.svg -n "coverage"
87
- else
88
- python -c "
89
- from genbadge import Badge
90
- b = Badge(left_txt='coverage', right_txt='error', color='red')
91
- b.write_to('.github/badges/coverage-badge.svg', use_shields=False)
92
- "
93
- fi
94
-
95
- - name: Run mypy
96
- id: mypy
97
- continue-on-error: true
98
- run: mypy ./pluginify --junit-xml reports/mypy.xml || true
99
-
100
- - name: Generate mypy badge
101
- run: |
102
- if [ -f reports/mypy.xml ]; then
103
- genbadge tests -i reports/mypy.xml -o .github/badges/mypy-badge.svg -n "mypy"
104
- else
105
- python -c "
106
- from genbadge import Badge
107
- b = Badge(left_txt='mypy', right_txt='passing', color='brightgreen')
108
- b.write_to('.github/badges/mypy-badge.svg', use_shields=False)
109
- "
110
- fi
111
-
112
- - name: Run ruff
113
- id: ruff
114
- continue-on-error: true
115
- run: ruff check ./pluginify --output-format=junit -o reports/ruff.xml || true
116
-
117
- - name: Generate ruff badge
118
- run: |
119
- if [ -f reports/ruff.xml ] && [ -s reports/ruff.xml ]; then
120
- if grep -q "<testcase" reports/ruff.xml; then
121
- genbadge tests -i reports/ruff.xml -o .github/badges/ruff-badge.svg -n "ruff"
122
- else
123
- python -c "
124
- from genbadge import Badge
125
- b = Badge(left_txt='ruff', right_txt='passing', color='brightgreen')
126
- b.write_to('.github/badges/ruff-badge.svg', use_shields=False)
127
- "
128
- fi
129
- else
130
- python -c "
131
- from genbadge import Badge
132
- b = Badge(left_txt='ruff', right_txt='passing', color='brightgreen')
133
- b.write_to('.github/badges/ruff-badge.svg', use_shields=False)
134
- "
135
- fi
136
-
137
- - name: Test install
138
- id: install
139
- continue-on-error: true
140
- run: uv pip install -e . --reinstall
141
-
142
- - name: Generate install badge
143
- run: |
144
- if [ "${{ steps.install.outcome }}" == "success" ]; then
145
- python -c "
146
- from genbadge import Badge
147
- b = Badge(left_txt='install', right_txt='passing', color='brightgreen')
148
- b.write_to('.github/badges/install-badge.svg', use_shields=False)
149
- "
150
- else
151
- python -c "
152
- from genbadge import Badge
153
- b = Badge(left_txt='install', right_txt='failing', color='red')
154
- b.write_to('.github/badges/install-badge.svg', use_shields=False)
155
- "
156
- fi
157
-
158
- - name: Check commitizen
159
- id: commitizen
160
- continue-on-error: true
161
- env:
162
- COMMITIZEN_START_REV: "9d83e6984277ccbe6c5f6713f65b75a90e7c7e40"
163
- run: cz check --rev-range ${{ env.COMMITIZEN_START_REV }}..HEAD
164
-
165
- - name: Generate commitizen badge
166
- run: |
167
- if [ "${{ steps.commitizen.outcome }}" == "success" ]; then
168
- python -c "
169
- from genbadge import Badge
170
- b = Badge(left_txt='commitizen', right_txt='passing', color='brightgreen')
171
- b.write_to('.github/badges/commitizen-badge.svg', use_shields=False)
172
- "
173
- else
174
- python -c "
175
- from genbadge import Badge
176
- b = Badge(left_txt='commitizen', right_txt='failing', color='red')
177
- b.write_to('.github/badges/commitizen-badge.svg', use_shields=False)
178
- "
179
- fi
180
-
181
- - name: Check for badge changes
182
- id: badge_changes
183
- run: |
184
- git add .github/badges/*.svg
185
- if git diff --cached --quiet; then
186
- echo "has_changes=false" >> "$GITHUB_OUTPUT"
187
- echo "No badge changes detected"
188
- else
189
- echo "has_changes=true" >> "$GITHUB_OUTPUT"
190
- echo "Badge changes detected:"
191
- git diff --cached --name-status .github/badges/
192
- fi
193
-
194
- - name: Commit and push badges
195
- if: steps.badge_changes.outputs.has_changes == 'true'
196
- run: |
197
- git config --local user.email "github-actions[bot]@users.noreply.github.com"
198
- git config --local user.name "github-actions[bot]"
199
- git commit -m "chore: update badges [skip ci]"
200
- git push
201
-
202
- - name: Generate summary
203
- run: |
204
- {
205
- echo "## Badge Generation Summary"
206
- echo ""
207
- echo "| Check | Status |"
208
- echo "|-------|--------|"
209
- echo "| Pytest & Coverage | ${{ steps.pytest.outcome }} |"
210
- echo "| Mypy | ${{ steps.mypy.outcome }} |"
211
- echo "| Ruff | ${{ steps.ruff.outcome }} |"
212
- echo "| Install | ${{ steps.install.outcome }} |"
213
- echo "| Commitizen | ${{ steps.commitizen.outcome }} |"
214
- echo ""
215
- echo "Badges committed: ${{ steps.badge_changes.outputs.has_changes }}"
216
- } >> "$GITHUB_STEP_SUMMARY"
File without changes