Flask-Nacos
English | 简体中文
Flask-Nacos integrates Flask with Nacos service registration, discovery, and configuration center. Version 1.1.1 uses a target-state lifecycle, lazy app/PID-bound Clients, and strict multi-application isolation.
New users start here
- Beginner quickstart
- Complete application-factory example
- Configuration reference
- API reference
- Service registration lifecycle
- Service discovery
- Health check
- Production deployment
- Troubleshooting
- Compatibility matrix
- Changelog
Features
- Flask extension and application-factory integration.
- Nacos Naming registration/deregistration with exact cached identity.
- Ephemeral-instance heartbeat delegated to Nacos SDK 2.x.
- Normalized discovery, cluster/metadata filtering, and first/random/weight selection.
- Raw configuration-center reads with configurable timeout.
- Target-state registration lifecycle with last-command-wins behavior.
- One lifecycle Worker and one Naming RPC at a time per app/PID Runtime.
- Lazy Client creation: initialization, status, and health remain SDK-free.
- Fork-safe PID Runtime rebuilding and strict multi-app context isolation.
- Sanitized colored console logs and optional rotating file logs.
- Python 3.8 syntax, type marker, and bilingual documentation.
Compatibility
- Python
>=3.8; CI verifies Python 3.8 through 3.14. - Flask
>=1.0; CI verifies valid combinations from Flask 1.0.x through 3.1.x. nacos-sdk-python>=2.0.0,<3.0.0.
Python 3.8 is tested with Flask 1.0.4, 1.1.4, 2.x, and 3.0.x. Flask 3.1 requires newer Python, so unsupported upstream combinations are not claimed.
Installation
python -m pip install flask-nacos
Installing flask-nacos from PyPI also installs the compatible
nacos-sdk-python dependency. When validating from TestPyPI, use PyPI as the
dependency source because TestPyPI is not a complete mirror:
python -m pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ flask-nacos==1.1.1
Quick start
from flask import Flask
from flask_nacos import FlaskNacos
app = Flask(__name__)
app.config.update(
NACOS_SERVER_ADDR="127.0.0.1:8848",
NACOS_SERVICE_NAME="orders-api",
NACOS_SERVICE_IP="127.0.0.1",
NACOS_SERVICE_PORT=5000,
NACOS_HEALTH_CHECK_ENABLED=True,
)
nacos = FlaskNacos(app)
The default automatic-registration path is:
init_app(app)
-> validate deterministic configuration
-> commit a Client-free PID Runtime
-> call register_instance(app)
-> return while a daemon Worker creates the Client and performs Naming I/O
NACOS_AUTO_REGISTER (default True) is the single automatic-registration
switch. Automatic registration runs when both NACOS_ENABLED and
NACOS_AUTO_REGISTER are enabled. Turning automatic registration off does not
block an explicit register_instance(app) command.
Configuration quick reference
All settings are read from Flask app.config. This table matches the current
code defaults; see the configuration reference
for validation rules and complete examples.
| Setting | Default | Description |
|---|---|---|
NACOS_ENABLED |
True |
Master switch; when disabled, no Client is created and related operations are no-ops. |
NACOS_SERVER_ADDR |
"127.0.0.1:8848" |
Nacos Server address in host:port form. |
NACOS_NAMESPACE_ID |
"" |
Namespace ID. |
NACOS_USERNAME |
None |
Username authentication value; configure it together with the password. |
NACOS_PASSWORD |
None |
Username authentication password; never commit it to source control. |
NACOS_ACCESS_KEY |
None |
AccessKey authentication value; configure it together with the SecretKey. |
NACOS_SECRET_KEY |
None |
SecretKey authentication value; never commit it to source control. |
NACOS_GROUP_NAME |
"DEFAULT_GROUP" |
Default group fallback. |
NACOS_AUTO_REGISTER |
True |
Submit the registration target during initialization and post-fork recovery. |
NACOS_DEREGISTER_ON_EXIT |
True |
Install the best-effort normal-process-exit deregistration callback. |
NACOS_SERVICE_NAME |
None |
Service name; required when registering. |
NACOS_SERVICE_IP |
None |
Registered instance IP; auto-detected when unset. |
NACOS_SERVICE_PORT |
None |
Registered instance port in 1-65535; required when registering. |
NACOS_SERVICE_GROUP |
"DEFAULT_GROUP" |
Group used for service registration. |
NACOS_SERVICE_CLUSTER |
"DEFAULT" |
Cluster used for service registration. |
NACOS_SERVICE_WEIGHT |
1.0 |
Finite load-balancing weight greater than 0. |
NACOS_SERVICE_METADATA |
{} |
Registered instance metadata. |
NACOS_SERVICE_EPHEMERAL |
True |
Register as an ephemeral instance. |
NACOS_SERVICE_HEARTBEAT_INTERVAL |
5.0 |
SDK heartbeat interval in seconds for ephemeral instances. |
NACOS_SERVICE_HEALTHY |
True |
Initial health flag sent during registration. |
NACOS_SERVICE_ENABLED |
True |
Whether the registered instance is enabled. |
NACOS_CONFIG_ENABLED |
True |
Enable configuration-center features. |
NACOS_CONFIG_DATA_ID |
None |
Default used when get_config() receives no data_id. |
NACOS_CONFIG_GROUP |
"DEFAULT_GROUP" |
Default configuration-center group. |
NACOS_RETRY_ENABLED |
True |
Enable finite retry and registration lifecycle recovery. |
NACOS_RETRY_TIMES |
3 |
Maximum finite-stage attempts; integer >=1. |
NACOS_RETRY_INTERVAL |
1.0 |
Finite delay between attempts in seconds; must be >=0. |
NACOS_REQUEST_TIMEOUT |
5.0 |
Configuration-center read timeout; does not override Naming Client timeout. |
NACOS_HEALTH_CHECK_ENABLED |
False |
Register the Flask health-check route. |
NACOS_HEALTH_CHECK_PATH |
"/health/nacos" |
Health-check route path. |
NACOS_DISCOVERY_STRATEGY |
"first" |
Healthy-instance strategy: first, random, or weight. |
NACOS_DISCOVERY_CLUSTER |
None |
Default discovery cluster filter. |
NACOS_DISCOVERY_METADATA |
{} |
Default discovery metadata filter. |
NACOS_INSTANCE_NORMALIZE |
True |
Return normalized instance dictionaries and skip malformed endpoints. |
NACOS_LOG_ENABLED |
False |
Master switch for Flask-Nacos safety logs. |
NACOS_LOG_LEVEL |
"INFO" |
Safety-log level. |
NACOS_LOG_CONSOLE_ENABLED |
True |
Emit console records when logging is enabled. |
NACOS_LOG_FILE_ENABLED |
True |
Write file records when logging is enabled. |
NACOS_LOG_PATH |
"./logs" |
Log file directory. |
NACOS_LOG_FILENAME |
"flask-nacos.log" |
Log filename inside NACOS_LOG_PATH. |
NACOS_LOG_FORMAT |
"%(asctime)s [%(levelname)s] %(name)s: %(message)s" |
Format used by Flask-Nacos handlers. |
NACOS_LOG_PROPAGATE |
True |
Propagate records to parent loggers. |
NACOS_LOG_MAX_BYTES |
10485760 |
Rotating-file size limit; None selects a plain file handler. |
NACOS_LOG_BACKUP_COUNT |
5 |
Number of rotated log backups to retain. |
Application factory
from flask import Flask
from flask_nacos import FlaskNacos
nacos = FlaskNacos()
def create_app():
app = Flask(__name__)
app.config.from_object("config.Config")
nacos.init_app(app)
return app
Configuration must be loaded before init_app(app). Calls without an explicit
app require the corresponding Flask app/request context. Flask-Nacos never
falls back to the first or most recently initialized application.
Registration lifecycle
The public lifecycle API is deliberately small:
nacos.register_instance(app) # returns None immediately
removed = nacos.deregister_instance(app)
status = nacos.get_status(app)
client = nacos.get_client(app)
register_instance(app=None) -> None changes the final target to registered
and publishes at most one daemon convergence Worker. Client construction,
network retry, Naming RPC, and SDK heartbeat startup never delay the caller.
deregister_instance(app=None) -> bool changes the final target to
unregistered. It returns True for an idempotent/accepted/successful cleanup or
when a newer register command makes the RPC unnecessary, and False when
cleanup is still required but fails.
The Worker continually re-reads the latest target. A sequence such as register → deregister → register therefore ends at the final register target; an old delayed command cannot permanently override it. Register, normal deregister, compensating deregister, and exit deregister share one Naming single-flight lock.
Successful registration caches the exact service/group/cluster/IP/port used by the SDK. Every deregistration uses that identity; Flask-Nacos never guesses a replacement IP. Registration success transfers ephemeral heartbeat ownership to the SDK, and the Flask-Nacos Worker exits.
See the full lifecycle diagrams and old/new scheduling table.
Deterministic validation and retry
When automatic registration is enabled, init_app(app) validates
NACOS_SERVICE_NAME, port, weight, metadata, ephemeral/heartbeat settings,
authentication, and retry settings before creating extension state.
When automatic registration is disabled, initialization skips registration-only
validation. The first explicit register_instance(app) performs that local,
deterministic validation and caches its result without creating a Client or
performing network I/O.
Deterministic local errors for active automatic registration raise before
app.extensions["nacos"] is committed. An invalid explicit registration also
raises synchronously without changing the lifecycle target. Thread
creation/start, Client construction, timeout, connection, and SDK failures stay
inside the lifecycle Worker and are recorded by safe type/code in last_error.
Registration lifecycle failures are classified immediately and conservatively:
- deterministic configuration, authentication, permission, parameter, or invariant failures stop the Worker immediately;
- unknown failures use only the existing finite retry budget;
- explicitly identified transient transport failures use the same finite budget first, then continue low-frequency, interruptible lifecycle recovery with bounded backoff and jitter until the target changes, shutdown begins, a later failure is no longer transient, or registration succeeds.
Recovery covers both lazy Client construction and Naming registration when the
failure has verified transient evidence. In SDK 2.0.11, the exact bare
nacos.exception.NacosRequestException raised while an authenticated Client is
constructed is covered only for the register direction. Structured 401/403
authentication failures remain deterministic and stop immediately.
NACOS_RETRY_ENABLED=False disables both finite retry and lifecycle recovery.
Recovery applies only to proven transient failures and does not poll remote
state. Once registered == target_registered, the Worker exits and the Nacos
SDK continues to own heartbeat and connection maintenance.
Heartbeat observability is Client-local: failures are throttled separately for
each service/group/cluster/IP/port identity, and the first successful beat after
a failure emits one recovery record. If a complete safe identity cannot be
extracted, logging falls back to stateless <unknown> records rather than a
possibly colliding key. These records never change Lifecycle state or trigger
registration.
NACOS_RETRY_TIMES must be an integer >=1; NACOS_RETRY_INTERVAL must be a
finite number >=0; NACOS_REQUEST_TIMEOUT must be finite and >0 when the
configuration center is enabled. Numeric strings are accepted. Booleans, NaN,
Infinity, fractional attempt counts, and out-of-range values are rejected.
NACOS_REQUEST_TIMEOUT applies only to configuration-center reads. Naming RPCs
use the SDK Client's own default_timeout; shutdown snapshots that actual value
and waits for its remaining budget plus a small allowance, capped at five
seconds. It falls back to three seconds when no valid SDK timeout is available.
Local status
get_status(app=None) returns exactly 16 local fields:
{
"enabled": True,
"pid": 12345,
"client_created": True,
"service_name": "orders-api",
"group_name": "DEFAULT_GROUP",
"cluster_name": "DEFAULT",
"service_ip": "203.0.113.20",
"service_port": 5000,
"target_registered": True,
"registered": True,
"operation_running": False,
"last_error": None,
"heartbeat_state": "healthy",
"last_heartbeat_success_at": 1770000000.25,
"last_heartbeat_failure_at": None,
"heartbeat_error_type": None,
}
registered is the most recent local fact confirmed by a successful Naming
RPC, not a live Nacos query. heartbeat_state is the most recent local SDK
heartbeat observation for the current ephemeral registration cycle:
unknown before the first observed beat, healthy after success, failing
after failure, and not_applicable while disabled, unregistered, or persistent.
Heartbeat timestamps are Unix epoch seconds; the error field contains only a
safe exception type. These fields do not prove that the remote instance still
exists and never drive Lifecycle recovery.
Status does not create a Client, contact Nacos, detect an IP, start a thread, or resume post-fork registration.
get_client(app) explicitly creates or returns the app/PID Client and raises a
safe NacosConfigError or NacosClientError if preparation fails. The .client property is cache-only
and requires a current Flask context.
Health route
Enable it with:
NACOS_HEALTH_CHECK_ENABLED = True
NACOS_HEALTH_CHECK_PATH = "/health/nacos"
It returns exactly status, enabled, client_created, target_registered,
registered, operation_running, and last_error. status=ok means local
lifecycle state is converged or is progressing without a recorded error. It is
not a remote Nacos or heartbeat probe and never creates a Client.
Discovery
with app.app_context():
instances = nacos.list_instances("inventory-api")
instance = nacos.get_one_healthy_instance(
"inventory-api",
strategy="weight",
cluster="CANARY",
metadata={"version": "v2"},
)
Malformed instances are logged and skipped. IP must be a non-empty string and port must be an integer from 1 to 65535. String booleans are normalized, and invalid/non-finite weight falls back safely.
Configuration center
with app.app_context():
content = nacos.get_config("application.yaml", group="DEFAULT_GROUP")
get_config() returns raw text. Flask-Nacos does not parse YAML/JSON and does
not write remote content into app.config.
NACOS_USERNAME/NACOS_PASSWORD must be configured as a complete pair,
NACOS_ACCESS_KEY/NACOS_SECRET_KEY must be a complete pair, and the two
authentication methods are mutually exclusive. Never commit real credentials.
Logging
NACOS_LOG_ENABLED=False by default. SDK-native logs are isolated, so
Flask-Nacos does not create ~/logs/nacos or an SDK log file. The complete
logging settings and defaults are listed in the quick-reference table above.
Console records use blue DEBUG, green INFO, yellow WARNING, red ERROR, and bold red CRITICAL. File records contain no ANSI colors. When logging is disabled, configured paths are not created.
Choose one logging topology to avoid duplicate output: either disable the
extension console/file handlers and keep NACOS_LOG_PROPAGATE=True so the host
owns all output, or enable the extension console handler with file output off
and set NACOS_LOG_PROPAGATE=False for container stdout. Do not let multiple
processes share one rotating file handler.
Fork, Gunicorn, and shutdown
Runtime resources are bound to one Flask app and PID. After fork, the entire
parent Runtime is discarded. Explicit registration or a real Client,
Discovery, or Config SDK operation may resume automatic registration without
waiting for convergence; ordinary business requests, status, health, and
.client reads do not.
For Gunicorn --preload, use NACOS_AUTO_REGISTER=False and call
nacos.register_instance(app) in the post-fork/worker-init hook. This avoids
starting SDK runtime in the preload master.
Workers advertising the same service/group/cluster with the same IP and port
represent one Nacos instance. Set NACOS_DEREGISTER_ON_EXIT=False for a shared
endpoint so one exiting worker cannot delete it while others remain alive.
NACOS_DEREGISTER_ON_EXIT=True installs the process-exit deregistration
callback; False installs no remote-cleanup callback. The setting never blocks
an explicit deregister_instance(). On a normal interpreter exit, cleanup
never changes the user's target, never starts normal retry, and waits only a
bounded time for an already-active Naming RPC before one best-effort cleanup.
Forced termination such as SIGKILL cannot guarantee callback execution.
Security note
The synchronous Nacos SDK 2.x line has limitations around HTTPS server certificate verification. Use a trusted network or a certificate-validating proxy/sidecar when transport policy requires verified TLS.
See the security policy for private vulnerability reporting.
Examples and development
Local quality checks:
pytest
ruff check .
mypy flask_nacos
python scripts/check_api_snapshot.py
python scripts/check_docs.py
python scripts/check_examples.py
License
Metadata
Release files for flask-nacos 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flask_nacos-1.1.1.tar.gz | 205.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flask_nacos-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 258.5 kB
Release files / flask_nacos-1.1.1.tar.gz
| Download URL | flask_nacos-1.1.1.tar.gz |
|---|---|
| Size | 205.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee9269a6e7efe66e05d3f108894487974e695e5f331c4c78f873f537909c1f92
|
|
BLAKE2b-256 checksum How to use checksums |
508e86a13a2d9d9939730469e65a672116db06890a1ecb55147dee1feb0fce6e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency logRelease files / flask_nacos-1.1.1-py3-none-any.whl
| Download URL | flask_nacos-1.1.1-py3-none-any.whl |
|---|---|
| Size | 53.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6ccd9058e3c7ee71ba8cfbb9415c7b58a25115e8adefe4c9f813abd4795b860b
|
|
BLAKE2b-256 checksum How to use checksums |
4b84d27bfd312c21b234e1e837d7dcc3a3187b5e6a83f00229308516a1fa2c3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency log