python-base-command 0.1.2__tar.gz → 0.1.3__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 (42) hide show
  1. {python_base_command-0.1.2 → python_base_command-0.1.3}/PKG-INFO +1 -1
  2. {python_base_command-0.1.2 → python_base_command-0.1.3}/pyproject.toml +1 -1
  3. {python_base_command-0.1.2 → python_base_command-0.1.3}/python_base_command/runner.py +22 -10
  4. python_base_command-0.1.3/usage_example/commands/registry_cmd.py +33 -0
  5. {python_base_command-0.1.2 → python_base_command-0.1.3}/.config/README.md +0 -0
  6. {python_base_command-0.1.2 → python_base_command-0.1.3}/.config/black.toml +0 -0
  7. {python_base_command-0.1.2 → python_base_command-0.1.3}/.config/pylintrc +0 -0
  8. {python_base_command-0.1.2 → python_base_command-0.1.3}/.config/pylintrc_tests +0 -0
  9. {python_base_command-0.1.2 → python_base_command-0.1.3}/.config/ruff.toml +0 -0
  10. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/CODEOWNERS +0 -0
  11. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  12. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/chat-titles.md +0 -0
  13. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/claude-sonnet-4.md +0 -0
  14. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/gemini-2.5-pro.md +0 -0
  15. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/gpt-4.1.md +0 -0
  16. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/gpt-4o.md +0 -0
  17. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/gpt-5-mini.md +0 -0
  18. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/gpt-5.md +0 -0
  19. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/nes-tab-completion.md +0 -0
  20. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/instructions/IDE Agent/prompt.md +0 -0
  21. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/pull_request_template.md +0 -0
  22. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/workflows/lint.yml +0 -0
  23. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/workflows/publish_to_pypi.yml +0 -0
  24. {python_base_command-0.1.2 → python_base_command-0.1.3}/.github/workflows/tests.yml +0 -0
  25. {python_base_command-0.1.2 → python_base_command-0.1.3}/.gitignore +0 -0
  26. {python_base_command-0.1.2 → python_base_command-0.1.3}/.pre-commit-config.yaml +0 -0
  27. {python_base_command-0.1.2 → python_base_command-0.1.3}/CHANGELOG.md +0 -0
  28. {python_base_command-0.1.2 → python_base_command-0.1.3}/LICENSE +0 -0
  29. {python_base_command-0.1.2 → python_base_command-0.1.3}/MANIFEST.in +0 -0
  30. {python_base_command-0.1.2 → python_base_command-0.1.3}/README.md +0 -0
  31. {python_base_command-0.1.2 → python_base_command-0.1.3}/Taskfile.yml +0 -0
  32. {python_base_command-0.1.2 → python_base_command-0.1.3}/cli.py +0 -0
  33. {python_base_command-0.1.2 → python_base_command-0.1.3}/pytest.ini +0 -0
  34. {python_base_command-0.1.2 → python_base_command-0.1.3}/python_base_command/__init__.py +0 -0
  35. {python_base_command-0.1.2 → python_base_command-0.1.3}/python_base_command/base.py +0 -0
  36. {python_base_command-0.1.2 → python_base_command-0.1.3}/python_base_command/registry.py +0 -0
  37. {python_base_command-0.1.2 → python_base_command-0.1.3}/python_base_command/utils.py +0 -0
  38. {python_base_command-0.1.2 → python_base_command-0.1.3}/tests/__init__.py +0 -0
  39. {python_base_command-0.1.2 → python_base_command-0.1.3}/tests/test_base_command.py +0 -0
  40. {python_base_command-0.1.2 → python_base_command-0.1.3}/usage_example/__init__.py +0 -0
  41. {python_base_command-0.1.2 → python_base_command-0.1.3}/usage_example/commands/__init__.py +0 -0
  42. {python_base_command-0.1.2 → python_base_command-0.1.3}/usage_example/commands/greet.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-base-command
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Django-style BaseCommand framework for standalone Python CLI tools
5
5
  Project-URL: Homepage, https://github.com/aviz92/python-base-command
6
6
  Project-URL: Repository, https://github.com/aviz92/python-base-command
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-base-command"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Django-style BaseCommand framework for standalone Python CLI tools"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -48,6 +48,7 @@ from typing import Optional
48
48
 
49
49
  from custom_python_logger import get_logger
50
50
 
51
+ from . import CommandRegistry
51
52
  from .base import BaseCommand, CommandError
52
53
 
53
54
  logger = get_logger("python-base-command")
@@ -78,8 +79,15 @@ class Runner:
78
79
 
79
80
  def _discover(self) -> dict[str, type[BaseCommand]]:
80
81
  """
81
- Walk ``self._commands_dir`` and import every non-private module that
82
- exposes a ``Command`` class inheriting from ``BaseCommand``.
82
+ Walk ``self._commands_dir`` and import every non-private module.
83
+
84
+ Two conventions are supported per module:
85
+
86
+ 1. **Classic** — a class literally named ``Command`` that subclasses
87
+ ``BaseCommand``. The command name is the module's file stem.
88
+ 2. **Registry** — one or more ``CommandRegistry`` instances defined at
89
+ module level. Every command registered on those instances is merged
90
+ in; the names come from the registry (not the file stem).
83
91
  """
84
92
  commands: dict[str, type[BaseCommand]] = {}
85
93
 
@@ -94,15 +102,19 @@ class Runner:
94
102
  if module is None:
95
103
  continue
96
104
 
105
+ # --- 1. Classic: a top-level class named "Command" ---
97
106
  command_class = getattr(module, "Command", None)
98
- if command_class is None:
99
- continue
100
- if not (
101
- isinstance(command_class, type) and issubclass(command_class, BaseCommand)
102
- ):
103
- continue
104
-
105
- commands[path.stem] = command_class
107
+ if command_class is not None and isinstance(command_class, type) and issubclass(command_class, BaseCommand):
108
+ commands[path.stem] = command_class
109
+
110
+ # --- 2. Registry: any CommandRegistry instances in the module ---
111
+ for attr_name in dir(module):
112
+ obj = getattr(module, attr_name)
113
+ if isinstance(obj, CommandRegistry):
114
+ for name in obj.list_commands():
115
+ cls = obj.get(name)
116
+ if cls is not None:
117
+ commands[name] = cls
106
118
 
107
119
  return commands
108
120
 
@@ -0,0 +1,33 @@
1
+ from python_base_command import BaseCommand, CommandError, CommandRegistry
2
+
3
+ registry = CommandRegistry()
4
+
5
+
6
+ @registry.register("greet2")
7
+ class Greet2Command(BaseCommand):
8
+ help = "Greet a user"
9
+
10
+ def add_arguments(self, parser):
11
+ parser.add_argument("name", type=str)
12
+
13
+ def handle(self, **kwargs):
14
+ self.logger.info(f"Hello, {kwargs['name']}!")
15
+
16
+
17
+ @registry.register("export")
18
+ class ExportCommand(BaseCommand):
19
+ help = "Export data"
20
+
21
+ def add_arguments(self, parser):
22
+ parser.add_argument("--format", choices=["csv", "json"], default="csv")
23
+ parser.add_argument("--dry-run", action="store_true")
24
+
25
+ def handle(self, **kwargs):
26
+ if kwargs["dry_run"]:
27
+ self.logger.warning("Dry run — no files written.")
28
+ return
29
+ self.logger.info(f"Exported as {kwargs['format']}.")
30
+
31
+
32
+ if __name__ == "__main__":
33
+ registry.run()