diff --git a/CHANGELOG.md b/CHANGELOG.md
index cfd6e5f8..f84da141 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
---
+## v26.09.11 (2026-09-30)
+
+### Fixed
+
+- `TaskScheduler.discover()` skips custom descriptors before binding
+ scheduled methods. Scanning a Pydantic settings instance no longer evaluates
+ `model_fields` or `model_computed_fields`, which emitted
+ `PydanticDeprecatedSince211` warnings in 26.09.10. Discovery still finds and
+ executes ordinary, inherited, static and class methods marked `@scheduled`.
+- Regression coverage verifies both the absence of Pydantic warnings and that
+ discovery never executes custom descriptor, property or cached-property getters.
+
+### Upgrading
+
+- No configuration or public API changes. No warning filters are required.
+ Custom descriptors are not evaluated to obtain scheduled callables; expose
+ scheduled jobs as methods.
+
## v26.09.10 (2026-09-29)
Reusable infrastructure hardening discovered while building Firefly Weave.
diff --git a/README.md b/README.md
index 997ab623..5c9e2b60 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
@@ -850,13 +850,13 @@ See **[`samples/lumen/`](samples/lumen/README.md)** for an end-to-end DDD micros
```bash
# Install the latest release (uv)
-uv add "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.9.10-py3-none-any.whl"
+uv add "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.9.11-py3-none-any.whl"
# Install with specific extras
-uv add "pyfly[web,data-relational,cache] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.9.10-py3-none-any.whl"
+uv add "pyfly[web,data-relational,cache] @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.9.11-py3-none-any.whl"
# Or with pip
-pip install "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.9.10-py3-none-any.whl"
+pip install "pyfly @ https://github.com/fireflyframework/fireflyframework-pyfly/releases/latest/download/pyfly-26.9.11-py3-none-any.whl"
```
### One-Line Install (CLI + Framework)
@@ -1184,6 +1184,7 @@ The git tag and human-readable display use the leading-zero form (`v26.05.01`);
The full release history lives in **[CHANGELOG.md](CHANGELOG.md)** ([Keep a Changelog](https://keepachangelog.com/) format). Recent highlights:
+- **`v26.09.11`** (2026-09-30) — scheduled-method discovery skips custom descriptors, avoiding Pydantic instance-field deprecation warnings.
- **`v26.09.10`** (2026-09-29) — bounded JWKS rotation and HTTP responses, standalone OAuth acquisition, complete failed-start cleanup, substitutable observability and exact-byte webhook validation.
- **`v26.09.09`** (2026-09-28) — a restarted context no longer resolves an interface through the previous run's binding (a second `start()` of the same context failed with a `KeyError`).
- **`v26.09.08`** (2026-09-28) — **the data layer on a unit of work**: Spring `@transactional` semantics on every backend (all seven propagations, rollback-only, isolation, read-only replica routing, timeouts), one `DataSourceRegistry` for every engine, repositories that commit per call outside a transaction, the framework's stores in the business transaction, MongoDB transactions on a replica set, and a tested matrix of SQLite, PostgreSQL, MySQL, MariaDB and MongoDB. Several changes are breaking: read the upgrade guide in the [CHANGELOG](CHANGELOG.md).
diff --git a/docs/cli.md b/docs/cli.md
index 43267233..5a97ed03 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -775,7 +775,7 @@ Missing optional tools are shown with a `-` dash indicator (dimmed), while missi
Verifies that PyFly itself is importable and displays the installed version:
```
-✓ pyfly v26.09.10
+✓ pyfly v26.09.11
```
### Summary
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 2b7dffd0..c0ecb2ef 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -480,7 +480,7 @@ ______ ___.__._/ ____\ | ___.__.
| __// ____| |__| |____/ ____|
|__| \/ \/
- PyFly v26.09.10 | Python 3.12.0
+ PyFly v26.09.11 | Python 3.12.0
2026-01-15T10:30:00Z [info] starting_application app=my-service version=0.1.0
2026-01-15T10:30:00Z [info] no_active_profiles message=No active profiles set, falling back to default
diff --git a/docs/installation.md b/docs/installation.md
index dfd8aceb..f869d79a 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -513,7 +513,7 @@ PyFly Doctor
✓ mypy — Type checker
PyFly packages:
- ✓ pyfly v26.09.10
+ ✓ pyfly v26.09.11
All checks passed!
```
diff --git a/docs/modules/core.md b/docs/modules/core.md
index 8ff0e963..79bc4404 100644
--- a/docs/modules/core.md
+++ b/docs/modules/core.md
@@ -599,7 +599,7 @@ class BannerMode(enum.Enum):
| Mode | Behavior |
|---|---|
| `TEXT` | Full ASCII art banner (default) with a framework version line. |
-| `MINIMAL` | Single line: `:: PyFly :: (v26.09.10)` |
+| `MINIMAL` | Single line: `:: PyFly :: (v26.09.11)` |
| `OFF` | No banner output at all. |
### BannerPrinter Class
@@ -640,7 +640,7 @@ ______ ___.__._/ ____\ | ___.__.
| __// ____| |__| |____/ ____|
|__| \/ \/
-:: PyFly Framework :: (v26.09.10)
+:: PyFly Framework :: (v26.09.11)
```
### Custom Banner Files
diff --git a/docs/modules/scheduling.md b/docs/modules/scheduling.md
index 4bcdbbae..a65b7b1f 100644
--- a/docs/modules/scheduling.md
+++ b/docs/modules/scheduling.md
@@ -457,7 +457,12 @@ scheduler = TaskScheduler(lock=RedisLock(redis_client))
Call `discover()` with a list of bean instances. It scans every public attribute
(names not starting with `_`) and records those marked with
-`__pyfly_scheduled__ = True`. Returns the number of scheduled methods found:
+`__pyfly_scheduled__ = True`. Since v26.09.11, discovery skips custom
+descriptors as well as properties and cached properties before binding methods.
+Pydantic settings can be included without instance-field deprecation warnings.
+Ordinary, inherited, static and class methods remain discoverable; expose jobs
+as methods rather than descriptor-produced callables. Returns the number of
+scheduled methods found:
```python
beans = [metrics_collector, data_syncer, report_generator]
diff --git a/docs/versioning.md b/docs/versioning.md
index 82d3049f..8fd6deb0 100644
--- a/docs/versioning.md
+++ b/docs/versioning.md
@@ -64,6 +64,7 @@ rare case where a substantial change needs an additional review window.
| Version | Date | Notes |
|---------|------|-------|
+| `26.09.11` | 2026-09-30 | Scheduled-method discovery avoids evaluating Pydantic and custom descriptors. |
| `26.09.10` | 2026-09-29 | Bounded identity and HTTP primitives, lifecycle ownership, substitutable observability and exact-byte webhooks. |
| `26.09.09` | 2026-09-28 | A restarted context drops the previous run's interface bindings with its registrations, so a second `start()` of the same context no longer fails with a `KeyError` (a test suite whose modules share an application). |
| `26.09.08` | 2026-09-28 | The data layer on a unit of work: Spring `@transactional` semantics on every backend, one `DataSourceRegistry` for every engine, repositories that commit per call outside a transaction, the framework stores in the business transaction, MongoDB transactions, and a backend matrix (SQLite, PostgreSQL, MySQL, MariaDB, MongoDB replica set). Several breaking changes: see the changelog. |
@@ -93,17 +94,17 @@ shipped, with the version metadata updated.
```python
import pyfly
-print(pyfly.__version__) # → "26.09.10"
+print(pyfly.__version__) # → "26.09.11"
```
```bash
-pyfly --version # → 26.09.10
+pyfly --version # → 26.09.11
```
The startup banner displays the leading-zero form:
```
-:: PyFly Framework :: (v26.09.10) (Python 3.13.9)
+:: PyFly Framework :: (v26.09.11) (Python 3.13.9)
```
---
diff --git a/install.sh b/install.sh
index 0e7dab14..56e55773 100755
--- a/install.sh
+++ b/install.sh
@@ -26,7 +26,7 @@ set -euo pipefail
# ── Constants ──────────────────────────────────────────────────────────────────
-PYFLY_VERSION="26.09.10"
+PYFLY_VERSION="26.09.11"
PYFLY_REPO="https://github.com/fireflyframework/fireflyframework-pyfly.git"
DEFAULT_INSTALL_DIR="$HOME/.pyfly"
MIN_PYTHON_MAJOR=3
diff --git a/pyproject.toml b/pyproject.toml
index 20c47434..e695f282 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ name = "pyfly"
# CalVer YY.MM.PATCH — package metadata uses PEP 440 normalized form (26.5.4);
# git tag, GitHub release and human-readable display use leading-zero form
# (v26.05.04) to match the Java/.NET/Go siblings.
-version = "26.9.10"
+version = "26.9.11"
description = "The official Python implementation of the Firefly Framework — DI, CQRS, EDA, hexagonal architecture, and more."
readme = "README.md"
license = "Apache-2.0"
diff --git a/src/pyfly/__init__.py b/src/pyfly/__init__.py
index e5b42002..bd6267d9 100644
--- a/src/pyfly/__init__.py
+++ b/src/pyfly/__init__.py
@@ -13,4 +13,4 @@
# limitations under the License.
"""PyFly — Enterprise Python Framework."""
-__version__ = "26.09.10"
+__version__ = "26.09.11"
diff --git a/src/pyfly/scheduling/task_scheduler.py b/src/pyfly/scheduling/task_scheduler.py
index a603d0c6..f790f1e7 100644
--- a/src/pyfly/scheduling/task_scheduler.py
+++ b/src/pyfly/scheduling/task_scheduler.py
@@ -123,8 +123,9 @@ def executor(self) -> TaskExecutorPort:
def discover(self, beans: list[Any]) -> int:
"""Scan beans for @scheduled methods. Return number of scheduled methods found.
- For each bean, inspects all attributes. If an attribute is callable and
- has ``__pyfly_scheduled__ == True``, it is recorded for later scheduling.
+ For each bean, inspects public attributes without evaluating custom
+ descriptors. Callable attributes marked ``__pyfly_scheduled__ == True``
+ are recorded for later scheduling.
"""
count = 0
for bean in beans:
@@ -137,6 +138,15 @@ def discover(self, beans: list[Any]) -> int:
static_attr = inspect.getattr_static(bean, name, None)
if isinstance(static_attr, (property, functools.cached_property)):
continue
+ # Custom descriptors (including Pydantic's deprecated instance
+ # properties) can look like method descriptors on Python 3.12.
+ if hasattr(type(static_attr), "__get__") and not (
+ inspect.isfunction(static_attr)
+ or isinstance(static_attr, (staticmethod, classmethod))
+ or inspect.ismethoddescriptor(static_attr)
+ and type(static_attr).__module__ == "builtins"
+ ):
+ continue
try:
attr = getattr(bean, name)
except Exception: # noqa: BLE001
diff --git a/tests/scheduling/test_discovery_descriptors.py b/tests/scheduling/test_discovery_descriptors.py
new file mode 100644
index 00000000..1f1a591a
--- /dev/null
+++ b/tests/scheduling/test_discovery_descriptors.py
@@ -0,0 +1,100 @@
+# Copyright 2026 Firefly Software Foundation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Discovery must not evaluate bean descriptors while finding scheduled methods."""
+
+from __future__ import annotations
+
+import asyncio
+import warnings
+from datetime import timedelta
+from functools import cached_property
+from typing import Any
+
+import pytest
+from pydantic import BaseModel
+from pydantic.warnings import PydanticDeprecatedSince211
+
+from pyfly.scheduling.decorators import scheduled
+from pyfly.scheduling.task_scheduler import TaskScheduler
+
+
+@pytest.mark.asyncio
+async def test_pydantic_settings_do_not_warn_and_scheduled_bindings_still_execute() -> None:
+ observed = {name: asyncio.Event() for name in ("model", "inherited", "static", "class")}
+
+ class Settings(BaseModel):
+ name: str = "settings"
+
+ @scheduled(fixed_delay=timedelta(milliseconds=10))
+ async def model_job(self) -> None:
+ assert self.name == "settings"
+ observed["model"].set()
+
+ class ParentJobs:
+ @scheduled(fixed_delay=timedelta(milliseconds=10))
+ async def inherited_job(self) -> None:
+ observed["inherited"].set()
+
+ class Jobs(ParentJobs):
+ @staticmethod
+ @scheduled(fixed_delay=timedelta(milliseconds=10))
+ async def static_job() -> None:
+ observed["static"].set()
+
+ @classmethod
+ @scheduled(fixed_delay=timedelta(milliseconds=10))
+ async def class_job(cls) -> None:
+ assert cls is Jobs
+ observed["class"].set()
+
+ scheduler = TaskScheduler()
+ with warnings.catch_warnings(record=True) as caught:
+ warnings.simplefilter("always", PydanticDeprecatedSince211)
+ count = scheduler.discover([Settings(), Jobs()])
+ assert not caught, [str(warning.message) for warning in caught]
+ assert count == 4
+ try:
+ await scheduler.start()
+ await asyncio.wait_for(asyncio.gather(*(event.wait() for event in observed.values())), timeout=1)
+ finally:
+ await scheduler.stop()
+
+
+def test_discovery_does_not_evaluate_custom_descriptors_or_properties() -> None:
+ accessed: list[str] = []
+
+ class Descriptor:
+ def __get__(self, instance: Any, owner: type | None = None) -> Any:
+ accessed.append("descriptor")
+ raise RuntimeError("must not execute during discovery")
+
+ class Bean:
+ metadata = Descriptor()
+
+ @property
+ def value(self) -> str:
+ accessed.append("property")
+ raise RuntimeError("must not execute during discovery")
+
+ @cached_property
+ def cached(self) -> str:
+ accessed.append("cached_property")
+ raise RuntimeError("must not execute during discovery")
+
+ @scheduled(fixed_delay=timedelta(seconds=1))
+ async def job(self) -> None:
+ pass
+
+ assert TaskScheduler().discover([Bean()]) == 1
+ assert accessed == []
diff --git a/uv.lock b/uv.lock
index fdb52983..5ce573e8 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2337,7 +2337,7 @@ wheels = [
[[package]]
name = "pyfly"
-version = "26.9.10"
+version = "26.9.11"
source = { editable = "." }
dependencies = [
{ name = "pydantic" },
diff --git a/web/index.html b/web/index.html
index e86a244d..e0b3459b 100644
--- a/web/index.html
+++ b/web/index.html
@@ -91,7 +91,7 @@