Skip to content

feat(transport)!: make the Redis and Kafka brokers optional extras - #321

Open
sebastianwessel wants to merge 1 commit into
mainfrom
feat/optional-broker-extras
Open

sebastianwessel wants to merge 1 commit into
mainfrom
feat/optional-broker-extras

Conversation

@sebastianwessel

Copy link
Copy Markdown
Contributor

Description

An application uses one broker, but every pip install eggai pulls in FastStream with both its Redis and its Kafka client, and import eggai loads aiokafka even in a Redis-only service (eggai.transport imported KafkaTransport eagerly). That is extra code to install, scan and patch in every image.

This makes each broker an extra: eggai[redis] installs FastStream and redis-py, eggai[kafka] installs FastStream and aiokafka, and plain eggai is the core (Agent, Channel, InMemoryTransport) with neither.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Test coverage improvement
  • CI/CD update
  • Dependency update

Related Issue

None.

Changes Made

  • Extras (sdk/pyproject.toml): faststream, redis and aiokafka are optional; redis = ["faststream", "redis"], kafka = ["faststream", "aiokafka"]. aiokafka's range is the one FastStream 0.7 declares for its own kafka extra (>=0.9,<0.15); redis keeps the SDK's >=7.2,<9.0.
  • Lazy transports (eggai/__init__.py, eggai/transport/__init__.py): KafkaTransport and RedisTransport are resolved on first use, from eggai and eggai.transport as before (module __getattr__, TYPE_CHECKING imports for type checkers and mkdocstrings). Without their extra they raise ImportError: KafkaTransport needs the 'kafka' extra: pip install 'eggai[kafka]'. A missing module of the SDK itself is re-raised as it is, never reported as a missing extra.
  • transport.lease_options: Agent imported transport.lease, which imports redis-py at module level, so the core needed the Redis client. The Redis-independent part (LEASE_OPTION_KEYS, LeaseOptions, resolve_lease_options, LeaseLostError, ProcessingTimeoutError) moves unchanged (byte-identical) to transport/lease_options.py; transport.lease re-exports it, so existing imports and the tests that patch lease.open_client keep working.
  • CI (ci.yaml, wheel check): installs the built wheel three times (core, [redis], [kafka]) and checks that the core has no broker package and names the missing extra, that [redis] has no aiokafka, and that [kafka] has no redis-py.
  • CLI template: the Kafka project's requirements.txt requires eggai[kafka]>=0.8.0 instead of a separate faststream[kafka] (0.8.0 still bundles Kafka, later versions use the extra).
  • Docs: install sections of both READMEs, redis-transport.md and kafka-transport.md.
  • Changelog: under [Unreleased] → Changed, marked BREAKING, with the migration.

Migration

Add the extra wherever eggai is required: eggai[redis], eggai[kafka], or eggai[redis,kafka] for both. No code change.

Testing

  • Existing tests pass locally (make test)
  • Added new tests for new functionality
  • Tested manually (describe below if applicable)

tests/test_optional_brokers.py runs fresh interpreters with broker packages blocked: the core runs an in-memory publish/subscribe round trip with no broker installed; each transport names its extra when its client or FastStream is missing; one broker installed is enough for its transport; a missing SDK module is not mistaken for a missing extra; both names resolve to the same classes as before and appear in dir(). Against main the three behaviour tests fail and the two regression guards pass. The test names and sources avoid the broker names, because conftest.py skips such tests when the broker is not running.

Manual Testing Steps

  1. Linux, Python 3.12, poetry install --all-extras with a throwaway Redis 7: 245 passed, 12 skipped; all 12 skips are Kafka tests (Kafka is not wired into CI either).
  2. ruff check, ruff format --check, mypy: clean. poetry check --lock: lock consistent.
  3. Built the wheel and installed it three ways: core = 8 packages (no FastStream, redis-py or aiokafka), [redis] = 11 (no aiokafka), [kafka] = 13 (no redis-py).
  4. griffe, which mkdocstrings uses, still resolves eggai.transport.KafkaTransport, RedisTransport, LeaseLostError and ProcessingTimeoutError to their classes with docstrings. The full docs build could not run locally: it needs the eggai-examples checkout.

Changelog

  • I have updated sdk/CHANGELOG.md under [Unreleased] section (required for code changes)

Checklist

  • My code follows the project's code style (make lint passes)
  • My code is properly formatted (make format applied)
  • I have added/updated tests that prove my fix/feature works
  • I have added/updated documentation as needed
  • All tests pass locally
  • I have reviewed my own code
  • My changes generate no new warnings
  • My commit messages follow the Conventional Commits standard

Additional Notes

  • Breaking for anyone relying on the old default, so this needs a minor version bump at release (0.8 → 0.9). The version is not bumped in this PR.
  • Kafka users get the same packages as before minus redis-py; Redis users minus aiokafka (and its async-timeout, packaging).

🤖 Generated with Claude Code

An application uses one broker, but every install pulled in FastStream with
both its Redis and its Kafka client, and `import eggai` loaded aiokafka even
for a Redis-only service.

- `pip install "eggai[redis]"` or `pip install "eggai[kafka]"` installs one
  broker client; `pip install eggai` is the core (Agent, Channel,
  InMemoryTransport) with neither.
- KafkaTransport and RedisTransport are imported on first use, from `eggai`
  and `eggai.transport` as before; without their extra they raise an
  ImportError naming it. A missing module of the SDK itself is not taken for a
  missing extra.
- The Redis-independent part of `transport.lease` (option keys, option
  resolution, LeaseLostError, ProcessingTimeoutError) moves unchanged to
  `transport.lease_options`, so Agent imports without the Redis client;
  `transport.lease` re-exports it.
- CI's wheel check installs the core, `[redis]` and `[kafka]` separately and
  checks that each brings only its own broker client.
- The CLI's Kafka template requires `eggai[kafka]`; README and the transport
  docs show the extras.

BREAKING CHANGE: a plain `pip install eggai` no longer installs a broker; add
the extra the application uses (`eggai[redis]`, `eggai[kafka]`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

QualOps Code Quality Analysis

Status: ⚠️ WARNINGS - Medium severity issues found

Summary

  • Total Issues: 5
  • Critical: 0 🔴
  • High: 0 🟠
  • Medium: 4 🟡
  • Low: 1 🟢
  • Files Analyzed: 15

🟡 Medium Issues (4)

  • sdk/eggai/transport/init.py:37 - bug
    The ModuleNotFoundError heuristic checks e.name against package names but e.name is a dotted module path, so the .split('.')[0] check may incorrectly suppress real import errors from sub-modules of the broker packages.

  • sdk/pyproject.toml:58 - bug
    The redis extra lists both faststream and redis packages, but faststream is declared as optional without being tied to a specific broker; installing eggai[redis] will pull in the entire FastStream package but not necessarily the FastStream Redis broker extras (e.g., faststream[redis]), meaning the Redis transport may be missing its actual broker dependencies.

  • sdk/eggai/transport/init.py:35 - bug
    __getattr__ swallows real ModuleNotFoundErrors from installed-but-broken extras by matching only the root package name, replacing genuine errors with a misleading 'needs the extra' ImportError.

...and 1 more medium issues

📊 Full Report

View detailed report


Powered by QualOps

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant