From da696870bcdd4bf0a2fb98aa5bcdde28b826d7b0 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Mon, 18 May 2026 13:25:33 -0700 Subject: [PATCH 1/2] feat(collectors): introduce deprecation warning for CollectorBase and update documentation --- core/collectors/base.py | 16 ++++++++++++++++ core/tests/test_collectors_base.py | 19 +++++++++++++++++++ docs/Core_public_API.md | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/collectors/base.py b/core/collectors/base.py index 25cad8b..9aa7a0e 100644 --- a/core/collectors/base.py +++ b/core/collectors/base.py @@ -9,6 +9,7 @@ from __future__ import annotations +import warnings from abc import ABC, abstractmethod from django.core.management import call_command @@ -16,6 +17,7 @@ from core.collectors.base_collector import _CollectorLifecycleMixin +# TODO(v1.0): remove CollectorBase after migrating callers to AbstractCollector. class CollectorBase(_CollectorLifecycleMixin, ABC): """ Legacy abstract base for collectors run via management commands or YAML schedules. @@ -43,6 +45,17 @@ class CollectorBase(_CollectorLifecycleMixin, ABC): by the command layer without calling :meth:`~_CollectorLifecycleMixin.handle_error`. """ + def __init_subclass__(cls, **kwargs: object) -> None: + super().__init_subclass__(**kwargs) + if getattr(cls, "_skip_collector_base_deprecation", False): + return + warnings.warn( + "CollectorBase is deprecated; use AbstractCollector instead. " + "CollectorBase will be removed in v1.0.", + DeprecationWarning, + stacklevel=2, + ) + @abstractmethod def run(self) -> None: """ @@ -61,6 +74,9 @@ def run(self) -> None: class DjangoCommandCollector(CollectorBase): """Runs a registered Django management command by name.""" + # Library adapter: not an app-defined collector; skip CollectorBase deprecation noise. + _skip_collector_base_deprecation = True + __slots__ = ("command_name",) def __init__(self, command_name: str) -> None: diff --git a/core/tests/test_collectors_base.py b/core/tests/test_collectors_base.py index 7266004..fc9392c 100644 --- a/core/tests/test_collectors_base.py +++ b/core/tests/test_collectors_base.py @@ -1,5 +1,6 @@ """Tests for core.collectors base types.""" +import warnings from io import StringIO from unittest.mock import patch @@ -12,6 +13,24 @@ from core.collectors.command_base import BaseCollectorCommand +def test_collector_base_subclass_emits_deprecation_warning(): + assert getattr(DjangoCommandCollector, "_skip_collector_base_deprecation") is True + + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter("always", DeprecationWarning) + + class _WarnProbeCollector(CollectorBase): + def run(self) -> None: + return None + + dep = [w for w in recorded if issubclass(w.category, DeprecationWarning)] + assert dep, "expected DeprecationWarning when subclassing CollectorBase" + msg = str(dep[-1].message) + assert "CollectorBase" in msg + assert "AbstractCollector" in msg + assert "v1.0" in msg + + @pytest.mark.django_db def test_django_command_collector_run_calls_call_command(): with patch("core.collectors.base.call_command") as m: diff --git a/docs/Core_public_API.md b/docs/Core_public_API.md index 67fda01..6027cb1 100644 --- a/docs/Core_public_API.md +++ b/docs/Core_public_API.md @@ -6,7 +6,7 @@ The `core` Django app holds shared infrastructure. Treat the following as the ** | Import | Purpose | |--------|---------| -| `core.collectors.CollectorBase` | Legacy abstract `run()`, optional `sync_pinecone()`, `handle_error()` with structured logging. | +| `core.collectors.CollectorBase` | **Deprecated** (removed in v1.0): legacy abstract `run()`, optional `sync_pinecone()`, `handle_error()` with structured logging. Subclassing emits `DeprecationWarning` at class definition time. Prefer `AbstractCollector`; see [Migrating from `CollectorBase`](#migrating-from-collectorbase-to-abstractcollector). | | `core.collectors.AbstractCollector` | Preferred contract: `name`, `validate_config()`, `collect()`; concrete `run()` runs validate then collect; same lifecycle hooks as `CollectorBase`. | | `core.collectors.CollectorRunnable` | `Protocol` for objects returned from `get_collector()` (`run`, `sync_pinecone`, `handle_error`). | | `core.collectors.BaseCollectorCommand` | Thin `BaseCommand` adapter: runs `get_collector(**opts).run()` then `sync_pinecone()`. | From 0443e66adbfe0c37c57b2ab3a51201581faad17f Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Mon, 18 May 2026 13:35:27 -0700 Subject: [PATCH 2/2] docs(Core_public_API): update deprecation notice for CollectorBase in documentation --- docs/Core_public_API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Core_public_API.md b/docs/Core_public_API.md index 6027cb1..d555975 100644 --- a/docs/Core_public_API.md +++ b/docs/Core_public_API.md @@ -6,7 +6,7 @@ The `core` Django app holds shared infrastructure. Treat the following as the ** | Import | Purpose | |--------|---------| -| `core.collectors.CollectorBase` | **Deprecated** (removed in v1.0): legacy abstract `run()`, optional `sync_pinecone()`, `handle_error()` with structured logging. Subclassing emits `DeprecationWarning` at class definition time. Prefer `AbstractCollector`; see [Migrating from `CollectorBase`](#migrating-from-collectorbase-to-abstractcollector). | +| `core.collectors.CollectorBase` | **Deprecated** (removed in v1.0): legacy abstract `run()`, optional `sync_pinecone()`, `handle_error()` with structured logging. Subclassing emits `DeprecationWarning` at class definition time. Prefer `AbstractCollector`| | `core.collectors.AbstractCollector` | Preferred contract: `name`, `validate_config()`, `collect()`; concrete `run()` runs validate then collect; same lifecycle hooks as `CollectorBase`. | | `core.collectors.CollectorRunnable` | `Protocol` for objects returned from `get_collector()` (`run`, `sync_pinecone`, `handle_error`). | | `core.collectors.BaseCollectorCommand` | Thin `BaseCommand` adapter: runs `get_collector(**opts).run()` then `sync_pinecone()`. |