Public API Compatibility
nats-sinks is intended to be imported by external Python projects as well as
run from the command line. That means the documented import paths are part of
the user experience, not an internal implementation detail.
Public API compatibility tests protect those import paths before every release. They make refactoring safer by allowing maintainers to move internal code while keeping the external contract stable.
What The Public API Contract Protects
The compatibility contract covers symbols that users are encouraged to import from their own projects, extension modules, tests, or operational tooling.
The most important top-level imports are:
from nats_sinks import FanoutSink, JetStreamSinkRunner, NatsEnvelope, Sink
from nats_sinks.file import FileSink
from nats_sinks.mysql import MySqlSink
from nats_sinks.oracle import OracleSink
from nats_sinks.s3 import S3Sink
from nats_sinks.sinks import SinkConnector, SinkRegistry
These imports are intentionally stable. A user should not need to know whether
JetStreamSinkRunner lives internally in nats_sinks.core.runner, whether the
file sink stores helper code in a mapping module, or whether the Oracle sink
has several internal SQL builders. The package-level imports are the supported
surface.
The tests also cover:
- framework error classes such as
TemporarySinkError,PermanentSinkError,ConfigurationError,AckError, andDeadLetterError, - payload encryption helpers such as
EncryptionConfig,PayloadEncryptor,SubjectPayloadEncryptor,PayloadKeyRegistry, anddecrypt_payload, - message authenticity helpers such as
MessageAuthenticityConfig,MessageAuthenticityRuleConfig,canonical_message_authenticity_bytes,canonical_message_authenticity_document,hmac_sha256_signature_b64, andevaluate_message_authenticity, - custody helpers such as
CustodyConfig,compute_custody_metadata,attach_custody_metadata, andcanonical_json_bytes, - durable consumer-management helpers such as
ConsumerManagementConfig,ensure_jetstream_consumer,detect_consumer_drift, andbuild_consumer_config, including the richer durable pull-consumer policy fields for filter subjects, BackOff, replicas, memory storage, and metadata, - push-consumer guardrail helpers such as
PushConsumerConfig,ensure_jetstream_push_consumer,detect_push_consumer_capabilities, andbuild_push_consumer_configfor explicit manual-ACK push mode, - JetStream advisory helpers such as
JetStreamAdvisoryConfig,JetStreamAdvisoryMonitor,parse_jetstream_advisory, andobserve_jetstream_advisory_message, - message metadata configuration classes,
- pre-sink policy configuration and evaluation helpers such as
PreSinkPolicyConfig,PreSinkPolicyRuleConfig,PolicyViolationError, andevaluate_pre_sink_policy, - routing and ACK-gate helpers such as
RoutingMatchPolicyConfig,RouteTargetConfig,select_route_targets, andwait_for_fanout_ack_gate, - the production fan-out orchestration sink
FanoutSink, - payload normalization helpers,
- metrics classes and helpers such as
MetricNames,InMemoryMetrics,JsonFileMetrics,load_metrics_snapshot, andmetric_rows_from_snapshot, - observability policy and connector helpers such as
ObservabilityPolicy,PrometheusTextfilePolicy,PrometheusHttpEndpointPolicy,OtlpMetricsPolicy,ElasticObservabilityPolicy,GrafanaAlloyObservabilityPolicy,SplunkHecObservabilityPolicy,StatsdObservabilityPolicy,SyslogObservabilityPolicy,NatsServerMonitoringPolicy,SubjectAwareObservabilityPolicy,SubjectAwareRule,SubjectAwareDecision,evaluate_subject_observability_policy,SubjectFamilyAggregationResult,aggregate_subject_family_counter,attach_labeled_metric_rows,collect_nats_monitoring_snapshot,render_otlp_metrics_json,export_otlp_metrics,render_elastic_otlp_metrics_json,export_elastic_observability_metrics, andrender_grafana_alloy_otlp_metrics_json,export_grafana_alloy_metrics,render_grafana_alloy_config,render_splunk_hec_event_json,export_splunk_hec_metrics,render_statsd_lines,export_statsd_metrics,render_datadog_lines,export_datadog_metrics,render_syslog_messages,export_syslog_metrics, andrender_nats_monitoring_prometheus, - sink extension points such as
Sink,HealthCheckableSink,SchemaAwareSink,FlushableSink,SinkRegistry,SinkConnector,load_entry_point_connectors, andnormalize_connector_name, - testing helpers such as
run_subject_observability_certificationfor reusable subject-aware observability release evidence, - production sink package exports for
nats_sinks.file,nats_sinks.mysql,nats_sinks.oracle, andnats_sinks.s3, - documented configuration helpers such as
load_configandredacted_config, - command entry points for
nats-sink,nats-sink-metrics, andnats-sink-observe.
Why This Matters
Without compatibility tests, an internal cleanup can accidentally break users. For example, moving a class from one module to another might still pass the unit tests for that class, but fail a real user application that imports the documented path.
The compatibility tests catch that kind of problem early.
flowchart LR
Docs[README and docs examples] --> Contract[Public API contract]
PyPI[PyPI package metadata] --> Contract
Contract --> Tests[tests/unit/test_public_api.py]
Tests --> CI[CI and release validation]
CI --> Users[External Python projects]
The tests do not freeze every internal module. They protect the parts that are documented as stable. Internal helpers can still evolve when needed, as long as the documented import paths and command names remain available.
Compatibility Test File
The contract lives in:
tests/unit/test_public_api.py
That test file contains a manifest of supported module exports and documented imports. It checks three things:
- Each documented symbol can be imported.
- Stable public packages expose those symbols through
__all__. - The console script names in
pyproject.tomlstill resolve to Typer apps.
Run the focused test with:
pytest tests/unit/test_public_api.py
The same test is also included in the normal test suite and the repository check script:
scripts/check.sh
Adding A New Public Symbol
When a feature should become part of the supported Python API, update the code, documentation, and compatibility test together.
For example, the first-party Oracle MySQL sink exposes this stable public import:
from nats_sinks.mysql import MySqlSink
When adding another first-party sink, use the same release-ready checklist:
src/nats_sinks/mysql/__init__.pyexportingMySqlSink.- Documentation showing the import.
- A
tests/unit/test_public_api.pycontract entry fornats_sinks.mysql. - Changelog text explaining the new public API.
- Sink-specific unit tests and integration tests behind the appropriate markers.
- A
SinkConnectordescriptor registered in the explicit sink registry.
This keeps new sinks additive. Existing imports such as
from nats_sinks.oracle import OracleSink and
from nats_sinks.file import FileSink should continue to work.
Third-party connector packages should normally expose a SinkConnector
descriptor through the nats_sinks.sinks entry-point group. The descriptor API
is part of the public extension surface, but plugin discovery remains disabled
by default and allow-list based. See Sink Framework for
the connector descriptor and certification requirements.
Sink certification helpers are also documented public imports for maintainers and connector authors:
from nats_sinks.testing import (
SinkCertificationCase,
certification_envelope,
certify_sink_write_success,
)
These helpers are release-tested so future sink packages can share the same baseline evidence without copying internal test code. See Sink Certification.
Subject-aware observability certification helpers are also part of the documented testing surface:
from nats_sinks.testing import run_subject_observability_certification
def test_subject_observability_contract() -> None:
report = run_subject_observability_certification()
assert report.raw_subject_leaks == ()
assert report.delivery_probe_before == report.delivery_probe_after
These helpers use synthetic subjects and prepared labeled_metrics rows to
prove connector behavior without exposing real subject names. See
Subject-Aware Observability Runbook.
Breaking Changes
A breaking change is any change that removes, renames, or changes the meaning of a documented public import, documented configuration field, console command, or delivery safety invariant.
Breaking changes should be avoided while the project is still building trust with users. If one becomes necessary, it should be handled deliberately:
- document the reason,
- provide a migration path,
- update the changelog,
- update tests,
- consider a compatibility alias or deprecation period,
- use semantic versioning to communicate the impact.
The commit-then-acknowledge invariant is not a compatibility option. It is a project safety rule. A future version must not turn ACK-before-durable-success into supported behavior.
What Is Not Public API
Some modules exist so the package can stay maintainable, but they are not intended as stable imports unless a documentation page says otherwise.
Examples include low-level SQL builders, row mappers, private CLI helper functions, and destination-specific implementation details. External users should prefer the documented package imports, sink classes, configuration models, and CLI commands.
If a user needs a lower-level helper to become stable, it should be promoted intentionally: document it, test it in the compatibility contract, and describe it in the changelog.