Flask-XXLJob
A Flask extension that implements the official XXL-JOB 2.4.1 executor protocol, so a Flask project can act as an XXL-JOB executor directly without a Java relay service.
Flask-XXLJob is a protocol adapter. It handles protocol integration and does not execute business tasks.
Features
- Implements the official XXL-JOB 2.4.1 executor endpoints:
/beat,/idleBeat,/run,/kill,/log. - Exact, case-sensitive JobHandler dispatch through named
on_run("name")callbacks. - Executor registration / deregistration with automatic renewal.
- Task-result callback client (
callback,callback_success,callback_failure). - Access token support using the official
XXL-JOB-ACCESS-TOKENheader. - Multiple admin addresses with failover.
- Flask Application Factory support and per-application runtime isolation.
- Strict protocol string validation and startup detection of conflicting executor routes.
- Optional, isolated rotating-file and level-colored console plugin diagnostics.
- Minimal dependencies (
Flask,requests), typed (py.typed).
What it does not do
Flask-XXLJob never executes business tasks. It does not create thread pools, process pools, task queues, Celery tasks, or message queues; it does not manage task state, logs, timeouts, or cancellation; and it does not send the final task callback automatically. Your Flask project decides how to submit, execute, cancel, log, and finally call back.
Installation
pip install flask-xxljob
Five-minute quick start
New to Python, Flask, or XXL-JOB? Follow the step-by-step beginner's guide. It starts locally without requiring XXL-JOB Admin.
from flask import Flask
from flask_xxljob import FlaskXXLJob, XXLJobResponse
app = Flask(__name__)
app.config.update(
XXL_JOB_AUTO_REGISTER=False, # Start locally without Admin.
XXL_JOB_ROUTE_PREFIX="/xxl-job",
)
xxl_job = FlaskXXLJob(app)
@xxl_job.on_run("demoJobHandler")
def handle_run(request):
print("job:", request.job_id, "params:", request.parse_params())
return XXLJobResponse.success(content="job received")
app.run(port=5001)
Set the Admin job's JobHandler to exactly demoJobHandler. Flask-XXLJob
validates and dispatches it automatically; an unknown name returns an XXL-JOB
code=500 response without calling another handler.
Save it as app.py, then run:
python app.py
The tested copy is available at
examples/beginner/app.py. Connect Admin only after
the local /xxl-job/run test succeeds.
Task-result callback
When your task finishes, call back into XXL-JOB yourself:
with app.app_context():
xxl_job.callback_success(
log_id=log_id,
log_date_time=log_date_time,
message="Task completed successfully.",
)
Configuration
| Key | Default | Description |
|---|---|---|
XXL_JOB_ENABLED |
True |
Master switch for executor routes and all Registry, Remove and Callback Admin traffic. |
XXL_JOB_ADMIN_ADDRESSES |
[] |
List of XXL-JOB admin base URLs. |
XXL_JOB_ACCESS_TOKEN |
"" |
Access token; empty means no-token mode. |
XXL_JOB_EXECUTOR_APP_NAME |
"flask-xxljob-executor" |
Executor application name. |
XXL_JOB_EXECUTOR_ADDRESS |
"" |
Executor service base URL (scheme/host/port). XXL_JOB_ROUTE_PREFIX is appended automatically. |
XXL_JOB_ROUTE_PREFIX |
"" |
URL prefix for the executor endpoints; also appended to XXL_JOB_EXECUTOR_ADDRESS. |
XXL_JOB_AUTO_REGISTER |
True |
With ENABLED=True, prepare an activation-gated Registry Worker during initialization and activate it after Flask commit. |
XXL_JOB_DEREGISTER_ON_EXIT |
False |
Best-effort background deregistration during Runtime shutdown. |
XXL_JOB_REGISTRY_INTERVAL |
30 |
Registration renewal interval (seconds). |
XXL_JOB_HTTP_CONNECT_TIMEOUT |
3 |
HTTP connect timeout (seconds). |
XXL_JOB_HTTP_READ_TIMEOUT |
5 |
HTTP read timeout (seconds). |
XXL_JOB_CALLBACK_MESSAGE_MAX_LENGTH |
10000 |
Max handleMsg length (characters). |
XXL_JOB_MAX_REQUEST_SIZE |
1048576 |
Max request body size (bytes). |
XXL_JOB_MAX_PARAM_LENGTH |
65536 |
Max executorParams length (characters). |
XXL_JOB_CALLBACK_BATCH_MAX_SIZE |
100 |
Max items per callback_many batch. |
XXL_JOB_ADMIN_RETRY_COUNT |
0 |
Same-address synchronous retries (capped). |
XXL_JOB_ADMIN_RETRY_BACKOFF |
0.0 |
Seconds between retries (capped). |
XXL_JOB_ADMIN_FAILOVER_ON_HTTP_ERROR |
True |
Fail over on a non-200 status. |
XXL_JOB_ADMIN_FAILOVER_ON_INVALID_JSON |
False |
Fail over on invalid JSON or an invalid response object. |
XXL_JOB_ADMIN_FAILOVER_ON_BUSINESS_ERROR |
False |
Fail over on a business failure. |
XXL_JOB_LOG_ENABLED |
False |
Enable plugin-managed diagnostic handlers. |
XXL_JOB_LOG_FILE_ENABLED |
True |
Write managed logs to a rotating file. |
XXL_JOB_LOG_CONSOLE_ENABLED |
True |
Write managed normal and error logs to the console. |
XXL_JOB_LOG_LEVEL |
"INFO" |
Shared managed-handler level. |
XXL_JOB_LOG_FORMAT |
standard format | Shared Python Logging format. |
XXL_JOB_LOG_DATE_FORMAT |
"%Y-%m-%d %H:%M:%S" |
Timestamp format. |
XXL_JOB_LOG_PATH |
"./logs" |
File-log directory, relative to the process working directory. |
XXL_JOB_LOG_FILENAME |
"flask-xxljob.log" |
File-log name. |
XXL_JOB_LOG_ENCODING |
"utf-8" |
File encoding. |
XXL_JOB_LOG_MAX_BYTES |
10485760 |
Rotation size in bytes. |
XXL_JOB_LOG_BACKUP_COUNT |
5 |
Number of rotated backups. |
XXL_JOB_LOG_PROPAGATE |
False |
Propagate records while managed handlers exist. |
Managed logging is off by default and creates no directory, file or console
handler. Enabling only XXL_JOB_LOG_ENABLED writes to
./logs/flask-xxljob.log and the console. The built-in rotating file target is
appropriate for a single process; multiple processes must not share that file.
For containers and multi-worker servers, prefer console or host-managed logs.
See the logging guide.
XXL_JOB_ENABLED=False is a complete feature switch: the five executor routes
are not registered and Registry, Remove and Callback calls perform no Admin
HTTP. The local Runtime, status and CLI remain available, and synchronous APIs
return the existing disabled CallResult. Use XXL_JOB_ENABLED=True with
XXL_JOB_AUTO_REGISTER=False when a process needs Callback but not renewal.
Callback APIs still resolve the target application Runtime first; once that
Runtime is disabled they return before validating, normalizing or iterating the
business payload. Application-resolution and initialization errors remain
visible, and enabled Callback validation is unchanged.
Disabled initialization still validates local field types and logging, but does
not interpret unused Admin/executor URL strings or Route Prefix path syntax.
When enabled, Admin and non-empty executor URLs reject whitespace and control
characters, userinfo, query strings, fragments and invalid hosts/ports; only
HTTP/HTTPS is accepted. Route Prefix accepts root, an optional leading slash
and one trailing slash, but rejects dynamic converters, dot segments, repeated
slashes, percent-encoded paths and URL/control syntax. It is a static Flask
path, so % is rejected directly rather than decoded or repaired. Admin POST
requests never follow redirects, and malformed JSON objects are reported as
invalid_response.
init_app() starts Registry only when XXL_JOB_ENABLED and
XXL_JOB_AUTO_REGISTER are both True. Set XXL_JOB_AUTO_REGISTER=False for
Gunicorn preload or an Application Factory shared with Celery, then call
start_registry(app) only in a process that should renew the executor. Each
Gunicorn worker still owns an independent process-local Registry lifecycle;
this release does not add leader election or cross-process locking. See
deployment.
With automatic Registry, register module-level handlers before init_app() to
minimize the short window in which Admin can see the executor before all
business handlers are ready. This is a recommendation, not a runtime readiness
check. Alternatively set XXL_JOB_AUTO_REGISTER=False, initialize the app,
register application handlers, and then call start_registry(app).
stop_registry() now stops local renewal immediately and preserves the latest
registered snapshot. Use stop_registry(remove=True) for one best-effort
background Remove for that lifecycle, or use stop_registry() followed by
remove_executor() when a synchronous CallResult is required. Exit
deregistration is off by default so one worker does not remove a shared
executor identity.
Terminal cleanup is idempotent by cleanup responsibility, not permanently by
generation. A successful terminal Remove is reused by later shutdown or
synchronous terminal removal. If an accepted register subsequently recreates
the remote identity in the same generation, it creates a new cleanup
responsibility; strict RPC sequencing plus one Active and one optional Pending
fallback ensure that the newer remote state is removed exactly when needed.
Calling remove_executor() while renewal is still running remains an ordinary
one-shot RPC and does not stop or consume the lifecycle.
Every explicit register_executor() that enters the current generation before
lifecycle cleanup is linearized is counted before it waits for the Registry
network lock. Shutdown closes that coordination window without blocking and
defers its Remove until the already-counted calls finish. A later explicit
register is still a normal one-shot operation and is not permanently rejected.
Its real RPC completion is accepted by strict sequence and ProcessState
identity; generation and coordination ownership only decide whether that
accepted success changes lifecycle cleanup responsibility.
Generation zero is a manual cleanup scope only: an accepted explicit Register
does not create a Worker or start renewal, but exit cleanup can pair it with one
Remove when XXL_JOB_DEREGISTER_ON_EXIT=True. Its result cache never satisfies
the later generation-one Worker lifecycle.
Initialization first performs a side-effect-free preflight. Removed keys,
field values, automatic Registry completeness and route/Blueprint/CLI conflicts
are rejected before log handlers, files or Flask state are created. Private
prepare then starts an activation-gated daemon Thread, which makes no Admin
call, and creates only a detachable finalizer handle; neither resource publishes
app.extensions, the application registry, CLI, routes or hooks. Flask commit
publishes the reversible state before registering the Blueprint/hooks, and only
the Prepared creator may then commit the generation/Worker and wake the Thread.
A preparation failure detaches the finalizer, cancels a started Prepared Thread,
removes only state still owned by that initialization, closes its managed log
handlers and preserves the original error. A committed Worker still runs its
lifecycle finally, even if stopped before its first Registry RPC. This is
targeted private-resource atomicity, not a general rollback of arbitrary Flask
route or hook mutations. Once the exact Blueprint object created by this
init_app() call is visible in the current app, Runtime, CLI, application
record and finalizer ownership remain published even if activation raises. The
original error still propagates, but live protocol routes are never left
without their Runtime. A failed activation-Event retry likewise preserves an
already committed Worker for the existing stop/shutdown/finalizer lifecycle.
CLI
flask --app "project:create_app" xxljob register
flask --app "project:create_app" xxljob remove
flask --app "project:create_app" xxljob status
The CLI remove command is terminal for the current renewal lifecycle: it
stops the local Registry Worker before attempting synchronous deregistration.
The Worker remains stopped even when Admin removal fails. The low-level
remove_executor() API remains a one-shot RPC and does not stop renewal.
Compatibility
Target support is Flask >= 1.0 and Python >= 3.8. The compatibility matrix
(Python 3.8-3.14 x Flask 1/2/3) is configured in
.github/workflows/ci.yml. This release was verified locally on Python 3.12.13
with Flask 3.1.3; the remaining combinations are configured in CI but were not
executed locally. Run the test suite in your own environment before claiming a
specific combination. Final local 0.4.0 test and coverage results are recorded
in the changelog.
When one FlaskXXLJob instance initializes multiple Flask applications, pass
app= to callback, registration, status and lifecycle helpers outside an
application context. Omitting it is supported only when exactly one application
has been initialized; pre-initialization on_* decorators remain default
handlers for every subsequently initialized application.
Documentation
See the docs directory, including getting-started.md, configuration.md, the logging guide, the API reference and integration testing. For an end-to-end Flask template, use the complete integration example. Upgrading from an earlier version? See the migration guide and the CHANGELOG.
License
Apache License 2.0. See NOTICE for attribution information.
Metadata
Release files for flask-xxljob 0.4.0
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_xxljob-0.4.0.tar.gz | 198.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flask_xxljob-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 281.0 kB
Release files / flask_xxljob-0.4.0.tar.gz
| Download URL | flask_xxljob-0.4.0.tar.gz |
|---|---|
| Size | 198.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e666af7754973e67f0b9c1f553f8102a895259b0411d9364996f165c0e78f1dc
|
|
BLAKE2b-256 checksum How to use checksums |
b8b83746822e99742742951525b57a2c722d059ccb29f30748e855d12d989d30
|
| 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 21, 2026.
Transparency logRelease files / flask_xxljob-0.4.0-py3-none-any.whl
| Download URL | flask_xxljob-0.4.0-py3-none-any.whl |
|---|---|
| Size | 82.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a214ff68ab10cd01a50327de440132dcf455423381af7fe0eee5183a21102f65
|
|
BLAKE2b-256 checksum How to use checksums |
4a4bf6b9e1a4003996cc5302c2659871506136ab75c34267fb54a35e59d75c94
|
| 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 21, 2026.
Transparency log