diff --git a/cli/python/base_github_projects/engine.py b/cli/python/base_github_projects/engine.py index d7ea274..5964ef8 100644 --- a/cli/python/base_github_projects/engine.py +++ b/cli/python/base_github_projects/engine.py @@ -28,6 +28,7 @@ from .project_model import ConfigureAction, FieldUpdate, Finding, OwnerInfo from .project_model import ProjectArguments, ProjectField, ProjectInfo, ProjectSchema, ProjectView from .project_model import SelectFieldSpec, SelectOption +from .project_operations import ProjectOperations from .project_schema import ISSUE_DEFAULT_OUTPUT_ORDER # pylint: disable=unused-import from .project_schema import compare_schema from .project_schema import configuration_plan @@ -113,22 +114,57 @@ def run_command(args: ProjectArguments) -> int: raise ProjectUsageError(f"Unknown project command '{args.command}'.") +def project_operations() -> ProjectOperations: + return ProjectOperations( + ProjectError=ProjectError, + ProjectUsageError=ProjectUsageError, + add_project_item=add_project_item, + apply_missing_project_item_defaults=apply_missing_project_item_defaults, + backfill_repository_issues=backfill_repository_issues, + compare_schema=compare_schema, + configuration_plan=configuration_plan, + copy_missing_project_item_fields=copy_missing_project_item_fields, + copy_template_project=copy_template_project, + create_project=create_project, + create_single_select_field=create_single_select_field, + fetch_issue_id=fetch_issue_id, + fetch_project_fields=fetch_project_fields, + fetch_project_views=fetch_project_views, + find_owner_and_project=find_owner_and_project, + find_project_item_id=find_project_item_id, + issue_field_values_for_args=issue_field_values_for_args, + link_project_to_repository=link_project_to_repository, + missing_option_names=missing_option_names, + project_config_for_args=project_config_for_args, + project_field_defaults_for_config=project_field_defaults_for_config, + require_owner=require_owner, + require_repo=require_repo, + resolve_issue_field_updates=resolve_issue_field_updates, + schema_for_args=schema_for_args, + split_repo=split_repo, + update_item_field=update_item_field, + update_project=update_project, + update_single_select_field=update_single_select_field, + verify_standard_template_views=verify_standard_template_views, + ) + + def doctor_command(args: ProjectArguments) -> int: from .project_doctor_command import doctor_command as command - return command(args, ops=sys.modules[__name__]) + return command(args, ops=project_operations()) def configure_command(args: ProjectArguments) -> int: from .project_configure_command import configure_command as command - return command(args, ops=sys.modules[__name__]) + return command(args, ops=project_operations()) def issue_set_fields_command(args: ProjectArguments) -> int: from .project_issue_fields_command import issue_set_fields_command as command - return command(args, ops=sys.modules[__name__]) + return command(args, ops=project_operations()) def find_owner_and_project(owner: str, title: str) -> OwnerInfo: diff --git a/cli/python/base_github_projects/project_operations.py b/cli/python/base_github_projects/project_operations.py new file mode 100644 index 0000000..291d442 --- /dev/null +++ b/cli/python/base_github_projects/project_operations.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from types import SimpleNamespace + + +class ProjectOperations(SimpleNamespace): + """Explicit operations surface passed to GitHub Project command helpers.""" diff --git a/cli/python/base_github_projects/tests/test_engine_structure.py b/cli/python/base_github_projects/tests/test_engine_structure.py index 1a0d518..d41bcab 100644 --- a/cli/python/base_github_projects/tests/test_engine_structure.py +++ b/cli/python/base_github_projects/tests/test_engine_structure.py @@ -39,3 +39,12 @@ def test_project_git_helpers_are_split_from_engine() -> None: assert git_path.exists() assert "def infer_repo_from_git" in git_path.read_text(encoding="utf-8") assert "def infer_repo_from_git" not in engine_path.read_text(encoding="utf-8") + + +def test_project_operations_are_explicit_instead_of_module_ops() -> None: + engine_path = Path(engine.__file__) + operations_path = engine_path.with_name("project_operations.py") + + assert operations_path.exists() + assert "class ProjectOperations" in operations_path.read_text(encoding="utf-8") + assert "sys.modules[__name__]" not in engine_path.read_text(encoding="utf-8")