From 376fd34d601f582cd30523ed21f703e94e39a3d3 Mon Sep 17 00:00:00 2001 From: ulises-jeremias Date: Thu, 16 Jul 2026 03:01:32 -0300 Subject: [PATCH] feat(core): add CPA_* typed errors and abort semantics Closes #24 Co-authored-by: Cursor --- .../src/create_python_app_core/__init__.py | 16 ++++++++ .../src/create_python_app_core/errors.py | 37 +++++++++++++++++++ .../tests/test_errors.py | 12 ++++++ 3 files changed, 65 insertions(+) create mode 100644 packages/create-python-app-core/src/create_python_app_core/errors.py create mode 100644 packages/create-python-app-core/tests/test_errors.py diff --git a/packages/create-python-app-core/src/create_python_app_core/__init__.py b/packages/create-python-app-core/src/create_python_app_core/__init__.py index f18acff..ff6787c 100644 --- a/packages/create-python-app-core/src/create_python_app_core/__init__.py +++ b/packages/create-python-app-core/src/create_python_app_core/__init__.py @@ -10,9 +10,25 @@ create_python_app, print_env_info, ) +from create_python_app_core.errors import ( + NON_EMPTY_DIR_ERROR_CODE, + ConfigParseError, + CpaError, + ManifestLoadError, + NonEmptyTargetDirectoryError, + PackageManagerFallbackError, + ScaffoldAbortedError, +) __all__ = [ "__version__", + "CpaError", + "ConfigParseError", + "ManifestLoadError", + "PackageManagerFallbackError", + "ScaffoldAbortedError", + "NonEmptyTargetDirectoryError", + "NON_EMPTY_DIR_ERROR_CODE", "CPA_USER_AGENT", "check_for_latest_version", "check_python_version", diff --git a/packages/create-python-app-core/src/create_python_app_core/errors.py b/packages/create-python-app-core/src/create_python_app_core/errors.py new file mode 100644 index 0000000..9afa3ed --- /dev/null +++ b/packages/create-python-app-core/src/create_python_app_core/errors.py @@ -0,0 +1,37 @@ +"""Typed CPA errors (mirrors create-node-app-core/errors.ts).""" + +from __future__ import annotations + + +class CpaError(Exception): + """Base error with a stable machine-readable code.""" + + code: str = "CPA_ERROR" + + def __init__(self, message: str, *, code: str | None = None) -> None: + super().__init__(message) + if code is not None: + self.code = code + + +class ConfigParseError(CpaError): + code = "CPA_CONFIG_PARSE" + + +class ManifestLoadError(CpaError): + code = "CPA_MANIFEST_LOAD" + + +class PackageManagerFallbackError(CpaError): + code = "CPA_PM_FALLBACK" + + +class ScaffoldAbortedError(CpaError): + code = "CPA_ABORTED" + + +class NonEmptyTargetDirectoryError(CpaError): + code = "CPA_NON_EMPTY_TARGET_DIR" + + +NON_EMPTY_DIR_ERROR_CODE = NonEmptyTargetDirectoryError.code diff --git a/packages/create-python-app-core/tests/test_errors.py b/packages/create-python-app-core/tests/test_errors.py new file mode 100644 index 0000000..78f3aa1 --- /dev/null +++ b/packages/create-python-app-core/tests/test_errors.py @@ -0,0 +1,12 @@ +from create_python_app_core import ( + NON_EMPTY_DIR_ERROR_CODE, + ConfigParseError, + CpaError, + NonEmptyTargetDirectoryError, +) + + +def test_error_codes_use_cpa_prefix() -> None: + assert ConfigParseError("x").code.startswith("CPA_") + assert NonEmptyTargetDirectoryError("x").code == NON_EMPTY_DIR_ERROR_CODE + assert isinstance(ConfigParseError("x"), CpaError)