streamgate 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {streamgate-0.2.0 → streamgate-0.3.0}/.github/workflows/ci.yml +5 -4
- streamgate-0.3.0/.import_linter_cache/a1215eee6d5c7b9f11de3767d6965c4a9c703b3e.data.json +1 -0
- streamgate-0.3.0/.import_linter_cache/streamgate.meta.json +1 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/CHANGELOG.md +47 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/CONFIGURATION.md +51 -5
- {streamgate-0.2.0 → streamgate-0.3.0}/CONTRIBUTING.md +3 -2
- {streamgate-0.2.0 → streamgate-0.3.0}/PKG-INFO +53 -22
- {streamgate-0.2.0 → streamgate-0.3.0}/README.md +43 -21
- {streamgate-0.2.0 → streamgate-0.3.0}/README.zh-CN.md +43 -17
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/docker-compose.yml +3 -3
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/http_probe/README.md +13 -7
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/http_probe/produce.py +3 -2
- streamgate-0.3.0/examples/prod_pipeline/README.md +66 -0
- streamgate-0.3.0/examples/prod_pipeline/consume.py +96 -0
- streamgate-0.3.0/examples/prod_pipeline/health_server.py +46 -0
- streamgate-0.3.0/examples/prod_pipeline/models.py +29 -0
- streamgate-0.3.0/examples/prod_pipeline/produce.py +103 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/pure_pipeline/README.md +5 -3
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/redis_admission/README.md +16 -13
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/redis_admission/produce.py +8 -5
- streamgate-0.3.0/examples/sqlite_sink/README.md +53 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/sqlite_sink/consume.py +10 -5
- {streamgate-0.2.0 → streamgate-0.3.0}/pyproject.toml +17 -24
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/__init__.py +7 -6
- streamgate-0.3.0/src/streamgate/contrib/__init__.py +16 -0
- streamgate-0.3.0/src/streamgate/contrib/_deps.py +24 -0
- streamgate-0.3.0/src/streamgate/contrib/http_probe/__init__.py +19 -0
- {streamgate-0.2.0/examples → streamgate-0.3.0/src/streamgate/contrib}/http_probe/probe_signal.py +4 -1
- streamgate-0.3.0/src/streamgate/contrib/mssql_sink/__init__.py +30 -0
- streamgate-0.3.0/src/streamgate/contrib/redis_admission/__init__.py +39 -0
- {streamgate-0.2.0/examples → streamgate-0.3.0/src/streamgate/contrib}/redis_admission/admission.py +3 -3
- streamgate-0.2.0/examples/redis_admission/settings.py → streamgate-0.3.0/src/streamgate/contrib/redis_admission/config.py +2 -2
- {streamgate-0.2.0/examples → streamgate-0.3.0/src/streamgate/contrib}/redis_admission/existence.py +1 -1
- streamgate-0.3.0/src/streamgate/contrib/sql_sink/__init__.py +36 -0
- streamgate-0.3.0/src/streamgate/contrib/sql_sink/_dialects/__init__.py +5 -0
- {streamgate-0.2.0/examples/sqlite_sink → streamgate-0.3.0/src/streamgate/contrib/sql_sink}/backfill.py +6 -3
- {streamgate-0.2.0/examples/sqlite_sink → streamgate-0.3.0/src/streamgate/contrib/sql_sink}/classifier.py +3 -3
- {streamgate-0.2.0/examples/sqlite_sink → streamgate-0.3.0/src/streamgate/contrib/sql_sink}/config.py +4 -4
- {streamgate-0.2.0/examples/sqlite_sink → streamgate-0.3.0/src/streamgate/contrib/sql_sink}/engines.py +2 -2
- {streamgate-0.2.0/examples/sqlite_sink → streamgate-0.3.0/src/streamgate/contrib/sql_sink}/upsert.py +9 -8
- streamgate-0.3.0/src/streamgate/contrib/sqlite_sink/__init__.py +29 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/uv.lock +23 -12
- streamgate-0.2.0/.import_linter_cache/a1215eee6d5c7b9f11de3767d6965c4a9c703b3e.data.json +0 -1
- streamgate-0.2.0/.import_linter_cache/streamgate.meta.json +0 -1
- streamgate-0.2.0/examples/sqlite_sink/README.md +0 -48
- streamgate-0.2.0/examples/sqlite_sink/dialects/__init__.py +0 -1
- {streamgate-0.2.0 → streamgate-0.3.0}/.github/workflows/release.yml +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/.gitignore +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/.import_linter_cache/.gitignore +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/.import_linter_cache/5780e4798d429798e55d962d9844c38a9b5e0494.data.json +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/.import_linter_cache/CACHEDIR.TAG +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/AGENTS.md +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/LICENSE +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/http_probe/consume.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/http_probe/health_server.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/http_probe/models.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/pure_pipeline/consume.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/pure_pipeline/models.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/pure_pipeline/produce.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/redis_admission/models.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/sqlite_sink/models.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/examples/sqlite_sink/produce.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/config.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/consumer/__init__.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/consumer/classifier.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/consumer/dlq.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/consumer/loop.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/consumer/runner.py +0 -0
- {streamgate-0.2.0/examples/sqlite_sink/dialects → streamgate-0.3.0/src/streamgate/contrib/sql_sink/_dialects}/mssql.py +0 -0
- {streamgate-0.2.0/examples/sqlite_sink/dialects → streamgate-0.3.0/src/streamgate/contrib/sql_sink/_dialects}/sqlite.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/ingest/__init__.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/ingest/admission/__init__.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/ingest/admission/in_memory.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/ingest/admission/no_admission.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/ingest/gateway.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/ingest/producer.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/obs/__init__.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/obs/logging.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/obs/metrics.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/protocols.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/resilience/__init__.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/resilience/backpressure.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/resilience/health.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/specs.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/transport/__init__.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/transport/codec.py +0 -0
- {streamgate-0.2.0 → streamgate-0.3.0}/src/streamgate/transport/kafka.py +0 -0
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
run: uv sync
|
|
23
23
|
- name: Lint
|
|
24
24
|
run: uv run ruff check .
|
|
25
|
-
- name: Import contracts (
|
|
25
|
+
- name: Import contracts (layered architecture; core never imports contrib)
|
|
26
26
|
run: uv run lint-imports
|
|
27
27
|
- name: Type check
|
|
28
28
|
run: uv run pyright
|
|
@@ -31,8 +31,9 @@ jobs:
|
|
|
31
31
|
|
|
32
32
|
examples:
|
|
33
33
|
# Examples are the storefront: they must meet the same quality bar as the
|
|
34
|
-
# package (lint + typecheck).
|
|
35
|
-
#
|
|
34
|
+
# package (lint + typecheck). The contrib strategies the examples import
|
|
35
|
+
# carry their third-party libs via extras ([redis]/[sql]/[http]), pulled in
|
|
36
|
+
# by the dev group's self-referential dependency.
|
|
36
37
|
name: examples (lint + typecheck)
|
|
37
38
|
runs-on: ubuntu-latest
|
|
38
39
|
steps:
|
|
@@ -77,7 +78,7 @@ jobs:
|
|
|
77
78
|
steps:
|
|
78
79
|
- uses: actions/checkout@v4
|
|
79
80
|
- uses: astral-sh/setup-uv@v5
|
|
80
|
-
- name: Bare install (no extras —
|
|
81
|
+
- name: Bare install (no extras — contrib and its dependencies are opt-in)
|
|
81
82
|
run: |
|
|
82
83
|
uv venv bare
|
|
83
84
|
uv pip install --python bare/bin/python .
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"imports_by_module":{"streamgate":[["streamgate.obs.metrics",false,42,"from streamgate.obs.metrics import LoggingMetricsSink, MetricsSink"],["streamgate.consumer.classifier",false,26,"from streamgate.consumer.classifier import DefaultErrorClassifier"],["streamgate.consumer.runner",false,36,"from streamgate.consumer.runner import ConsumerWorker"],["streamgate.protocols",false,43,"from streamgate.protocols import ("],["streamgate.ingest.gateway",false,39,"from streamgate.ingest.gateway import IngestGateway"],["streamgate.config",false,25,"from streamgate.config import BackpressureConfig, ConsumerConfig, KafkaConfig"],["streamgate.ingest.admission.in_memory",false,37,"from streamgate.ingest.admission.in_memory import InMemoryAdmission"],["streamgate.ingest.producer",false,40,"from streamgate.ingest.producer import KafkaProducerService"],["streamgate.consumer.dlq",false,27,"from streamgate.consumer.dlq import ("],["streamgate.resilience.backpressure",false,66,"from streamgate.resilience.backpressure import ManualBackpressureSignal"],["streamgate.ingest.admission.no_admission",false,38,"from streamgate.ingest.admission.no_admission import NoAdmission"],["streamgate.resilience.health",false,67,"from streamgate.resilience.health import ("],["streamgate.consumer.loop",false,35,"from streamgate.consumer.loop import ConsumeRuntime, consume_loop"],["streamgate.transport.kafka",false,75,"from streamgate.transport.kafka import KafkaConsumerService"],["streamgate.specs",false,73,"from streamgate.specs import ConsumeSpec, IngestBinding, IngestRecordT"],["streamgate.transport.codec",false,74,"from streamgate.transport.codec import JsonEnvelopeCodec"],["streamgate.obs.logging",false,41,"from streamgate.obs.logging import configure_logger, logger"]],"streamgate.config":[["pydantic",false,8,"from pydantic import BaseModel, field_validator"]],"streamgate.consumer":[],"streamgate.consumer.classifier":[["streamgate.protocols",false,20,"from streamgate.protocols import ErrorKind"],["asyncio",false,16,"import asyncio"],["aiokafka",false,18,"from aiokafka.errors import KafkaError"]],"streamgate.consumer.dlq":[["asyncio",false,15,"import asyncio"],["json",false,16,"import json"],["datetime",false,18,"from datetime import datetime, timezone"],["aiokafka",false,20,"from aiokafka import AIOKafkaProducer"],["streamgate.config",false,22,"from streamgate.config import KafkaConfig"],["streamgate.obs.logging",false,23,"from streamgate.obs.logging import logger"],["streamgate.protocols",false,24,"from streamgate.protocols import ErrorKind, JsonObject, RecordWriter"],["dataclasses",false,17,"from dataclasses import dataclass, field"]],"streamgate.consumer.loop":[["asyncio",false,5,"import asyncio"],["collections",false,7,"from collections.abc import Callable"],["streamgate.transport.kafka",false,37,"from streamgate.transport.kafka import KafkaConsumerService, KafkaRecord"],["streamgate.protocols",false,26,"from streamgate.protocols import ("],["aiokafka",false,11,"from aiokafka.structs import OffsetAndMetadata, TopicPartition"],["streamgate.obs.logging",false,24,"from streamgate.obs.logging import logger"],["streamgate.consumer.classifier",false,14,"from streamgate.consumer.classifier import DefaultErrorClassifier"],["datetime",false,8,"from datetime import datetime, timezone"],["streamgate.config",false,13,"from streamgate.config import ConsumerConfig"],["streamgate.obs.metrics",false,25,"from streamgate.obs.metrics import DEFAULT_METRICS, MetricsSink"],["streamgate.specs",false,36,"from streamgate.specs import ConsumeSpec"],["streamgate.consumer.dlq",false,15,"from streamgate.consumer.dlq import ("],["aiokafka",false,10,"from aiokafka.errors import CommitFailedError"],["time",false,6,"import time"]],"streamgate.consumer.runner":[["streamgate.resilience.health",false,16,"from streamgate.resilience.health import ("],["signal",false,9,"import signal"],["streamgate.protocols",false,15,"from streamgate.protocols import ErrorClassifier, MessageCodec"],["asyncio",false,8,"import asyncio"],["streamgate.consumer.loop",false,13,"from streamgate.consumer.loop import ConsumeRuntime, consume_loop"],["streamgate.specs",false,20,"from streamgate.specs import ConsumeSpec"],["streamgate.obs.logging",false,14,"from streamgate.obs.logging import logger"],["streamgate.transport.kafka",false,22,"from streamgate.transport.kafka import KafkaConsumerService"],["streamgate.config",false,11,"from streamgate.config import ConsumerConfig, KafkaConfig"],["streamgate.consumer.dlq",false,12,"from streamgate.consumer.dlq import DlqProducer"],["streamgate.transport.codec",false,21,"from streamgate.transport.codec import JsonEnvelopeCodec"]],"streamgate.contrib":[],"streamgate.contrib._deps":[["typing",false,3,"from typing import NoReturn"]],"streamgate.contrib.http_probe":[["streamgate.contrib._deps",false,9,"from streamgate.contrib._deps import require_extra_import"],["streamgate.contrib.http_probe.probe_signal",false,12,"from streamgate.contrib.http_probe.probe_signal import ("]],"streamgate.contrib.http_probe.probe_signal":[["streamgate.protocols",false,25,"from streamgate.protocols import BackpressureSnapshot, ProbeResult"],["httpx",false,22,"import httpx"],["streamgate",false,24,"from streamgate import BackpressureConfig, logger"],["time",false,20,"import time"],["asyncio",false,19,"import asyncio"]],"streamgate.contrib.mssql_sink":[["streamgate.contrib.sql_sink",false,10,"from streamgate.contrib.sql_sink import ("]],"streamgate.contrib.redis_admission":[["streamgate.contrib.redis_admission.admission",false,11,"from streamgate.contrib.redis_admission.admission import ("],["streamgate.contrib._deps",false,8,"from streamgate.contrib._deps import require_extra_import"],["streamgate.contrib.redis_admission.config",false,20,"from streamgate.contrib.redis_admission.config import RedisConfig"],["streamgate.contrib.redis_admission.existence",false,21,"from streamgate.contrib.redis_admission.existence import ("]],"streamgate.contrib.redis_admission.admission":[["streamgate.contrib.redis_admission.config",false,19,"from streamgate.contrib.redis_admission.config import RedisConfig"],["dataclasses",false,12,"from dataclasses import dataclass"],["enum",false,13,"from enum import Enum"],["redis",false,16,"from redis.exceptions import RedisError"],["streamgate",false,18,"from streamgate import logger"],["collections",false,11,"from collections.abc import Callable"],["asyncio",false,10,"import asyncio"],["streamgate.protocols",false,21,"from streamgate.protocols import ("],["typing",false,14,"from typing import Generic"],["streamgate.contrib.redis_admission.existence",false,20,"from streamgate.contrib.redis_admission.existence import RedisExistenceCache"]],"streamgate.contrib.redis_admission.config":[["pydantic",false,6,"from pydantic import BaseModel"]],"streamgate.contrib.redis_admission.existence":[["redis",false,14,"import redis.asyncio as aioredis"],["typing",false,12,"from typing import TypeVar"],["streamgate",false,16,"from streamgate import logger"],["json",false,10,"import json"],["streamgate.contrib.redis_admission.config",false,17,"from streamgate.contrib.redis_admission.config import RedisConfig"],["collections",false,11,"from collections.abc import Awaitable"],["asyncio",false,9,"import asyncio"]],"streamgate.contrib.sql_sink":[["streamgate.contrib.sql_sink.engines",false,15,"from streamgate.contrib.sql_sink.engines import ("],["streamgate.contrib.sql_sink.classifier",false,13,"from streamgate.contrib.sql_sink.classifier import SQLAlchemyErrorClassifier"],["streamgate.contrib.sql_sink.config",false,14,"from streamgate.contrib.sql_sink.config import DbConfig"],["streamgate.contrib.sql_sink.upsert",false,19,"from streamgate.contrib.sql_sink.upsert import ("],["streamgate.contrib.sql_sink.backfill",false,12,"from streamgate.contrib.sql_sink.backfill import SqlBackfill"],["streamgate.contrib._deps",false,9,"from streamgate.contrib._deps import require_extra_import"]],"streamgate.contrib.sql_sink._dialects":[],"streamgate.contrib.sql_sink._dialects.mssql":[["sqlmodel",false,19,"from sqlmodel import SQLModel"],["sqlalchemy",false,18,"from sqlalchemy.dialects import mssql"],["collections",false,14,"from collections.abc import Iterator"],["streamgate.protocols",false,21,"from streamgate.protocols import JsonObject"],["sqlalchemy",false,16,"from sqlalchemy import String, TextClause, text"],["sqlalchemy",false,17,"from sqlalchemy import inspect as sa_inspect"]],"streamgate.contrib.sql_sink._dialects.sqlite":[["sqlalchemy",false,4,"from sqlalchemy.dialects.sqlite import insert as sqlite_insert"],["sqlmodel",false,6,"from sqlmodel import SQLModel"],["sqlalchemy",false,5,"from sqlalchemy.ext.asyncio import AsyncSession"],["sqlalchemy",false,3,"from sqlalchemy import text"],["streamgate.protocols",false,8,"from streamgate.protocols import JsonObject"]],"streamgate.contrib.sql_sink.backfill":[["streamgate",false,13,"from streamgate import logger"],["sqlalchemy",false,11,"from sqlalchemy.ext.asyncio import AsyncEngine"],["datetime",false,8,"from datetime import datetime"],["sqlalchemy",false,10,"from sqlalchemy import text"],["asyncio",false,7,"import asyncio"],["streamgate.protocols",false,14,"from streamgate.protocols import JsonObject"]],"streamgate.contrib.sql_sink.classifier":[["sqlalchemy",false,23,"from sqlalchemy.exc import ("],["sqlalchemy",false,28,"from sqlalchemy.exc import ("],["streamgate",false,32,"from streamgate import ErrorKind"],["collections",false,20,"from collections.abc import Iterator"],["asyncio",false,18,"import asyncio"],["re",false,19,"import re"],["aiokafka",false,22,"from aiokafka.errors import KafkaError"]],"streamgate.contrib.sql_sink.config":[["pydantic",false,7,"from pydantic import BaseModel"]],"streamgate.contrib.sql_sink.engines":[["sqlalchemy",false,3,"from sqlalchemy import event"],["sqlalchemy",false,4,"from sqlalchemy.ext.asyncio import ("],["streamgate.contrib.sql_sink.config",false,12,"from streamgate.contrib.sql_sink.config import DbConfig"],["streamgate",false,11,"from streamgate import logger"]],"streamgate.contrib.sql_sink.upsert":[["streamgate.contrib.sql_sink._dialects.mssql",false,19,"from streamgate.contrib.sql_sink._dialects.mssql import mssql_cast_types"],["streamgate.protocols",false,21,"from streamgate.protocols import JsonObject, WriteResult"],["collections",false,9,"from collections.abc import Callable"],["asyncio",false,8,"import asyncio"],["streamgate.contrib.sql_sink.config",false,20,"from streamgate.contrib.sql_sink.config import DbConfig"],["sqlmodel",false,14,"from sqlmodel import SQLModel"],["sqlalchemy",false,11,"from sqlalchemy import inspect as sa_inspect"],["streamgate.contrib.sql_sink._dialects.mssql",false,17,"from streamgate.contrib.sql_sink._dialects import mssql as mssql_dialect"],["sqlalchemy",false,13,"from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker"],["streamgate",false,16,"from streamgate import logger"],["sqlalchemy",false,12,"from sqlalchemy import text"],["streamgate.contrib.sql_sink._dialects.sqlite",false,18,"from streamgate.contrib.sql_sink._dialects import sqlite as sqlite_dialect"]],"streamgate.contrib.sqlite_sink":[["streamgate.contrib.sql_sink",false,9,"from streamgate.contrib.sql_sink import ("]],"streamgate.ingest":[],"streamgate.ingest.admission":[],"streamgate.ingest.admission.in_memory":[["collections",false,12,"from collections.abc import Callable"],["typing",false,13,"from typing import Generic"],["streamgate.protocols",false,15,"from streamgate.protocols import Decision, JsonObject, RecordT"]],"streamgate.ingest.admission.no_admission":[["typing",false,3,"from typing import Generic"],["streamgate.protocols",false,5,"from streamgate.protocols import Decision, RecordT"]],"streamgate.ingest.gateway":[["streamgate.protocols",false,16,"from streamgate.protocols import ("],["streamgate.obs.logging",false,15,"from streamgate.obs.logging import logger"],["typing",false,9,"from typing import Generic"],["streamgate.config",false,11,"from streamgate.config import BackpressureConfig, KafkaConfig"],["streamgate.ingest.producer",false,14,"from streamgate.ingest.producer import KafkaProducerService"],["streamgate.resilience.health",false,26,"from streamgate.resilience.health import ("],["streamgate.transport.codec",false,31,"from streamgate.transport.codec import JsonEnvelopeCodec"],["streamgate.ingest.admission.in_memory",false,12,"from streamgate.ingest.admission.in_memory import InMemoryAdmission"],["streamgate.specs",false,30,"from streamgate.specs import IngestBinding, IngestRecordT"],["streamgate.ingest.admission.no_admission",false,13,"from streamgate.ingest.admission.no_admission import NoAdmission"],["datetime",false,8,"from datetime import datetime, timezone"],["streamgate.resilience.backpressure",false,25,"from streamgate.resilience.backpressure import ManualBackpressureSignal"]],"streamgate.ingest.producer":[["json",false,9,"import json"],["streamgate.obs.logging",false,17,"from streamgate.obs.logging import logger"],["aiokafka",false,13,"from aiokafka import AIOKafkaProducer"],["aiokafka",false,14,"from aiokafka.errors import KafkaConnectionError, KafkaError"],["asyncio",false,8,"import asyncio"],["time",false,10,"import time"],["datetime",false,11,"from datetime import datetime, timezone"],["streamgate.protocols",false,18,"from streamgate.protocols import JsonObject"],["streamgate.config",false,16,"from streamgate.config import KafkaConfig"]],"streamgate.obs":[],"streamgate.obs.logging":[["loguru",false,6,"from loguru._handler import Message"],["datetime",false,3,"from datetime import timezone"],["loguru",false,5,"from loguru import logger as _logger"],["sys",false,2,"import sys"],["json",false,1,"import json"]],"streamgate.obs.metrics":[["streamgate.obs.logging",false,10,"from streamgate.obs.logging import logger"],["typing",false,8,"from typing import Protocol"]],"streamgate.protocols":[["typing",false,11,"from typing import Protocol, TypeVar, runtime_checkable"],["collections",false,7,"from collections.abc import Awaitable, Callable"],["datetime",false,9,"from datetime import datetime"],["enum",false,10,"from enum import Enum"],["dataclasses",false,8,"from dataclasses import dataclass, field"]],"streamgate.resilience":[],"streamgate.resilience.backpressure":[["streamgate.protocols",false,10,"from streamgate.protocols import AllowAllSignal, BackpressureSnapshot"]],"streamgate.resilience.health":[["datetime",false,10,"from datetime import datetime, timezone"],["asyncio",false,7,"import asyncio"],["typing",false,11,"from typing import Protocol"],["pydantic",false,13,"from pydantic import BaseModel, Field"],["collections",false,9,"from collections.abc import Awaitable"],["streamgate.obs.logging",false,15,"from streamgate.obs.logging import logger"],["time",false,8,"import time"]],"streamgate.specs":[["collections",false,10,"from collections.abc import Callable, Hashable"],["streamgate.protocols",false,16,"from streamgate.protocols import ("],["pydantic",false,14,"from pydantic import BaseModel"],["typing",false,12,"from typing import Generic, Literal, TypeVar"],["dataclasses",false,11,"from dataclasses import dataclass, field"]],"streamgate.transport":[],"streamgate.transport.codec":[["streamgate.protocols",false,11,"from streamgate.protocols import Envelope, JsonObject"],["streamgate.obs.logging",false,10,"from streamgate.obs.logging import logger"],["json",false,8,"import json"]],"streamgate.transport.kafka":[["asyncio",false,3,"import asyncio"],["aiokafka",false,7,"from aiokafka.errors import CommitFailedError, KafkaConnectionError, KafkaError"],["aiokafka",false,5,"from aiokafka import AIOKafkaConsumer"],["aiokafka",false,8,"from aiokafka.structs import ConsumerRecord, OffsetAndMetadata, TopicPartition"],["streamgate.config",false,10,"from streamgate.config import ConsumerConfig, KafkaConfig"],["aiokafka",false,6,"from aiokafka.consumer.group_coordinator import GroupCoordinator"],["streamgate.obs.logging",false,11,"from streamgate.obs.logging import logger"]]},"version":2}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"streamgate.contrib.sql_sink.backfill": 1788835942.310305, "streamgate.consumer.classifier": 1788778605.4753034, "streamgate.obs.logging": 1788749262.353655, "streamgate.obs.metrics": 1788749264.9746833, "streamgate.contrib.redis_admission.config": 1788835837.2772365, "streamgate": 1788836110.1479475, "streamgate.contrib.redis_admission": 1788835898.0475388, "streamgate.contrib._deps": 1788835802.9044309, "streamgate.consumer": 1788742270.5761602, "streamgate.contrib.sql_sink.config": 1788835924.6027458, "streamgate.ingest.admission.no_admission": 1788759910.7492158, "streamgate.contrib.sql_sink.engines": 1788835942.286183, "streamgate.contrib.http_probe": 1788836174.9308388, "streamgate.consumer.runner": 1788778819.43872, "streamgate.consumer.dlq": 1788778795.1676013, "streamgate.contrib.sql_sink._dialects.sqlite": 1788835977.7374504, "streamgate.config": 1788778673.809221, "streamgate.contrib.sqlite_sink": 1788836174.9292364, "streamgate.contrib": 1788835802.905441, "streamgate.resilience.health": 1788767769.120218, "streamgate.transport": 1788742270.5761602, "streamgate.contrib.sql_sink._dialects": 1788835977.7374504, "streamgate.ingest": 1788742270.5761602, "streamgate.contrib.http_probe.probe_signal": 1788836090.470792, "streamgate.contrib.redis_admission.admission": 1788835898.0445402, "streamgate.contrib.redis_admission.existence": 1788835837.2841742, "streamgate.protocols": 1788780004.1189399, "streamgate.resilience": 1788742270.5761602, "streamgate.transport.kafka": 1788776206.140225, "streamgate.specs": 1788778652.1194282, "streamgate.ingest.admission.in_memory": 1788778617.9172873, "streamgate.ingest.producer": 1788776206.139223, "streamgate.consumer.loop": 1788778765.328234, "streamgate.ingest.gateway": 1788778851.3711455, "streamgate.contrib.mssql_sink": 1788836174.9297483, "streamgate.contrib.sql_sink": 1788836043.336221, "streamgate.contrib.sql_sink._dialects.mssql": 1788836025.5239024, "streamgate.obs": 1788742270.5761602, "streamgate.contrib.sql_sink.classifier": 1788835977.7154298, "streamgate.ingest.admission": 1788742270.5761602, "streamgate.contrib.sql_sink.upsert": 1788836025.5259016, "streamgate.resilience.backpressure": 1788778626.931601, "streamgate.transport.codec": 1788749619.0487723}
|
|
@@ -5,6 +5,53 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-09-08
|
|
9
|
+
|
|
10
|
+
**Contrib release.** The production-grade I/O strategies that 0.2.0 demoted to
|
|
11
|
+
copy-paste examples are back in the wheel — as `streamgate.contrib.*`,
|
|
12
|
+
importable, opt-in subpackages. The core stays pure: it gains no dependencies,
|
|
13
|
+
and it still never imports contrib (the import-linter forbidden contract is
|
|
14
|
+
replaced by a layered contract with the same guarantee). Purely additive; no
|
|
15
|
+
breaking changes.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`streamgate.contrib`** — official strategy layer (provisional / beta
|
|
20
|
+
grade; ships in the wheel, third-party deps via extras, friendly
|
|
21
|
+
"install streamgate[...]" errors when an extra is missing):
|
|
22
|
+
- `contrib.redis_admission` (extra `[redis]`): `RedisExistenceAdmission`,
|
|
23
|
+
`RedisExistenceCache`, `RedisConfig` — shared-storage uniqueness
|
|
24
|
+
admission with atomic Lua reservation, TTL idle-GC, empty-entity
|
|
25
|
+
sentinels, fail-closed semantics.
|
|
26
|
+
- `contrib.sql_sink` / `contrib.sqlite_sink` / `contrib.mssql_sink`
|
|
27
|
+
(extra `[sql]`): `Upsert`/`UpsertWriter` (`RecordWriter` impl),
|
|
28
|
+
engine factory, `SqlBackfill` (`BackfillSource` impl),
|
|
29
|
+
`SQLAlchemyErrorClassifier`, `DbConfig` — SQLite `ON CONFLICT` and
|
|
30
|
+
MSSQL multi-row `MERGE` + HOLDLOCK dialects.
|
|
31
|
+
- `contrib.http_probe` (extra `[http]`): `HttpProbeSignal` /
|
|
32
|
+
`HysteresisController` — backlog-age trip/recover hysteresis,
|
|
33
|
+
fail-closed probing, kafka-down fast reject.
|
|
34
|
+
- **Extras** `[redis]` / `[sql]` / `[http]` (new names; the pre-0.2.0 extras
|
|
35
|
+
`[sqlite]` / `[mssql]` / `[http-probe]` / `[all]` are not restored).
|
|
36
|
+
- **`examples/prod_pipeline/`** — production-topology demo wiring the three
|
|
37
|
+
contrib components together (Redis uniqueness admission + HTTP-probe
|
|
38
|
+
backpressure + MSSQL idempotent sink; runs on SQLite locally via
|
|
39
|
+
connection-string dialect selection).
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **import-linter**: the "pure core" forbidden-modules contract is replaced
|
|
44
|
+
by a layered contract — `streamgate.contrib` sits on top and may use the
|
|
45
|
+
core; no core layer may import contrib. Bare-install purity is still
|
|
46
|
+
CI-gated (bare install pulls exactly `aiokafka`/`loguru`/`pydantic`).
|
|
47
|
+
- **`examples/`**: the three strategy examples now import from
|
|
48
|
+
`streamgate.contrib`; demo scripts (produce/consume/health_server/models)
|
|
49
|
+
stay in the repo only. `pure_pipeline/` remains the zero-dependency main
|
|
50
|
+
example and is unchanged.
|
|
51
|
+
- **Dev group** now depends on `streamgate[redis,sql,http]` instead of
|
|
52
|
+
duplicating the third-party libs, keeping a single source of truth for
|
|
53
|
+
contrib dependency versions.
|
|
54
|
+
|
|
8
55
|
## [0.2.0] - 2026-09-07
|
|
9
56
|
|
|
10
57
|
**Pure-core release.** All database, Redis and HTTP-client code is removed from
|
|
@@ -14,10 +14,56 @@ underscore):
|
|
|
14
14
|
is missing, assembly fails at startup with an error that names the setting and
|
|
15
15
|
how to fix it — the process never runs with project-specific guessed defaults.
|
|
16
16
|
|
|
17
|
-
Database and Redis connection settings are **not part of the framework**:
|
|
17
|
+
Database and Redis connection settings are **not part of the core framework**:
|
|
18
18
|
storage carriers are injected via protocols and their configuration belongs to
|
|
19
|
-
your application
|
|
20
|
-
`
|
|
19
|
+
your application. The official strategy implementations under
|
|
20
|
+
`streamgate.contrib` ship typed config objects (below) that you construct and
|
|
21
|
+
pass to the strategy classes.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Contrib strategy configs
|
|
26
|
+
|
|
27
|
+
These are plain constructor arguments (not env-mapped). All belong to
|
|
28
|
+
`streamgate.contrib` packages — install the matching extra first
|
|
29
|
+
(`[redis]` / `[sql]` / `[http]`).
|
|
30
|
+
|
|
31
|
+
### `contrib.redis_admission.RedisConfig`
|
|
32
|
+
|
|
33
|
+
| Field | Default | Description |
|
|
34
|
+
|-------|---------|-------------|
|
|
35
|
+
| `url` | `redis://localhost:6379/0` | Redis connection URL. |
|
|
36
|
+
| `key_prefix` | `streamgate:` | Key prefix for existence hashes. |
|
|
37
|
+
| `existence_ttl_seconds` | `18000` | Existence hash TTL (idle-GC heartbeat: every write renews it). |
|
|
38
|
+
| `empty_existence_ttl_seconds` | `3600` | Empty-entity sentinel TTL (shorter than existence TTL). |
|
|
39
|
+
| `socket_timeout_ms` | `1000` | Read/query path timeout. |
|
|
40
|
+
| `recv_timeout_ms` | `500` | Ingest path (reserve/summary write) timeout. |
|
|
41
|
+
| `fail_closed_on_unavailable` | `true` | Reject instead of silently degrading when Redis is unavailable (`false` = legacy degraded escape hatch). |
|
|
42
|
+
|
|
43
|
+
Strategy-level knobs live on `RedisExistenceAdmissionConfig`
|
|
44
|
+
(`fail_closed_on_unavailable`, `cold_path_max_concurrency`, gate-full /
|
|
45
|
+
unavailable `retry_after` seconds, stable error codes, `log_context` mapping).
|
|
46
|
+
|
|
47
|
+
### `contrib.sql_sink.DbConfig`
|
|
48
|
+
|
|
49
|
+
| Field | Default | Description |
|
|
50
|
+
|-------|---------|-------------|
|
|
51
|
+
| `connection_string` | **required** | SQLAlchemy async URL (`sqlite+aiosqlite:///...` or `mssql+aioodbc://...`). Missing ⇒ assembly error with fix instructions. |
|
|
52
|
+
| `echo` | `false` | SQLAlchemy echo logging. |
|
|
53
|
+
| `write_timeout_seconds` | `20` | Driver statement timeout (MSSQL/pyodbc hook; must be < `write_wait_seconds`). |
|
|
54
|
+
| `write_wait_seconds` | `25` | `UpsertWriter.write` call-level `wait_for` ceiling. |
|
|
55
|
+
| `pool_timeout_seconds` | `3` | Pool checkout timeout (fail fast when exhausted). |
|
|
56
|
+
| `write_pool_size` | `10` | Fixed write-pool connections. |
|
|
57
|
+
| `write_pool_max_overflow` | `20` | Write-pool overflow connections. |
|
|
58
|
+
|
|
59
|
+
The dialect (SQLite vs MSSQL) is selected by the connection string;
|
|
60
|
+
`contrib.sqlite_sink` and `contrib.mssql_sink` are thin entry packages
|
|
61
|
+
documenting each backend.
|
|
62
|
+
|
|
63
|
+
### Backpressure probing (contrib.http_probe)
|
|
64
|
+
|
|
65
|
+
`HttpProbeSignal` consumes the same `BACKPRESSURE__*` environment mapping via
|
|
66
|
+
`BackpressureConfig` (see below) — no extra configuration surface.
|
|
21
67
|
|
|
22
68
|
---
|
|
23
69
|
|
|
@@ -60,8 +106,8 @@ your application (see the example-local configs in `examples/sqlite_sink/` and
|
|
|
60
106
|
## BackpressureConfig (`BACKPRESSURE__*`)
|
|
61
107
|
|
|
62
108
|
The gateway defaults to the built-in `ManualBackpressureSignal` (static
|
|
63
|
-
switch). For dynamic probing inject your own `BackpressureSignal` —
|
|
64
|
-
|
|
109
|
+
switch). For dynamic probing inject your own `BackpressureSignal` —
|
|
110
|
+
`contrib.http_probe.HttpProbeSignal` reads this config object.
|
|
65
111
|
|
|
66
112
|
| Env var | Default | Description |
|
|
67
113
|
|---------|---------|-------------|
|
|
@@ -26,8 +26,9 @@ uv build # wheel + sdist build
|
|
|
26
26
|
## Scope and conventions
|
|
27
27
|
|
|
28
28
|
- **Mechanisms vs. policies.** The package owns mechanisms (admission orchestration, delivery, backpressure, self-healing). Policy choices (entity/slot semantics, schemas, storage carriers) belong to users. PRs should keep this boundary.
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
29
|
+
- **Core purity / layering gate.** The core layers must never import `streamgate.contrib`, and `import streamgate` must work with zero I/O third-party dependencies — enforced by the import-linter layered contract in `pyproject.toml` (`[tool.importlinter]`) plus the CI bare-install smoke. Official I/O strategy implementations belong in `streamgate.contrib.*` with their third-party dependencies declared as extras (`[redis]` / `[sql]` / `[http]`); a missing extra must raise an import error that names the extra (use `streamgate.contrib._deps.require_extra_import`). If you add a protocol extension point, add or update a contrib implementation and a runnable example for it in the same PR.
|
|
30
|
+
- **Contrib is provisional.** Contrib packages are public API at beta grade: signatures may adjust within minor releases, but behavior changes and deprecations must go through the CHANGELOG. Merged strategy code must keep behavior-preserving moves only (lint + pyright gates apply to it exactly like the core).
|
|
31
|
+
- **Examples are the storefront.** Code in `examples/` is held to the same quality bar as the package itself: CI runs lint and typecheck over it, and example READMEs must match reality. Demos import strategies from `streamgate.contrib` — never copy strategy source into `examples/`. Rotting examples rot the project.
|
|
31
32
|
- **Architecture gate.** The layering contract in `pyproject.toml` (`[tool.importlinter]`) is enforced: higher layers may import lower layers, never the reverse. If your change legitimately needs a new edge, update the contract in the same PR and explain why.
|
|
32
33
|
- **Comments language.** Internal docstrings/comments are written in Chinese and kept that way. Docstrings on public API symbols (exported from `streamgate.__init__`) should be English-friendly.
|
|
33
34
|
- **No test suite yet.** The project currently ships without tests (top roadmap item). Until then, please include a minimal reproduction script (or a runnable snippet) with bug reports and behavioral PRs so reviewers can verify against real data.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: streamgate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A pure-core Kafka data pipeline framework: conditional admission, reliable delivery, pluggable sinks via typed hooks.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pwg-code/streamgate
|
|
6
6
|
Project-URL: Repository, https://github.com/pwg-code/streamgate
|
|
@@ -23,6 +23,15 @@ Requires-Python: >=3.10
|
|
|
23
23
|
Requires-Dist: aiokafka>=0.10
|
|
24
24
|
Requires-Dist: loguru>=0.7
|
|
25
25
|
Requires-Dist: pydantic>=2.5
|
|
26
|
+
Provides-Extra: http
|
|
27
|
+
Requires-Dist: httpx>=0.27; extra == 'http'
|
|
28
|
+
Provides-Extra: redis
|
|
29
|
+
Requires-Dist: redis>=5; extra == 'redis'
|
|
30
|
+
Provides-Extra: sql
|
|
31
|
+
Requires-Dist: aioodbc>=0.5; extra == 'sql'
|
|
32
|
+
Requires-Dist: aiosqlite>=0.19; extra == 'sql'
|
|
33
|
+
Requires-Dist: sqlalchemy>=2.0; extra == 'sql'
|
|
34
|
+
Requires-Dist: sqlmodel>=0.0.16; extra == 'sql'
|
|
26
35
|
Description-Content-Type: text/markdown
|
|
27
36
|
|
|
28
37
|
# streamgate
|
|
@@ -36,17 +45,17 @@ English | [中文](README.zh-CN.md)
|
|
|
36
45
|
|
|
37
46
|
**streamgate** is a pure-core Kafka data pipeline framework: conditional admission at the door, reliable delivery through Kafka, and a single typed hook — `RecordWriter` — wherever you want the data to land.
|
|
38
47
|
|
|
39
|
-
The wheel installs exactly three dependencies (`aiokafka`, `loguru`, `pydantic`) and contains **zero database, Redis or HTTP-client code**.
|
|
48
|
+
The wheel installs exactly three dependencies (`aiokafka`, `loguru`, `pydantic`) and the core contains **zero database, Redis or HTTP-client code**. Official I/O strategy implementations live in [`streamgate.contrib`](#contrib-official-strategy-implementations) — opt-in via extras — and runnable demos live in [`examples/`](examples/).
|
|
40
49
|
|
|
41
50
|
- 中文提示:本仓库对外文档以英文为主;内部代码注释保留中文。
|
|
42
51
|
|
|
43
52
|
## Why streamgate
|
|
44
53
|
|
|
45
54
|
- **Mechanisms in the framework, policies in your hands.** The framework owns admission *orchestration*, backpressure, health probing, reconnection, offset handling, batch buffering and graceful shutdown. You own entity/slot semantics, schemas, and — critically — **the storage carriers**: your database, your Redis, your HTTP probe are injected via typed protocols, never imported by the core.
|
|
46
|
-
- **A uniqueness contract without a mandated store.** The admission flow (existence check → atomic slot reservation → cold-entity backfill → consumer-side authoritative refresh) is framework machinery. Whether the existence store is Redis, an in-process dict, or something else is your policy: implement `AdmissionPolicy`, or use the built-in zero-I/O shortcuts (`"none"`, `"in-memory"`). A production-grade Redis
|
|
47
|
-
- **One sink hook, any storage.** `ConsumeSpec(sink=...)` takes a `RecordWriter` — write to SQL, Elasticsearch, another service, a file; the framework handles batching, retries, error classification and DLQ bisection around it.
|
|
48
|
-
- **Error classification as a hook.** Transient vs. poison vs. fatal is storage-specific knowledge. Inject an `ErrorClassifier`; the default only knows generic exceptions (and maps unknowns to poison — probe-protected bisect, never silent drops). A production-hardened SQLAlchemy classifier
|
|
49
|
-
- **Backpressure with hysteresis.** Ingest probes consumer health on a side channel and rejects based on backlog age with separate trip/recover thresholds. The core ships zero-I/O signals (`ManualBackpressureSignal`); the HTTP-probe
|
|
55
|
+
- **A uniqueness contract without a mandated store.** The admission flow (existence check → atomic slot reservation → cold-entity backfill → consumer-side authoritative refresh) is framework machinery. Whether the existence store is Redis, an in-process dict, or something else is your policy: implement `AdmissionPolicy`, or use the built-in zero-I/O shortcuts (`"none"`, `"in-memory"`). A production-grade Redis implementation ships as [`streamgate.contrib.redis_admission`](#contrib-official-strategy-implementations).
|
|
56
|
+
- **One sink hook, any storage.** `ConsumeSpec(sink=...)` takes a `RecordWriter` — write to SQL, Elasticsearch, another service, a file; the framework handles batching, retries, error classification and DLQ bisection around it. SQL upsert implementations ship as [`streamgate.contrib.sql_sink` / `sqlite_sink` / `mssql_sink`](#contrib-official-strategy-implementations).
|
|
57
|
+
- **Error classification as a hook.** Transient vs. poison vs. fatal is storage-specific knowledge. Inject an `ErrorClassifier`; the default only knows generic exceptions (and maps unknowns to poison — probe-protected bisect, never silent drops). A production-hardened SQLAlchemy classifier ships as `streamgate.contrib.sql_sink.SQLAlchemyErrorClassifier`.
|
|
58
|
+
- **Backpressure with hysteresis.** Ingest probes consumer health on a side channel and rejects based on backlog age with separate trip/recover thresholds. The core ships zero-I/O signals (`ManualBackpressureSignal`); the HTTP-probe implementation ships as [`streamgate.contrib.http_probe`](#contrib-official-strategy-implementations).
|
|
50
59
|
- **DLQ bisection instead of head-of-line blocking.** A poison message never wedges a partition: the failing batch is bisected with probe comparison, the bad record is quarantined to a dead-letter topic, the rest commits.
|
|
51
60
|
|
|
52
61
|
## Quick Start
|
|
@@ -130,26 +139,48 @@ your HTTP app (presentation is yours: auth/routing/OpenAPI)
|
|
|
130
139
|
└─ health snapshot (expose it with your own web framework)
|
|
131
140
|
```
|
|
132
141
|
|
|
133
|
-
The package contains **zero web-framework code** (no fastapi/uvicorn) and
|
|
142
|
+
The package contains **zero web-framework code** (no fastapi/uvicorn), and the core layers never import `streamgate.contrib` — enforced by the import-linter layered contract in CI.
|
|
134
143
|
|
|
135
|
-
##
|
|
144
|
+
## Contrib: official strategy implementations
|
|
136
145
|
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
`streamgate.contrib` is the official home for I/O strategy implementations on top of the core protocols. Everything below ships in the wheel; third-party dependencies are opt-in via extras, and importing without the extra raises an error that tells you which extra to install.
|
|
147
|
+
|
|
148
|
+
| Extra | Install | Subpackage(s) | Provides |
|
|
149
|
+
|-------|---------|---------------|----------|
|
|
150
|
+
| `[redis]` | `pip install "streamgate[redis]"` | `contrib.redis_admission` | `RedisExistenceAdmission` (`AdmissionPolicy` impl), `RedisExistenceCache`, `RedisConfig` |
|
|
151
|
+
| `[sql]` | `pip install "streamgate[sql]"` | `contrib.sql_sink` / `contrib.sqlite_sink` / `contrib.mssql_sink` | `Upsert`/`UpsertWriter` (`RecordWriter` impl, SQLite `ON CONFLICT` + MSSQL `MERGE`+HOLDLOCK), engine factory, `SqlBackfill` (`BackfillSource` impl), `SQLAlchemyErrorClassifier`, `DbConfig` |
|
|
152
|
+
| `[http]` | `pip install "streamgate[http]"` | `contrib.http_probe` | `HttpProbeSignal` / `HysteresisController` (`BackpressureSignal` impl with trip/recover hysteresis, fail-closed probing) |
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from streamgate import IngestBinding
|
|
156
|
+
from streamgate.contrib.redis_admission import RedisConfig, RedisExistenceAdmission, RedisExistenceCache
|
|
157
|
+
|
|
158
|
+
binding = IngestBinding(
|
|
159
|
+
message_type="order",
|
|
160
|
+
entity_key=lambda r: r.order_id,
|
|
161
|
+
slot_key=lambda r: "order",
|
|
162
|
+
summary=lambda r: {"amount": r.amount},
|
|
163
|
+
admission=RedisExistenceAdmission(
|
|
164
|
+
cache=RedisExistenceCache(RedisConfig(url="redis://localhost:6379/0")),
|
|
165
|
+
entity_key=lambda r: r.order_id,
|
|
166
|
+
slot_key=lambda r: "order",
|
|
167
|
+
summary=lambda r: {"amount": r.amount},
|
|
168
|
+
),
|
|
169
|
+
)
|
|
139
170
|
```
|
|
140
171
|
|
|
141
|
-
|
|
142
|
-
third-party library your implementation needs (e.g. `redis`, `sqlalchemy`,
|
|
143
|
-
`httpx`) to *your* project:
|
|
172
|
+
**Stability:** contrib packages are provisional (beta-grade) — usable in production (these implementations ran in production before being merged), but signatures may still adjust within minor releases. The core API contract is unaffected: the core never imports contrib.
|
|
144
173
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
## Installation
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pip install streamgate # aiokafka + loguru + pydantic. That's it.
|
|
178
|
+
pip install "streamgate[redis]" # + Redis admission
|
|
179
|
+
pip install "streamgate[sql]" # + SQL upsert sink (SQLite + MSSQL)
|
|
180
|
+
pip install "streamgate[http]" # + HTTP probe backpressure
|
|
181
|
+
```
|
|
151
182
|
|
|
152
|
-
|
|
183
|
+
For anything else (PostgreSQL, Elasticsearch, your own probe...), implement the protocol yourself (Tier 1) — the same hook every contrib package uses.
|
|
153
184
|
|
|
154
185
|
## Configuration
|
|
155
186
|
|
|
@@ -157,13 +188,13 @@ All components configure via typed objects that map 1:1 to environment variables
|
|
|
157
188
|
|
|
158
189
|
## Examples
|
|
159
190
|
|
|
160
|
-
See [`examples/`](examples/) for
|
|
191
|
+
See [`examples/`](examples/) for five runnable pipelines plus a `docker-compose.yml` (Kafka + Redis). Start with [`examples/pure_pipeline/`](examples/pure_pipeline/) — it runs with nothing but `pip install streamgate`. The strategy demos (`redis_admission/`, `sqlite_sink/`, `http_probe/`) import from `streamgate.contrib`, and [`examples/prod_pipeline/`](examples/prod_pipeline/) wires the full production topology (Redis admission + HTTP-probe backpressure + MSSQL sink) in one demo.
|
|
161
192
|
|
|
162
193
|
## Roadmap
|
|
163
194
|
|
|
164
195
|
- Test suite (first release ships without tests; APIs are exercised in production but the project considers this its top debt)
|
|
165
196
|
- Documentation site
|
|
166
|
-
- More admission/sink
|
|
197
|
+
- More admission/sink implementations in `streamgate.contrib`
|
|
167
198
|
|
|
168
199
|
## License
|
|
169
200
|
|
|
@@ -9,17 +9,17 @@ English | [中文](README.zh-CN.md)
|
|
|
9
9
|
|
|
10
10
|
**streamgate** is a pure-core Kafka data pipeline framework: conditional admission at the door, reliable delivery through Kafka, and a single typed hook — `RecordWriter` — wherever you want the data to land.
|
|
11
11
|
|
|
12
|
-
The wheel installs exactly three dependencies (`aiokafka`, `loguru`, `pydantic`) and contains **zero database, Redis or HTTP-client code**.
|
|
12
|
+
The wheel installs exactly three dependencies (`aiokafka`, `loguru`, `pydantic`) and the core contains **zero database, Redis or HTTP-client code**. Official I/O strategy implementations live in [`streamgate.contrib`](#contrib-official-strategy-implementations) — opt-in via extras — and runnable demos live in [`examples/`](examples/).
|
|
13
13
|
|
|
14
14
|
- 中文提示:本仓库对外文档以英文为主;内部代码注释保留中文。
|
|
15
15
|
|
|
16
16
|
## Why streamgate
|
|
17
17
|
|
|
18
18
|
- **Mechanisms in the framework, policies in your hands.** The framework owns admission *orchestration*, backpressure, health probing, reconnection, offset handling, batch buffering and graceful shutdown. You own entity/slot semantics, schemas, and — critically — **the storage carriers**: your database, your Redis, your HTTP probe are injected via typed protocols, never imported by the core.
|
|
19
|
-
- **A uniqueness contract without a mandated store.** The admission flow (existence check → atomic slot reservation → cold-entity backfill → consumer-side authoritative refresh) is framework machinery. Whether the existence store is Redis, an in-process dict, or something else is your policy: implement `AdmissionPolicy`, or use the built-in zero-I/O shortcuts (`"none"`, `"in-memory"`). A production-grade Redis
|
|
20
|
-
- **One sink hook, any storage.** `ConsumeSpec(sink=...)` takes a `RecordWriter` — write to SQL, Elasticsearch, another service, a file; the framework handles batching, retries, error classification and DLQ bisection around it.
|
|
21
|
-
- **Error classification as a hook.** Transient vs. poison vs. fatal is storage-specific knowledge. Inject an `ErrorClassifier`; the default only knows generic exceptions (and maps unknowns to poison — probe-protected bisect, never silent drops). A production-hardened SQLAlchemy classifier
|
|
22
|
-
- **Backpressure with hysteresis.** Ingest probes consumer health on a side channel and rejects based on backlog age with separate trip/recover thresholds. The core ships zero-I/O signals (`ManualBackpressureSignal`); the HTTP-probe
|
|
19
|
+
- **A uniqueness contract without a mandated store.** The admission flow (existence check → atomic slot reservation → cold-entity backfill → consumer-side authoritative refresh) is framework machinery. Whether the existence store is Redis, an in-process dict, or something else is your policy: implement `AdmissionPolicy`, or use the built-in zero-I/O shortcuts (`"none"`, `"in-memory"`). A production-grade Redis implementation ships as [`streamgate.contrib.redis_admission`](#contrib-official-strategy-implementations).
|
|
20
|
+
- **One sink hook, any storage.** `ConsumeSpec(sink=...)` takes a `RecordWriter` — write to SQL, Elasticsearch, another service, a file; the framework handles batching, retries, error classification and DLQ bisection around it. SQL upsert implementations ship as [`streamgate.contrib.sql_sink` / `sqlite_sink` / `mssql_sink`](#contrib-official-strategy-implementations).
|
|
21
|
+
- **Error classification as a hook.** Transient vs. poison vs. fatal is storage-specific knowledge. Inject an `ErrorClassifier`; the default only knows generic exceptions (and maps unknowns to poison — probe-protected bisect, never silent drops). A production-hardened SQLAlchemy classifier ships as `streamgate.contrib.sql_sink.SQLAlchemyErrorClassifier`.
|
|
22
|
+
- **Backpressure with hysteresis.** Ingest probes consumer health on a side channel and rejects based on backlog age with separate trip/recover thresholds. The core ships zero-I/O signals (`ManualBackpressureSignal`); the HTTP-probe implementation ships as [`streamgate.contrib.http_probe`](#contrib-official-strategy-implementations).
|
|
23
23
|
- **DLQ bisection instead of head-of-line blocking.** A poison message never wedges a partition: the failing batch is bisected with probe comparison, the bad record is quarantined to a dead-letter topic, the rest commits.
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
@@ -103,26 +103,48 @@ your HTTP app (presentation is yours: auth/routing/OpenAPI)
|
|
|
103
103
|
└─ health snapshot (expose it with your own web framework)
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
The package contains **zero web-framework code** (no fastapi/uvicorn) and
|
|
106
|
+
The package contains **zero web-framework code** (no fastapi/uvicorn), and the core layers never import `streamgate.contrib` — enforced by the import-linter layered contract in CI.
|
|
107
107
|
|
|
108
|
-
##
|
|
108
|
+
## Contrib: official strategy implementations
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
`streamgate.contrib` is the official home for I/O strategy implementations on top of the core protocols. Everything below ships in the wheel; third-party dependencies are opt-in via extras, and importing without the extra raises an error that tells you which extra to install.
|
|
111
|
+
|
|
112
|
+
| Extra | Install | Subpackage(s) | Provides |
|
|
113
|
+
|-------|---------|---------------|----------|
|
|
114
|
+
| `[redis]` | `pip install "streamgate[redis]"` | `contrib.redis_admission` | `RedisExistenceAdmission` (`AdmissionPolicy` impl), `RedisExistenceCache`, `RedisConfig` |
|
|
115
|
+
| `[sql]` | `pip install "streamgate[sql]"` | `contrib.sql_sink` / `contrib.sqlite_sink` / `contrib.mssql_sink` | `Upsert`/`UpsertWriter` (`RecordWriter` impl, SQLite `ON CONFLICT` + MSSQL `MERGE`+HOLDLOCK), engine factory, `SqlBackfill` (`BackfillSource` impl), `SQLAlchemyErrorClassifier`, `DbConfig` |
|
|
116
|
+
| `[http]` | `pip install "streamgate[http]"` | `contrib.http_probe` | `HttpProbeSignal` / `HysteresisController` (`BackpressureSignal` impl with trip/recover hysteresis, fail-closed probing) |
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from streamgate import IngestBinding
|
|
120
|
+
from streamgate.contrib.redis_admission import RedisConfig, RedisExistenceAdmission, RedisExistenceCache
|
|
121
|
+
|
|
122
|
+
binding = IngestBinding(
|
|
123
|
+
message_type="order",
|
|
124
|
+
entity_key=lambda r: r.order_id,
|
|
125
|
+
slot_key=lambda r: "order",
|
|
126
|
+
summary=lambda r: {"amount": r.amount},
|
|
127
|
+
admission=RedisExistenceAdmission(
|
|
128
|
+
cache=RedisExistenceCache(RedisConfig(url="redis://localhost:6379/0")),
|
|
129
|
+
entity_key=lambda r: r.order_id,
|
|
130
|
+
slot_key=lambda r: "order",
|
|
131
|
+
summary=lambda r: {"amount": r.amount},
|
|
132
|
+
),
|
|
133
|
+
)
|
|
112
134
|
```
|
|
113
135
|
|
|
114
|
-
|
|
115
|
-
third-party library your implementation needs (e.g. `redis`, `sqlalchemy`,
|
|
116
|
-
`httpx`) to *your* project:
|
|
136
|
+
**Stability:** contrib packages are provisional (beta-grade) — usable in production (these implementations ran in production before being merged), but signatures may still adjust within minor releases. The core API contract is unaffected: the core never imports contrib.
|
|
117
137
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
## Installation
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install streamgate # aiokafka + loguru + pydantic. That's it.
|
|
142
|
+
pip install "streamgate[redis]" # + Redis admission
|
|
143
|
+
pip install "streamgate[sql]" # + SQL upsert sink (SQLite + MSSQL)
|
|
144
|
+
pip install "streamgate[http]" # + HTTP probe backpressure
|
|
145
|
+
```
|
|
124
146
|
|
|
125
|
-
|
|
147
|
+
For anything else (PostgreSQL, Elasticsearch, your own probe...), implement the protocol yourself (Tier 1) — the same hook every contrib package uses.
|
|
126
148
|
|
|
127
149
|
## Configuration
|
|
128
150
|
|
|
@@ -130,13 +152,13 @@ All components configure via typed objects that map 1:1 to environment variables
|
|
|
130
152
|
|
|
131
153
|
## Examples
|
|
132
154
|
|
|
133
|
-
See [`examples/`](examples/) for
|
|
155
|
+
See [`examples/`](examples/) for five runnable pipelines plus a `docker-compose.yml` (Kafka + Redis). Start with [`examples/pure_pipeline/`](examples/pure_pipeline/) — it runs with nothing but `pip install streamgate`. The strategy demos (`redis_admission/`, `sqlite_sink/`, `http_probe/`) import from `streamgate.contrib`, and [`examples/prod_pipeline/`](examples/prod_pipeline/) wires the full production topology (Redis admission + HTTP-probe backpressure + MSSQL sink) in one demo.
|
|
134
156
|
|
|
135
157
|
## Roadmap
|
|
136
158
|
|
|
137
159
|
- Test suite (first release ships without tests; APIs are exercised in production but the project considers this its top debt)
|
|
138
160
|
- Documentation site
|
|
139
|
-
- More admission/sink
|
|
161
|
+
- More admission/sink implementations in `streamgate.contrib`
|
|
140
162
|
|
|
141
163
|
## License
|
|
142
164
|
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
查重、限流、背压 批量缓冲、重试、坏数据隔离
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
框架核心只装 3 个依赖(`aiokafka` / `loguru` / `pydantic`),核心层**没有一行数据库、Redis、HTTP 客户端代码**。官方 I/O 策略实现收录在 [`streamgate.contrib`](#不想自己写用-contrib)——按 extras 按需安装、装完即可 import;可运行的完整演示在 [`examples/`](examples/)。
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
## 为什么选它
|
|
22
22
|
|
|
23
23
|
- **机制归框架,策略归你。** 查重流程、重试、限流、断线重连、优雅停机这些"脏活累活"框架全包;你只管业务语义(主键是什么、摘要放什么)和存储选型。
|
|
24
|
-
- **查重不绑定存储。** 框架提供查重的"流程编排"(存在性判定 → 原子占位 → 冷回源 → 消费后刷新),至于用 Redis
|
|
24
|
+
- **查重不绑定存储。** 框架提供查重的"流程编排"(存在性判定 → 原子占位 → 冷回源 → 消费后刷新),至于用 Redis、进程内字典还是别的什么来存,由你注入。内置零依赖实现开箱即用;生产级 Redis 实现已收录为 `streamgate.contrib.redis_admission`(装 `[redis]` extra 即用)。
|
|
25
25
|
- **一个写入钩子,接任何存储。** MySQL、ES、另一个服务、一个文件……实现 `RecordWriter` 接口注入即可。批量缓冲、失败重试、坏数据二分隔离这些框架替你兜底。
|
|
26
26
|
- **错误分类也是钩子。** "连接超时该重试"还是"数据脏了该隔离",只有懂你存储的人知道。注入一个分类器即可;不注入也有安全的默认行为(不会丢数据)。
|
|
27
27
|
- **背压带磁滞防抖。** 消费端积压超标自动拒绝接收,回落后自动放行,来回抖动有磁滞带压着。
|
|
@@ -201,7 +201,7 @@ admission = MyAdmission(backfill=MyBackfill()) # 回源传给你的策略,不
|
|
|
201
201
|
| 配了,权威库说"确实没有" | 放行(确认是新数据) |
|
|
202
202
|
| 配了,但权威库也挂了 | 仍拒绝——核实不了绝不猜 |
|
|
203
203
|
|
|
204
|
-
两个内置准入策略都用不上回源:`NoAdmission` 压根不查重;`InMemoryAdmission` 的进程内字典本身就是权威(查不到 = 真的没有),代价是仅单进程有效、重启即清空——一旦多副本部署,就需要 Redis 查重 +
|
|
204
|
+
两个内置准入策略都用不上回源:`NoAdmission` 压根不查重;`InMemoryAdmission` 的进程内字典本身就是权威(查不到 = 真的没有),代价是仅单进程有效、重启即清空——一旦多副本部署,就需要 Redis 查重 + 回源的两级结构(`streamgate.contrib.redis_admission` + `streamgate.contrib.sql_sink.SqlBackfill` 已内置这套组合)。
|
|
205
205
|
|
|
206
206
|
### 例 4:自定义背压信号(跟着消费端积压走)
|
|
207
207
|
|
|
@@ -224,30 +224,56 @@ class MySignal: # 不用继承,duck typing
|
|
|
224
224
|
gateway = IngestGateway(..., signal=MySignal()) # 注入即可
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
-
不注入会怎样?默认 `ManualBackpressureSignal`——纯手动开关,永远放行;维护窗口可把 `signal.rejecting` 翻成 `True` 手动挡闸。接收端与消费端不在同一进程时,框架"看不见"
|
|
227
|
+
不注入会怎样?默认 `ManualBackpressureSignal`——纯手动开关,永远放行;维护窗口可把 `signal.rejecting` 翻成 `True` 手动挡闸。接收端与消费端不在同一进程时,框架"看不见"积压,用 `streamgate.contrib.http_probe`(积压探针 + 磁滞限流状态机,装 `[http]` extra)。
|
|
228
228
|
|
|
229
|
-
###
|
|
229
|
+
### 不想自己写?用 contrib
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
官方策略实现随 wheel 发布,按 extras 装依赖,**import 即用**(与核心同门禁:CI lint/类型检查全覆盖):
|
|
232
232
|
|
|
233
|
-
|
|
|
234
|
-
|
|
235
|
-
| [`
|
|
236
|
-
| [`
|
|
237
|
-
| [`
|
|
238
|
-
|
|
233
|
+
| Extra | 安装 | 子包 | 给你什么 |
|
|
234
|
+
|---|---|---|---|
|
|
235
|
+
| `[redis]` | `pip install "streamgate[redis]"` | `streamgate.contrib.redis_admission` | Redis 分布式查重(多实例安全,Lua 原子占位/冷回源/空实体哨兵/fail-closed) |
|
|
236
|
+
| `[sql]` | `pip install "streamgate[sql]"` | `streamgate.contrib.sqlite_sink` / `.mssql_sink` / `.sql_sink` | SQL 幂等落库全套(SQLite ON CONFLICT / MSSQL MERGE+HOLDLOCK、引擎工厂、`SqlBackfill` 回源)+ 数据库异常分类器 |
|
|
237
|
+
| `[http]` | `pip install "streamgate[http]"` | `streamgate.contrib.http_probe` | 积压探针 + 磁滞限流状态机(trip/recover 防抖、fail-closed) |
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from streamgate import IngestBinding
|
|
241
|
+
from streamgate.contrib.redis_admission import (
|
|
242
|
+
RedisConfig, RedisExistenceAdmission, RedisExistenceCache,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
binding = IngestBinding(
|
|
246
|
+
message_type="order",
|
|
247
|
+
entity_key=lambda r: r.order_id,
|
|
248
|
+
slot_key=lambda r: "order",
|
|
249
|
+
summary=lambda r: {"amount": r.amount},
|
|
250
|
+
admission=RedisExistenceAdmission(
|
|
251
|
+
cache=RedisExistenceCache(RedisConfig(url="redis://localhost:6379/0")),
|
|
252
|
+
entity_key=lambda r: r.order_id,
|
|
253
|
+
slot_key=lambda r: "order",
|
|
254
|
+
summary=lambda r: {"amount": r.amount},
|
|
255
|
+
),
|
|
256
|
+
)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
缺对应 extra 时 import 会报错并提示该装哪个 extras,不会出现裸的 `ModuleNotFoundError`。
|
|
260
|
+
|
|
261
|
+
**稳定性**:contrib 为 provisional(Beta 级)——可直接上生产(这些实现并入前已生产验证),但小版本内允许调整签名;核心 API 契约不受影响(核心层永不 import contrib)。
|
|
239
262
|
|
|
240
|
-
仓库根的 [`examples/docker-compose.yml`](examples/docker-compose.yml) 提供 Kafka + Redis
|
|
263
|
+
仓库根的 [`examples/docker-compose.yml`](examples/docker-compose.yml) 提供 Kafka + Redis 双服务;五个可运行演示见 [`examples/`](examples/)(策略演示直接 import contrib;[`examples/prod_pipeline/`](examples/prod_pipeline/) 把 Redis 准入 + HTTP 探活 + MSSQL 落库的生产拓扑一次接全)。
|
|
241
264
|
|
|
242
265
|
---
|
|
243
266
|
|
|
244
267
|
## 安装
|
|
245
268
|
|
|
246
269
|
```bash
|
|
247
|
-
pip install streamgate
|
|
270
|
+
pip install streamgate # 只装 aiokafka + loguru + pydantic
|
|
271
|
+
pip install "streamgate[redis]" # + Redis 分布式查重
|
|
272
|
+
pip install "streamgate[sql]" # + SQL 幂等落库(SQLite + MSSQL)
|
|
273
|
+
pip install "streamgate[http]" # + HTTP 探活背压
|
|
248
274
|
```
|
|
249
275
|
|
|
250
|
-
|
|
276
|
+
其他数据库(PostgreSQL、MySQL……):自己实现 `RecordWriter`——contrib 内置路径用的也是同一个钩子。
|
|
251
277
|
|
|
252
278
|
## 配置
|
|
253
279
|
|
|
@@ -271,13 +297,13 @@ pip install streamgate # 只装 aiokafka + loguru + pydantic,没有 extras
|
|
|
271
297
|
└─ 健康快照(用你自己的 Web 框架暴露)
|
|
272
298
|
```
|
|
273
299
|
|
|
274
|
-
|
|
300
|
+
核心层禁止 import `streamgate.contrib`(contrib 反向依赖核心是合法的)——由 import-linter 分层契约在 CI 强制拦截;contrib 的第三方依赖全部走 extras 可选声明,裸装 `pip install streamgate` 不携带。
|
|
275
301
|
|
|
276
302
|
## 路线图
|
|
277
303
|
|
|
278
304
|
- 测试套件(首发暂无测试;API 已在生产验证,项目视其为首要技术债)
|
|
279
305
|
- 文档站
|
|
280
|
-
- 更多
|
|
306
|
+
- 更多 `streamgate.contrib` 策略实现
|
|
281
307
|
|
|
282
308
|
## 许可证
|
|
283
309
|
|
|
@@ -3,13 +3,13 @@ services:
|
|
|
3
3
|
image: apache/kafka:3.9.0
|
|
4
4
|
container_name: streamgate-kafka
|
|
5
5
|
ports:
|
|
6
|
-
- "
|
|
6
|
+
- "9092:9092"
|
|
7
7
|
environment:
|
|
8
8
|
KAFKA_NODE_ID: 1
|
|
9
9
|
KAFKA_PROCESS_ROLES: broker,controller
|
|
10
10
|
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
|
|
11
|
-
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
|
|
12
|
-
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
|
11
|
+
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
|
|
12
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
|
13
13
|
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
|
|
14
14
|
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
|
15
15
|
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
|