From 537c54df145fa0ee713746214ba8f69753616b08 Mon Sep 17 00:00:00 2001 From: Marsway Date: Mon, 5 Jan 2026 14:58:50 +0800 Subject: [PATCH] init --- .env | 9 + README.md | 142 ++ app/__init__.py | 2 + app/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 112 bytes app/__pycache__/main.cpython-312.pyc | Bin 0 -> 2300 bytes app/admin/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 118 bytes app/admin/__pycache__/routes.cpython-312.pyc | Bin 0 -> 1842 bytes app/admin/__pycache__/views.cpython-312.pyc | Bin 0 -> 9535 bytes app/admin/routes.py | 37 + app/admin/templates/job_details.html | 17 + app/admin/templates/job_list.html | 309 ++++ app/admin/templates/joblog_details.html | 119 ++ app/admin/templates/joblog_list.html | 309 ++++ app/admin/views.py | 191 +++ app/core/__init__.py | 3 + app/core/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 117 bytes app/core/__pycache__/config.cpython-312.pyc | Bin 0 -> 962 bytes .../__pycache__/log_capture.cpython-312.pyc | Bin 0 -> 4061 bytes app/core/__pycache__/logging.cpython-312.pyc | Bin 0 -> 1599 bytes app/core/config.py | 20 + app/core/log_capture.py | 82 + app/core/logging.py | 37 + app/db/__init__.py | 3 + app/db/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 115 bytes app/db/__pycache__/crud.cpython-312.pyc | Bin 0 -> 2584 bytes app/db/__pycache__/engine.cpython-312.pyc | Bin 0 -> 820 bytes app/db/__pycache__/models.cpython-312.pyc | Bin 0 -> 3628 bytes app/db/__pycache__/schema.cpython-312.pyc | Bin 0 -> 1653 bytes app/db/crud.py | 64 + app/db/engine.py | 21 + app/db/models.py | 65 + app/db/schema.py | 26 + app/integrations/__init__.py | 5 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 255 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3751 bytes .../__pycache__/base_client.cpython-312.pyc | Bin 0 -> 263 bytes app/integrations/base.py | 70 + app/jobs/__init__.py | 3 + app/jobs/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 117 bytes app/jobs/__pycache__/base.cpython-312.pyc | Bin 0 -> 982 bytes app/jobs/base.py | 21 + app/main.py | 52 + app/plugins/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 120 bytes .../__pycache__/manager.cpython-312.pyc | Bin 0 -> 2182 bytes app/plugins/manager.py | 46 + app/security/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 121 bytes .../__pycache__/fernet.cpython-312.pyc | Bin 0 -> 3660 bytes app/security/fernet.py | 63 + app/tasks/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 118 bytes .../__pycache__/celery_app.cpython-312.pyc | Bin 0 -> 838 bytes .../__pycache__/dispatcher.cpython-312.pyc | Bin 0 -> 2916 bytes app/tasks/__pycache__/execute.cpython-312.pyc | Bin 0 -> 5273 bytes app/tasks/celery_app.py | 34 + app/tasks/dispatcher.py | 70 + app/tasks/execute.py | 112 ++ connecthub.sh | 164 ++ data/connecthub.db | Bin 0 -> 73728 bytes data/fernet.key | 1 + data/logs/connecthub.log | 1450 +++++++++++++++++ docker-compose.dev.yml | 27 + docker-compose.yml | 48 + docker/Dockerfile | 22 + env.example | 9 + extensions/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 119 bytes extensions/example/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 127 bytes .../__pycache__/client.cpython-312.pyc | Bin 0 -> 732 bytes .../example/__pycache__/job.cpython-312.pyc | Bin 0 -> 1443 bytes extensions/example/client.py | 16 + extensions/example/job.py | 33 + pyproject.toml | 33 + 76 files changed, 3753 insertions(+) create mode 100644 .env create mode 100644 README.md create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-312.pyc create mode 100644 app/__pycache__/main.cpython-312.pyc create mode 100644 app/admin/__init__.py create mode 100644 app/admin/__pycache__/__init__.cpython-312.pyc create mode 100644 app/admin/__pycache__/routes.cpython-312.pyc create mode 100644 app/admin/__pycache__/views.cpython-312.pyc create mode 100644 app/admin/routes.py create mode 100644 app/admin/templates/job_details.html create mode 100644 app/admin/templates/job_list.html create mode 100644 app/admin/templates/joblog_details.html create mode 100644 app/admin/templates/joblog_list.html create mode 100644 app/admin/views.py create mode 100644 app/core/__init__.py create mode 100644 app/core/__pycache__/__init__.cpython-312.pyc create mode 100644 app/core/__pycache__/config.cpython-312.pyc create mode 100644 app/core/__pycache__/log_capture.cpython-312.pyc create mode 100644 app/core/__pycache__/logging.cpython-312.pyc create mode 100644 app/core/config.py create mode 100644 app/core/log_capture.py create mode 100644 app/core/logging.py create mode 100644 app/db/__init__.py create mode 100644 app/db/__pycache__/__init__.cpython-312.pyc create mode 100644 app/db/__pycache__/crud.cpython-312.pyc create mode 100644 app/db/__pycache__/engine.cpython-312.pyc create mode 100644 app/db/__pycache__/models.cpython-312.pyc create mode 100644 app/db/__pycache__/schema.cpython-312.pyc create mode 100644 app/db/crud.py create mode 100644 app/db/engine.py create mode 100644 app/db/models.py create mode 100644 app/db/schema.py create mode 100644 app/integrations/__init__.py create mode 100644 app/integrations/__pycache__/__init__.cpython-312.pyc create mode 100644 app/integrations/__pycache__/base.cpython-312.pyc create mode 100644 app/integrations/__pycache__/base_client.cpython-312.pyc create mode 100644 app/integrations/base.py create mode 100644 app/jobs/__init__.py create mode 100644 app/jobs/__pycache__/__init__.cpython-312.pyc create mode 100644 app/jobs/__pycache__/base.cpython-312.pyc create mode 100644 app/jobs/base.py create mode 100644 app/main.py create mode 100644 app/plugins/__init__.py create mode 100644 app/plugins/__pycache__/__init__.cpython-312.pyc create mode 100644 app/plugins/__pycache__/manager.cpython-312.pyc create mode 100644 app/plugins/manager.py create mode 100644 app/security/__init__.py create mode 100644 app/security/__pycache__/__init__.cpython-312.pyc create mode 100644 app/security/__pycache__/fernet.cpython-312.pyc create mode 100644 app/security/fernet.py create mode 100644 app/tasks/__init__.py create mode 100644 app/tasks/__pycache__/__init__.cpython-312.pyc create mode 100644 app/tasks/__pycache__/celery_app.cpython-312.pyc create mode 100644 app/tasks/__pycache__/dispatcher.cpython-312.pyc create mode 100644 app/tasks/__pycache__/execute.cpython-312.pyc create mode 100644 app/tasks/celery_app.py create mode 100644 app/tasks/dispatcher.py create mode 100644 app/tasks/execute.py create mode 100755 connecthub.sh create mode 100644 data/connecthub.db create mode 100644 data/fernet.key create mode 100644 data/logs/connecthub.log create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 env.example create mode 100644 extensions/__init__.py create mode 100644 extensions/__pycache__/__init__.cpython-312.pyc create mode 100644 extensions/example/__init__.py create mode 100644 extensions/example/__pycache__/__init__.cpython-312.pyc create mode 100644 extensions/example/__pycache__/client.cpython-312.pyc create mode 100644 extensions/example/__pycache__/job.cpython-312.pyc create mode 100644 extensions/example/client.py create mode 100644 extensions/example/job.py create mode 100644 pyproject.toml diff --git a/.env b/.env new file mode 100644 index 0000000..2903281 --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +APP_NAME=ConnectHub +DATA_DIR=/data +DB_URL=sqlite:////data/connecthub.db +REDIS_URL=redis://redis:6379/0 +FERNET_KEY_PATH=/data/fernet.key +DEV_MODE=1 +LOG_DIR=/data/logs + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..599c7cc --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +## ConnectHub 开发手册 + +ConnectHub 是一个轻量级企业集成中间件:统一管理多系统集成任务(Job),提供定时调度、执行监控与“一键重试”。 + +### 项目结构树 + +``` +. +├── app +│ ├── admin +│ │ ├── routes.py +│ │ ├── templates +│ │ │ └── joblog_details.html +│ │ └── views.py +│ ├── core +│ │ ├── config.py +│ │ └── logging.py +│ ├── db +│ │ ├── crud.py +│ │ ├── engine.py +│ │ └── models.py +│ ├── integrations +│ │ └── base.py +│ ├── jobs +│ │ └── base.py +│ ├── plugins +│ │ └── manager.py +│ ├── security +│ │ └── fernet.py +│ ├── tasks +│ │ ├── celery_app.py +│ │ ├── dispatcher.py +│ │ └── execute.py +│ └── main.py +├── extensions +│ └── example +│ ├── client.py +│ └── job.py +├── docker +│ └── Dockerfile +├── docker-compose.yml +├── env.example +└── pyproject.toml +``` + +### 环境与配置 + +- `env.example`:环境变量示例(由于环境限制,仓库中使用该文件名;本地运行时请手动创建 `.env` 并参考此文件) +- 关键变量: + - `DATA_DIR=/data`:容器内数据目录 + - `DB_URL=sqlite:////data/connecthub.db`:SQLite DB 文件 + - `REDIS_URL=redis://redis:6379/0`:Celery Broker/Backend + - `FERNET_KEY_PATH=/data/fernet.key`:Fernet key 文件(自动生成并持久化) + - `LOG_DIR=/data/logs`:日志目录(可选) + +### 核心框架实现要点 + +#### BaseJob(插件规范) + +- 位置:`app/jobs/base.py` +- 规范:插件必须实现 `run(params, secrets)`,其中: + - `params` 来自 `Job.public_cfg`(明文) + - `secrets` 来自 `Job.secret_cfg` 解密后的明文(仅内存) + +#### BaseClient(适配器/SDK) + +- 位置:`app/integrations/base.py` +- 规范:业务 Job 禁止直接写 HTTP;必须通过 Client 访问外部系统(统一超时、重试、日志)。 + +#### Security(Fernet 加解密) + +- 位置:`app/security/fernet.py` +- 说明: + - `secret_cfg` 在数据库中保存 **Fernet 密文 token** + - Worker 执行前自动解密,仅在内存中传给 Job + - key 自动生成到 `FERNET_KEY_PATH`(默认 `/data/fernet.key`),volume 挂载后可持久化 + +#### PluginManager(动态加载) + +- 位置:`app/plugins/manager.py` +- `handler_path` 推荐格式:`extensions.example.job:ExampleJob` + +### 数据层与 Admin(SQLAdmin) + +- 模型:`app/db/models.py`(Job / JobLog) +- Admin: + - `Job`:可视化增删改查 + - `JobLog`:可视化查看执行日志(只读) + - `JobLog` 详情页自定义 `Retry` 按钮:点击后读取 `snapshot_params` 并触发重试 +- 关键文件: + - `app/admin/views.py`:ModelView 定义;保存 Job 时自动加密 `secret_cfg` + - `app/admin/templates/joblog_details.html`:JobLog 详情模板覆盖,加入 Retry 按钮 + - `app/admin/routes.py`:处理 Retry POST 并触发 Celery + +### 任务引擎(Celery) + +- Celery app:`app/tasks/celery_app.py` +- 调度: + - Beat 每分钟触发一次 `connecthub.dispatcher.tick` + - `dispatcher.tick` 读取 DB Jobs,根据 `cron_expr` 到点触发 `connecthub.execute_job` +- 执行: + - `app/tasks/execute.py` 的 `execute_job`:读库/解密/加载插件/执行/写 JobLog(含异常堆栈) + +### 运行指南 + +1. 在仓库根目录创建 `.env`(参考 `env.example`) +2. 生产模式启动: + +```bash +docker compose up -d --build +``` + +3. 打开 Admin: + - `http://localhost:8000/admin` +4. 创建一个示例 Job(ExampleJob): + - `id`: `example.hello` + - `cron_expr`: `* * * * *`(每分钟) + - `handler_path`: `extensions.example.job:ExampleJob` + - `public_cfg`: `{"name":"Mars"}` + - `secret_cfg`: `{"token":"demo-token"}`(保存时自动加密落库) +5. 等待 Beat 触发执行,或在 JobLog 里查看结果;若失败/想复跑,在 JobLog 详情页点击 **Retry**。 + +### 开发模式(实时更新代码) + +开发阶段可以使用 dev compose 叠加文件,实现: +- `backend`:`uvicorn --reload` +- `worker/beat`:监听代码变更后自动重启进程加载新代码 + +启动命令(二选一): + +```bash +# 方式 A:直接 docker compose 叠加 +docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build + +# 方式 B:使用脚本 +./connecthub.sh dev-build +./connecthub.sh dev-start +``` + +生产环境请只使用 `docker-compose.yml`(不挂载源码、不启用 reload/watch),发布通过重新 build 镜像完成。 + + diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..8fa2136 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,2 @@ +# + diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a63b8146a28f541c30f4582998c2536bfb8fba3f GIT binary patch literal 112 zcmX@j%ge<81p0c>nan`?F^Gc>KC=KtrZZGBXfpb(WGG?+@;-yq{Nhh6DA13O&&4<0CU8BV!TBd;k+46odc( literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8362666e81748fe14a2352746c94e7a84e59781 GIT binary patch literal 2300 zcma)7&2Jk;6rWv>y&HeTc9JG(n$i@d6&t`&frKKeO68*}{Rl`P6_&$lsZLJlXO#h zO5miUxT@YKaLP%$89n1>^{kjHPR{Mu`vq2=yj#!<0{1yZcR(KyIPDC&L;8@w8Rv*Q ztPcauTiM%KAAvCk`8j*q+S{Yn~nfT@n7l2z0Ax2dnGX)~+@2cpZ=5JN*!kZ)1%Pg62Ue*pm3 zI)?){4snffYtbrQhhhl=)@{j~F&EK3TeW8sBYNt8?{vu52X^AY5pCa|JeCJqFYqiM zt70ySWF+Q3f^4YdOFIBkENN;e`vD}xTq2fD1KI~)g(}aD0rOA+VsSuoT%PZ7$Ecd@ zhR6pU0F?+j9TG+c@q!v92CaF_b_qQWQl7ZrB6O>07pK;*tX4o>^Y~2t_)G)O?%?d#m;ae#PDnxD zS0KClbKLWuaf*w!Pu3-JkG_>dveBdTEwq9v-E71wBE-7cI4lP%h!tX6OWsOscFuB3 zT9GQ8um>9Bp%ksR+P_#1A7Y6rM`LMkv~awZmAsAO7~GOq(1%D%@k^RyHgm|$L4Ns6 zUXP|3ajOngjX+C;2@s1*8s@rU&W)SO^bKM>-9CP$viQu6h70;NXxIND30Y<@MmRuKObg-3;YA)fQ$C6VdN za2^pE?O2#LjD;F#iD6Lg2_1zlR2JYRGpn|+3f15302vrq*V631$!}~f>=yQJ8zyyUYO(SSo5Xm_3O~IBXfO=I6?IEaBi0Z zQm#>(EtjHtCW>2B%XqC4c@Swk$TsyHELC&JdHM#}@nK7(YYGgLBYGWxA#C}INgK7-W!5=|^9&`(Us&CJt} rkI&4@EQycTE2zB1VUwGmQks)$SHucb#R$a3AjU^#Mn=XWW*`dyvLF_! literal 0 HcmV?d00001 diff --git a/app/admin/__pycache__/routes.cpython-312.pyc b/app/admin/__pycache__/routes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..123d3dc8b02b60c2fbcdd01f811385dd8a3a566f GIT binary patch literal 1842 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 U@69N>Y0ZLX + /* 仅调整详情页顶部动作按钮间距(Go Back/Delete/Edit/自定义 Action) */ + a.btn + a.btn, + a.btn + button.btn, + button.btn + a.btn, + button.btn + button.btn { + margin-left: 0.5rem; + } + +{% endblock %} + + diff --git a/app/admin/templates/job_list.html b/app/admin/templates/job_list.html new file mode 100644 index 0000000..7152902 --- /dev/null +++ b/app/admin/templates/job_list.html @@ -0,0 +1,309 @@ +{% extends "sqladmin/list.html" %} + +{% block content %} + + +
+
+
+
+
+
+
+

{{ model_view.name_plural }}

+
+ {% if model_view.can_export %} + {% if model_view.export_types | length > 1 %} + + {% elif model_view.export_types | length == 1 %} + + {% endif %} + {% endif %} + {% if model_view.can_create %} + + {% endif %} +
+
+
+
+ + {% if model_view.column_searchable_list %} +
+
+ + + +
+
+ {% endif %} +
+
+
+ + + + + + {% for name in model_view._list_prop_names %} + {% set label = model_view._column_labels.get(name, name) %} + + {% endfor %} + + + + + {% for row in pagination.rows %} + + + + {% for name in model_view._list_prop_names %} + {% set value, formatted_value = model_view.get_list_value(row, name) %} + {% if name in model_view._relation_names %} + {% if is_list( value ) %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% endfor %} + + + {% endfor %} + +
+ {% if name in model_view._sort_fields %} + {% if request.query_params.get("sortBy") == name and request.query_params.get("sort") == "asc" %} + {{ + label }} + {% elif request.query_params.get("sortBy") == name and request.query_params.get("sort") == "desc" %} + {{ label + }} + {% else %} + {{ label }} + {% endif %} + {% else %} + {{ label }} + {% endif %} + Run Now
+ + + + {% if model_view.can_view_details %} + + + + {% endif %} + {% if model_view.can_edit %} + + + + {% endif %} + {% if model_view.can_delete %} + + + + {% endif %} + + {% for elem, formatted_elem in zip(value, formatted_value) %} + {% if model_view.show_compact_lists %} + ({{ formatted_elem }}) + {% else %} + {{ formatted_elem }}
+ {% endif %} + {% endfor %} +
{{ formatted_value }}{{ formatted_value }} +
+ +
+
+
+ +
+
+ {% if model_view.get_filters() %} +
+
+
+

Filters

+
+
+ {% for filter in model_view.get_filters() %} + {% if filter.has_operator %} +
+
{{ filter.title }}
+
+ {% set current_filter = request.query_params.get(filter.parameter_name, '') %} + {% set current_op = request.query_params.get(filter.parameter_name + '_op', '') %} + {% if current_filter %} +
+ Current: {{ current_op }} {{ current_filter }} + [Clear] +
+ {% endif %} +
+ {% for key, value in request.query_params.items() %} + {% if key != filter.parameter_name and key != filter.parameter_name + '_op' %} + + {% endif %} + {% endfor %} + + + +
+
+
+ {% else %} +
+
{{ filter.title }}
+
+ {% for lookup in filter.lookups(request, model_view.model, model_view._run_arbitrary_query) %} + + {{ lookup[1] }} + + {% endfor %} +
+
+ {% endif %} + {% endfor %} +
+
+
+ {% endif %} +
+
+
+ {% if model_view.can_delete %} + {% include 'sqladmin/modals/delete.html' %} + {% endif %} + + {% for custom_action in model_view._custom_actions_in_list %} + {% if custom_action in model_view._custom_actions_confirmation %} + {% with confirmation_message = model_view._custom_actions_confirmation[custom_action], custom_action=custom_action, + url=model_view._url_for_action(request, custom_action) %} + {% include 'sqladmin/modals/list_action_confirmation.html' %} + {% endwith %} + {% endif %} + {% endfor %} +
+{% endblock %} + + diff --git a/app/admin/templates/joblog_details.html b/app/admin/templates/joblog_details.html new file mode 100644 index 0000000..82340de --- /dev/null +++ b/app/admin/templates/joblog_details.html @@ -0,0 +1,119 @@ +{% extends "sqladmin/layout.html" %} +{% block content %} + + +
+
+
+

+ {% for pk in model_view.pk_columns -%} + {{ pk.name }} + {%- if not loop.last %};{% endif -%} + {% endfor %}: {{ get_object_identifier(model) }} +

+
+
+
+ + + + + + + + + {% for name in model_view._details_prop_names %} + {% set label = model_view._column_labels.get(name, name) %} + + + {% set value, formatted_value = model_view.get_detail_value(model, name) %} + {% if name in model_view._relation_names %} + {% if is_list( value ) %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + + {% endfor %} + +
ColumnValue
{{ label }} + {% for elem, formatted_elem in zip(value, formatted_value) %} + {% if model_view.show_compact_lists %} + ({{ formatted_elem }}) + {% else %} + {{ formatted_elem }}
+ {% endif %} + {% endfor %} +
{{ formatted_value }}{{ formatted_value }}
+
+ +
+
+
+{% if model_view.can_delete %} +{% include 'sqladmin/modals/delete.html' %} +{% endif %} + +{% for custom_action in model_view._custom_actions_in_detail %} +{% if custom_action in model_view._custom_actions_confirmation %} +{% with confirmation_message = model_view._custom_actions_confirmation[custom_action], custom_action=custom_action, +url=model_view._url_for_action(request, custom_action) + '?pks=' + (get_object_identifier(model) | string) %} +{% include 'sqladmin/modals/details_action_confirmation.html' %} +{% endwith %} +{% endif %} +{% endfor %} +{% endblock %} + + diff --git a/app/admin/templates/joblog_list.html b/app/admin/templates/joblog_list.html new file mode 100644 index 0000000..cc87927 --- /dev/null +++ b/app/admin/templates/joblog_list.html @@ -0,0 +1,309 @@ +{% extends "sqladmin/list.html" %} + +{% block content %} + + +
+
+
+
+
+
+
+

{{ model_view.name_plural }}

+
+ {% if model_view.can_export %} + {% if model_view.export_types | length > 1 %} + + {% elif model_view.export_types | length == 1 %} + + {% endif %} + {% endif %} + {% if model_view.can_create %} + + {% endif %} +
+
+
+
+ + {% if model_view.column_searchable_list %} +
+
+ + + +
+
+ {% endif %} +
+
+
+ + + + + + {% for name in model_view._list_prop_names %} + {% set label = model_view._column_labels.get(name, name) %} + + {% endfor %} + + + + + {% for row in pagination.rows %} + + + + {% for name in model_view._list_prop_names %} + {% set value, formatted_value = model_view.get_list_value(row, name) %} + {% if name in model_view._relation_names %} + {% if is_list( value ) %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% endfor %} + + + {% endfor %} + +
+ {% if name in model_view._sort_fields %} + {% if request.query_params.get("sortBy") == name and request.query_params.get("sort") == "asc" %} + {{ + label }} + {% elif request.query_params.get("sortBy") == name and request.query_params.get("sort") == "desc" %} + {{ label + }} + {% else %} + {{ label }} + {% endif %} + {% else %} + {{ label }} + {% endif %} + Retry
+ + + + {% if model_view.can_view_details %} + + + + {% endif %} + {% if model_view.can_edit %} + + + + {% endif %} + {% if model_view.can_delete %} + + + + {% endif %} + + {% for elem, formatted_elem in zip(value, formatted_value) %} + {% if model_view.show_compact_lists %} + ({{ formatted_elem }}) + {% else %} + {{ formatted_elem }}
+ {% endif %} + {% endfor %} +
{{ formatted_value }}{{ formatted_value }} +
+ +
+
+
+ +
+
+ {% if model_view.get_filters() %} +
+
+
+

Filters

+
+
+ {% for filter in model_view.get_filters() %} + {% if filter.has_operator %} +
+
{{ filter.title }}
+
+ {% set current_filter = request.query_params.get(filter.parameter_name, '') %} + {% set current_op = request.query_params.get(filter.parameter_name + '_op', '') %} + {% if current_filter %} +
+ Current: {{ current_op }} {{ current_filter }} + [Clear] +
+ {% endif %} +
+ {% for key, value in request.query_params.items() %} + {% if key != filter.parameter_name and key != filter.parameter_name + '_op' %} + + {% endif %} + {% endfor %} + + + +
+
+
+ {% else %} +
+
{{ filter.title }}
+
+ {% for lookup in filter.lookups(request, model_view.model, model_view._run_arbitrary_query) %} + + {{ lookup[1] }} + + {% endfor %} +
+
+ {% endif %} + {% endfor %} +
+
+
+ {% endif %} +
+
+
+ {% if model_view.can_delete %} + {% include 'sqladmin/modals/delete.html' %} + {% endif %} + + {% for custom_action in model_view._custom_actions_in_list %} + {% if custom_action in model_view._custom_actions_confirmation %} + {% with confirmation_message = model_view._custom_actions_confirmation[custom_action], custom_action=custom_action, + url=model_view._url_for_action(request, custom_action) %} + {% include 'sqladmin/modals/list_action_confirmation.html' %} + {% endwith %} + {% endif %} + {% endfor %} +
+{% endblock %} + + diff --git a/app/admin/views.py b/app/admin/views.py new file mode 100644 index 0000000..872278d --- /dev/null +++ b/app/admin/views.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +import json +from datetime import datetime +from typing import Any +from zoneinfo import ZoneInfo + +from croniter import croniter +from markupsafe import Markup +from sqladmin import ModelView, action +from sqladmin.models import Request +from starlette.responses import RedirectResponse + +from app.db.models import Job, JobLog +from app.plugins.manager import load_job_class +from app.security.fernet import encrypt_json +from app.tasks.execute import execute_job + + +def _maybe_json(value: Any) -> Any: + if isinstance(value, str): + s = value.strip() + if not s: + return value + try: + return json.loads(s) + except json.JSONDecodeError: + return value + return value + + +def _fmt_dt_seconds(dt: datetime | None) -> str: + if not dt: + return "" + # DB 中保存的时间多为 naive;按 UTC 解释后转换为 Asia/Shanghai 展示 + tz = ZoneInfo("Asia/Shanghai") + if dt.tzinfo is None: + dt = dt.replace(tzinfo=ZoneInfo("UTC")) + return dt.astimezone(tz).strftime("%Y-%m-%d %H:%M:%S") + + +def _truncate(s: str, n: int = 120) -> str: + s = s or "" + return (s[: n - 3] + "...") if len(s) > n else s + + +class JobAdmin(ModelView, model=Job): + name = "Job" + name_plural = "Jobs" + icon = "fa fa-cogs" + + column_list = [Job.id, Job.enabled, Job.cron_expr, Job.handler_path, Job.updated_at] + column_details_list = [ + Job.id, + Job.enabled, + Job.cron_expr, + Job.handler_path, + Job.public_cfg, + Job.secret_cfg, + Job.last_run_at, + Job.created_at, + Job.updated_at, + ] + + # 允许在表单中编辑主键(创建 Job 必填) + form_include_pk = True + form_columns = [Job.id, Job.enabled, Job.cron_expr, Job.handler_path, Job.public_cfg, Job.secret_cfg] + + # 为 Job 详情页指定模板(用于调整按钮间距) + details_template = "job_details.html" + + # 列表页模板:加入每行 Run Now + list_template = "job_list.html" + + @action( + name="run_now", + label="Run Now", + confirmation_message="Trigger this job now?", + add_in_list=True, + add_in_detail=True, + ) + async def run_now(self, request: Request): # type: ignore[override] + pks = request.query_params.get("pks", "").split(",") + for pk in [p for p in pks if p]: + execute_job.delay(job_id=pk) + referer = request.headers.get("Referer") + return RedirectResponse(referer or request.url_for("admin:list", identity=self.identity), status_code=303) + + async def on_model_change(self, data: dict, model: Job, is_created: bool, request) -> None: # type: ignore[override] + # id 必填(避免插入时触发 NOT NULL) + raw_id = data.get("id") if is_created else (data.get("id") or getattr(model, "id", None)) + if raw_id is None or not str(raw_id).strip(): + raise ValueError("id is required") + + # handler_path 强校验:必须可 import 且继承 BaseJob + handler_path = data.get("handler_path") if is_created else (data.get("handler_path") or model.handler_path) + if handler_path is None or not str(handler_path).strip(): + raise ValueError("handler_path is required") + load_job_class(str(handler_path).strip()) + + # cron_expr 校验:必须是合法 cron 表达式 + cron_expr = data.get("cron_expr") if is_created else (data.get("cron_expr") or model.cron_expr) + if cron_expr is None or not str(cron_expr).strip(): + raise ValueError("cron_expr is required") + base = datetime.now(ZoneInfo("Asia/Shanghai")) + itr = croniter(str(cron_expr).strip(), base) + _ = itr.get_next(datetime) + + # public_cfg 允许以 JSON 字符串输入 + pcfg = _maybe_json(data.get("public_cfg")) + if isinstance(pcfg, str): + raise ValueError("public_cfg must be a JSON object") + if isinstance(pcfg, dict): + data["public_cfg"] = pcfg + + # secret_cfg:若用户输入 JSON 字符串,则自动加密落库;若输入已是 token,则原样保存 + scfg = data.get("secret_cfg", "") + if scfg is None: + data["secret_cfg"] = "" + return + if isinstance(scfg, str): + s = scfg.strip() + if not s: + data["secret_cfg"] = "" + return + parsed = _maybe_json(s) + if isinstance(parsed, dict): + data["secret_cfg"] = encrypt_json(parsed) + else: + # 非 JSON:视为已加密 token + data["secret_cfg"] = s + return + if isinstance(scfg, dict): + data["secret_cfg"] = encrypt_json(scfg) + return + raise ValueError("secret_cfg must be JSON object or encrypted token string") + + +class JobLogAdmin(ModelView, model=JobLog): + name = "JobLog" + name_plural = "JobLogs" + icon = "fa fa-list" + + can_create = False + can_edit = False + can_delete = False + + # 列表更适合扫读:保留关键字段 + message(截断) + column_list = [JobLog.id, JobLog.job_id, JobLog.status, JobLog.started_at, JobLog.finished_at, JobLog.message] + # 默认按 started_at 倒序(最新在前) + column_default_sort = [(JobLog.started_at, True)] + column_details_list = [ + JobLog.id, + JobLog.job_id, + JobLog.status, + JobLog.snapshot_params, + JobLog.message, + JobLog.traceback, + JobLog.run_log, + JobLog.celery_task_id, + JobLog.attempt, + JobLog.started_at, + JobLog.finished_at, + ] + + # 列表页模板:加入每行 Retry + list_template = "joblog_list.html" + # 为 JobLog 详情页单独指定模板(用于加入 Retry 按钮) + details_template = "joblog_details.html" + + column_formatters = { + JobLog.started_at: lambda m, a: _fmt_dt_seconds(m.started_at), + JobLog.finished_at: lambda m, a: _fmt_dt_seconds(m.finished_at), + JobLog.message: lambda m, a: _truncate(m.message, 120), + } + + column_formatters_detail = { + JobLog.started_at: lambda m, a: _fmt_dt_seconds(m.started_at), + JobLog.finished_at: lambda m, a: _fmt_dt_seconds(m.finished_at), + JobLog.traceback: lambda m, a: Markup(f"
{m.traceback or ''}
"), + JobLog.run_log: lambda m, a: Markup( + "
"
+            + (m.run_log or "")
+            + "
" + ), + JobLog.snapshot_params: lambda m, a: Markup( + "
"
+            + json.dumps(m.snapshot_params or {}, ensure_ascii=False, indent=2, sort_keys=True)
+            + "
" + ), + } diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/app/core/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/app/core/__pycache__/__init__.cpython-312.pyc b/app/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6ef2d3266d8e001913fc1b58099353525bf40dd GIT binary patch literal 117 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 literal 0 HcmV?d00001 diff --git a/app/core/__pycache__/logging.cpython-312.pyc b/app/core/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edff46a7041d707e449394a63539673fe0c92ea0 GIT binary patch literal 1599 zcmbVM&1)M+6rbH4?U%kAIkIT$%8muuwvh`B6w**i3bGBV4NWgm3u3KZNh|M%*d2w) z$lySs_Q@#(oI(#hrr3x6AH7($g2Y6jg(QdE0`{q=&a8JOmzEYfhk3s@@4b1yc{6W* ziN|F?#!~Ss<9iN(=Tr#?dk&b>PYE~x8qgRMSWJasC}vHz!upb{a71z@v?7&=Z-XXp z2^E20UW@w$je-0;8$|u;scG-;8El-;I_?N;2Zm^C)Fl=*&f?U{@vbVrg;hL^gnp8QxEh; zXm4dgG?ngslnu4kY>1}v-=j7DNC;0wYVc+lhc1Vx^;U?cvh~gshv+}@h+!5@(&VFP zcyHTWkNck$;x1HZK4js)3O?MO0bQWP*6`(O&9QC0hMJuX3hPFrmU{127wI03Zm6}*PWv;bV)Ht4TGhI zj>-h5pw-*DpQ&muCA5Vu~c$gi&%(H{5EoR)e1Tf$4K8zjk2m~fyJc+{47@V*v@0a(=Kgkb*D{cz`^mlJBWWNm5Sl|V*B5h- z76#(mFJN@HbewtrvGxRhcuI~)Z<}{X30xb)HOJN!3Cq=Ly+c;?YSpEGn46#z?O&V< zE7v^#LxW36lFAloWywTeuz!tyEq?;;6&g*u0E;&*N9&mSC+@pMrQe&EBhzb+VVGxN W;W None: + super().__init__(level=level) + self.max_bytes = max_bytes + self._buf: list[str] = [] + self._size_bytes = 0 + self._truncated = False + + def emit(self, record: logging.LogRecord) -> None: # noqa: D401 + try: + if self._truncated: + return + try: + msg = self.format(record) + except Exception: + return + line = msg + "\n" + try: + b = line.encode("utf-8", errors="replace") + except Exception: + return + + if self._size_bytes + len(b) > self.max_bytes: + self._buf.append("[TRUNCATED] run_log exceeded max_bytes\n") + self._truncated = True + return + + self._buf.append(line) + self._size_bytes += len(b) + except Exception: + # 双保险:任何异常都不能冒泡 + return + + def get_text(self) -> str: + try: + return "".join(self._buf) + except Exception: + return "" + + +@contextmanager +def capture_logs(*, max_bytes: int = 200_000) -> Iterator[Callable[[], str]]: + """ + 捕获当前进程(root logger)输出的日志文本。 + 任何问题都不应影响业务执行。 + """ + root = logging.getLogger() + handler = SafeBufferingHandler(max_bytes=max_bytes) + handler.setLevel(logging.INFO) + handler.setFormatter( + logging.Formatter(fmt="%(asctime)s %(levelname)s %(name)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + ) + + try: + root.addHandler(handler) + except Exception: + # 无法挂载则降级为空 + yield lambda: "" + return + + try: + yield handler.get_text + finally: + try: + root.removeHandler(handler) + except Exception: + pass + + diff --git a/app/core/logging.py b/app/core/logging.py new file mode 100644 index 0000000..1fa274a --- /dev/null +++ b/app/core/logging.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import logging +import os +from logging.handlers import RotatingFileHandler + +from app.core.config import settings + + +def setup_logging() -> None: + logger = logging.getLogger() + if getattr(logger, "_connecthub_configured", False): + return + + logger.setLevel(logging.INFO) + formatter = logging.Formatter( + fmt="%(asctime)s %(levelname)s %(name)s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + sh = logging.StreamHandler() + sh.setFormatter(formatter) + logger.addHandler(sh) + + if settings.log_dir: + os.makedirs(settings.log_dir, exist_ok=True) + fh = RotatingFileHandler( + os.path.join(settings.log_dir, "connecthub.log"), + maxBytes=10 * 1024 * 1024, + backupCount=5, + ) + fh.setFormatter(formatter) + logger.addHandler(fh) + + setattr(logger, "_connecthub_configured", True) + + diff --git a/app/db/__init__.py b/app/db/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/app/db/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/app/db/__pycache__/__init__.cpython-312.pyc b/app/db/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47581d3bdac60794a8b1b813178378ff3b183cef GIT binary patch literal 115 zcmX@j%ge<81dH{eGg*N2V-N=&d2KczG$)vkyYsDu%Si$RQ!%#4hTMa)1J0FTZU@c;k- literal 0 HcmV?d00001 diff --git a/app/db/__pycache__/crud.cpython-312.pyc b/app/db/__pycache__/crud.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f03ecdc68cdd2bdc281dfcebba155648b470f956 GIT binary patch literal 2584 zcmb7FO>7%Q6rS;J);}Bn#g3EyQ8!5wEkQ{eL@zB!C=x#{N)O@Ws#u%dZQ^v-HZ$uM zN3N7g6{x2csZx62f~W*lxKyg>4I!>vT3p03q9=q9H>32_6YtI1&W{Re$KJQ!&b)m) z@4fH6`6-!95E!#F=PExA5b_5;^hPuUbn80+8^k6yb%{qylp>C}5ie4TNUXSurMCN}9w2Zq$pFViId^+)I=ao?gj3;yHkox2sLK zY20xg%Lf!)bT|h^LlMg9+LF*fd{A2!akwvnjRvb2xZyvSqu~bM|t>dp{>@I$`zZVZn5|*W!CGn_R_4y8um=RDWdRB z8B|yn)(7F@IS}h4(1&iEyngZ)zx_^IAAhKhZ^1ONcJ3$+6LP=b0N5aYe+_mH^LvL6 zyau}*&1(>J3V z>Ce-5v^)GO{nz60N5$j!#~)1Jn|?5RZ}x|Y_T0Pe;(P7P`)&R7L-jNpg4e8_aqIKp zQ&@Q!K3q{+3a3rdinNsSHebkUw-Uizf3%cdtSB<>IeFOZ>Qd4q3>jh!FbsYeFWogE z5Uf!xo4!y?+ZLKt^Sp|0s4{3Q4XPqy!y-lreJa8TqgZIvv3@Xq+{#n2CDwqnX6}RDv#AX;)SaVzo>Z`O+gk6 zO+-DY1asLDai5u%vt(KqMaqI2WzDj0@{3R$B5L}+#;6C?^)=movSq?@#%#jAJLrW5AUN!4{rRJCGCNn+G#=r&}M=^ooD2hoGQy>hD z;iegSCbWicRcn{oL7X{+Gg0PTU=Cj~^ldgU1D9V#@xP{p;?|ZqI)RMoj*vJhi#dJb z)Rq}`O#B}I2*f&hx}WITAU%x#(WxLa5)2&(MvfxL9r`&pcW3nL!(Se5ADIjCho9yW zy0$*wDUgA|y+rC#>g?6CH|HO#qk*1U@78I*s^EL*x}?L)_wBHrB4IsY9F2&KlhDBR z?XDd8P?cwP(BIQo_2ld_NBHy`B0sd1pvvx#>J3IVUiW@GodlZZ?NiG;&g4P z*eWj6s!p$3rRM9Pz81$0cMdep8?ilB7y_#@RZc5jnP{Q97{|qx1~jifMGX^9soqg5i;1|G^-a55^{g@u`1C z4$yRv%y$&HcQlgBbfSo2Bw6Uh5hX}rqN5`kB&lpCi6})fxlS5Uh76B(vWSLoHHRoq evc=9Yq5>H@(!nouMu|4KwGYtKQ`d-9*&pZ3EOOlm zf`xxT+O%>hw6YZ|!R7)1k3q2z5j(@BvhvOCg*48>oA3RYx8M6_K3A(Yg4JqnN53#a zKeThc%mU!aM*s(iB8p>_U=L%BOK~YFd*vxE$3{}|Dhct()Hh-?u{;Z-5@Ka$?Jb_S zyx9LA-Mey8agJzZ2+Z{21|`&taEc>Dt)sbNxrloX{D&}yRj3J{bz~1Kvy4lt)Ol%m z)xO)9|E+@!tehZCGa1MzO9i;qkh4HCpQW8BWsQ)FV&g4L7IIG%(sn&s?J0t!$3xoI#n&|Z3 zg`n4K(VbQ?UbEL%PKQZ-!P!0XZiK&$hgWcfx8Z)5YB*n%C(}6d1d>4 zF^%tYEx)osi690Ul0G+dVE!V|%y=?A$cfI>fJrmVI0KXJMjftSm?Ct?&v^`uWWc3( z9?7mE1zBE)f+<6;DNZ@@%leS@AjxPRvsHcrTrG(3!Tc#>jK86qKhdr4=q}h(g6!q7 z;lA2yZ9g45wKLPOO9OK1qJ`^+OK;5& V_eNXZDD0fnx&!x&V0>*#;2+y{(1!p3 literal 0 HcmV?d00001 diff --git a/app/db/__pycache__/models.cpython-312.pyc b/app/db/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bcc049fbcb5bcc7b3dea45173d945a5bf2f0bd9f GIT binary patch literal 3628 zcmbVOO>7&-6<(4{a``Vx6!kC5l2V&-EIF;?)=u2ivLwf{EW45nnCr5e6=x*T-d!@Y z%hXhRV1X3S(LK4x9Bo(!1uanZrnZL~C{VBvi&z5~g_=W=TMMZdpZeY`Nm&A11l_~i zH}l@S_vX!e-^`z5v9JW+_}IO|Z$gsv6%LwTz-2u9{{ZH(L?q%dB-5jMJjiQbT4~4Vw`)Vn)@d8B=3sT#cIvH6e5ZMw^*b zlV-cxZg!|0W=c(YB(HQ$BEiojqR`|spBszXDP}`38>XGD*|eCAz-*LuQSbd!a|?(6 zXL7?rx6qA&Zu~V1Jr!>z@inSsJUpUXR?*R&LeaA4V5|_`p-#c1!27OPl}w1sGYhxo zcyOXvG^lQIWfC+NL4(UvR@vm?=^~?r73&7A@ZfdJp(~W}z=FdH)(V#w=|hLh%VjH{ zk$Ga0<_(>Jw@>MWZc`q(sh3KW@R%w7TE1wMO^ZhvHQbO^3#I(w#w5w(4m|r&U>-|U zQawacy%kT!%VjhoS8Z%6|h!Io4JQsoWOkdTI2(lCvXz(%-L8Br1e+tHRhMq)?gaj`z$k|&7wh`fy? zV0~Lt4)G-+_T;lh&VfISw4JnrZpWK+VQt6#(_+g<$g{=>{H9uQcal!9mHvNieFI-z zt&T_wU)>E~U8INfZuGVC-%a`G4!<@?1Ydl6fu#0sX)D%t~Dc@=Rh z_nz&r9QPK;^i${^t85thl0kVW&x)2tAC?%8uId&sDAP*1v-;G_gM=>YWy4{^;N?}i zVTKg)4%&KW2zoA;gKCraKTy+@|bPi0nv2ZO0ZvEZ`d~5m=P*v3DVCt`x0u z36VnVf`{D!YH%N3jCY2hrKm&C7Fh{$01X;n((@0rQn6Hq?xr>m=5;%-6S~&S zf>E%Yd#51jv&EHDkOkQ&t^g|auugEw(nz|{eN59F+_u}xEQtaH1U7`ygr>a)YMRDI zP&A4J+m*|hY=+gzVo2Ub5=3$aiGpMtNgN3GS;e2Q@8Iw~B<)BpBI!mVP@xyO{w7HP zW4{9O3+Y9ww|a9gJ@WbVmRwI?u1>5+cI3;u18+aMur*a5n5bS~|LKlAu{(73$?{gY zJ~UOGeRO6=p4!b^*mSlRYm3=>My<}&+P<|Tt9yfIo=k2HZ)#`z|zgV{>+n8 zTj!s@RqwxDy|(_N9r^a|neY9s`}z6$#oM)eTK&w&)f=_s=^gpw-H~%&bZ?)pzxz=w ztJku*FSGhzv-(apU$gA`h=Wdsc4P ziBI)cjq-LO?`+9Cj;&7#c?+!Jdh0r3zf)W?O@PJyq#-vw&E3Pe>;5@6Pp{L~ z#Sq-+ZSAFp^ub>Go3R|X1$Gbf(n|(Fcd((`M^0=EHGQi6wKpwHgLr0_dN(um4tvIXfB*@m14UC02+&a(kfGrB1y#;c13auqqr zTr_86I1yPcSOt4k;JZM!jE8*}1g+gelsR?*X67;j$D8gFOuP7t&n1x~_C9j>)Mg(c zfv2|Hd2ALrgkXk8!EPcE2t0?}EhGZPZzK0ZBm#>OhS_B#^GFtu2pGngj`#T_&LP2U z+3-9UFg)nO@U^Yf^Tm34-UZ_M-QH7A!hx3#^#(@#>HY{2b%jfZUUP{AXNs*V*z)LCpPbv4Ml>3{M`@3{&N4j+o z@OVP&A09~XJ5Z7y;#nU(knn$S+UH3<@;zF7oc(3?K!U+RH061JeepjM{6wby3wQN` Aj{pDw literal 0 HcmV?d00001 diff --git a/app/db/__pycache__/schema.cpython-312.pyc b/app/db/__pycache__/schema.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0925e38fe2ccf3dd3c9c41887bc68db2d377d41 GIT binary patch literal 1653 zcmZuxeP|R{5P$D>?<05lP*K#7UbhLY*-Fn!Ed_gop1rGo^m4YEK$9Zp-t9({?(N36 zdzjb=QB#tuh6q*@{2~!UV^c8!3!zDn{#^4FNOk7)JxB2UoNeMz7ov@c_OK(cNTb%XaqO^t(A#fR1&>RL)YnT|PW$601T7Bc>68)(%4$-OwU$6d zPphrf!}6ktUJRmaEk6~WI5~3i(&mhQNgMroD*TVeg8KZHZJ1{bx6~&k)4D`x?hH?t zq$f`ZvTQJd(hq7jsVa)$wDLe|sR>KeQ<~+0>$mghaN3!~>tU9~Owb3-Lu6*l7}V+q zaF5T{ZR~oD&5Nx4j=e+IIO%WJ?m5)&Y(jgHhEF*#>Hzd$kn}Mf2=L)b{|27C9MF-^ zqusj16_*!F!(+waAI#C=!q4BYE?u*2bIWk^$YCim?G_{y} zc(Zu_Y;JKew|vFCGhci#T{^!EkC@Bz=H=1c!eaiXvxS)pt4kxfg>TLIbLQCn(qJZk z;jZ~`xo~CLTsmhiEUYeNET^>h6WxkvczV=CTvm^3hMS}n%ANk`)j(`D7Yc{Dj}JxL zTVq_=kZX&4(Z!(*2JqpEJKA=7ZtKd~=YS3j|DvVNxHaoq<`t2d2BGtnDcv;Jc%JB7#9iND>* zyJO6s@xRn4h~}F^jm$5N&d_dmjCYZ3ATk(DCsw Job | None: + return session.get(Job, job_id) + + +def list_enabled_jobs(session: Session) -> list[Job]: + return list(session.scalars(select(Job).where(Job.enabled.is_(True)))) + + +def update_job_last_run_at(session: Session, job_id: str, dt: datetime) -> None: + job = session.get(Job, job_id) + if not job: + return + job.last_run_at = dt + session.add(job) + session.commit() + + +def create_job_log( + session: Session, + *, + job_id: str, + status: JobStatus, + snapshot_params: dict[str, Any], + message: str = "", + traceback: str = "", + run_log: str = "", + celery_task_id: str = "", + attempt: int = 0, + started_at: datetime | None = None, + finished_at: datetime | None = None, +) -> JobLog: + log = JobLog( + job_id=job_id, + status=status, + snapshot_params=snapshot_params, + message=message, + traceback=traceback, + run_log=run_log, + celery_task_id=celery_task_id, + attempt=attempt, + started_at=started_at or datetime.utcnow(), + finished_at=finished_at, + ) + session.add(log) + session.commit() + session.refresh(log) + return log + + +def get_job_log(session: Session, log_id: int) -> JobLog | None: + return session.get(JobLog, log_id) + + diff --git a/app/db/engine.py b/app/db/engine.py new file mode 100644 index 0000000..247e489 --- /dev/null +++ b/app/db/engine.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from sqlalchemy import create_engine +from sqlalchemy.orm import Session, sessionmaker + +from app.core.config import settings + + +engine = create_engine( + settings.db_url, + connect_args={"check_same_thread": False} if settings.db_url.startswith("sqlite") else {}, + future=True, +) + +SessionLocal = sessionmaker(bind=engine, class_=Session, autoflush=False, autocommit=False, future=True) + + +def get_session() -> Session: + return SessionLocal() + + diff --git a/app/db/models.py b/app/db/models.py new file mode 100644 index 0000000..b024dd3 --- /dev/null +++ b/app/db/models.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import enum +from datetime import datetime +from typing import Any + +from sqlalchemy import JSON, Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, func +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship + + +class Base(DeclarativeBase): + pass + + +class Job(Base): + __tablename__ = "jobs" + + id: Mapped[str] = mapped_column(String, primary_key=True) + cron_expr: Mapped[str] = mapped_column(String, nullable=False) + handler_path: Mapped[str] = mapped_column(String, nullable=False) + public_cfg: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) + # 密文 token(Fernet 加密后的字符串) + secret_cfg: Mapped[str] = mapped_column(Text, default="", nullable=False) + + enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) + last_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False + ) + + logs: Mapped[list["JobLog"]] = relationship(back_populates="job", cascade="all, delete-orphan") + + +class JobStatus(str, enum.Enum): + SUCCESS = "SUCCESS" + FAILURE = "FAILURE" + RETRY = "RETRY" + + +class JobLog(Base): + __tablename__ = "job_logs" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + job_id: Mapped[str] = mapped_column(ForeignKey("jobs.id"), index=True, nullable=False) + + status: Mapped[JobStatus] = mapped_column( + Enum(JobStatus, native_enum=False, length=16), + nullable=False, + ) + snapshot_params: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) + + message: Mapped[str] = mapped_column(Text, default="", nullable=False) + traceback: Mapped[str] = mapped_column(Text, default="", nullable=False) + # 本次执行期间捕获到的完整运行日志(可能很长,按上层捕获器做截断) + run_log: Mapped[str] = mapped_column(Text, default="", nullable=False) + celery_task_id: Mapped[str] = mapped_column(String, default="", nullable=False) + attempt: Mapped[int] = mapped_column(Integer, default=0, nullable=False) + + started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) + finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) + + job: Mapped[Job] = relationship(back_populates="logs") + + diff --git a/app/db/schema.py b/app/db/schema.py new file mode 100644 index 0000000..8ad738e --- /dev/null +++ b/app/db/schema.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from sqlalchemy import Engine, text + +from app.db.models import Base + + +def _has_column(conn, table: str, col: str) -> bool: + rows = conn.execute(text(f"PRAGMA table_info({table})")).fetchall() + return any(r[1] == col for r in rows) # PRAGMA columns: (cid, name, type, notnull, dflt_value, pk) + + +def ensure_schema(engine: Engine) -> None: + """ + SQLite 轻量自升级: + - create_all 不会更新既有表结构,因此用 PRAGMA + ALTER TABLE 补列 + - 必须保证任何失败都不影响主流程(上层可选择忽略异常) + """ + Base.metadata.create_all(bind=engine) + + with engine.begin() as conn: + # job_logs.run_log + if not _has_column(conn, "job_logs", "run_log"): + conn.execute(text("ALTER TABLE job_logs ADD COLUMN run_log TEXT NOT NULL DEFAULT ''")) + + diff --git a/app/integrations/__init__.py b/app/integrations/__init__.py new file mode 100644 index 0000000..62b5580 --- /dev/null +++ b/app/integrations/__init__.py @@ -0,0 +1,5 @@ +"""系统集成适配器""" + +from app.integrations.base import BaseClient + +__all__ = ["BaseClient"] \ No newline at end of file diff --git a/app/integrations/__pycache__/__init__.cpython-312.pyc b/app/integrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17af24df7567497c2292302ed4b9453798163f9a GIT binary patch literal 255 zcmX@j%ge<81Q(2=Gc$nnV-N=hn4pZ$Qb5LZh7^V#wg}W z7ERVtQJ{k7n|D9oJ^$tGwr3p^UN$tnZ0&tIbHz)L5t@v*xSSG;Q=M}%Q}arSn1Mom znk={2_zW`Ymkdy? zKC)W<`1s7c%#!$cy@JYH95%W6DWy57c10jxF#>V12$1-|%*e?2k&S^-<|%{D18(sS Ly+-yT4xj`8=G#eo literal 0 HcmV?d00001 diff --git a/app/integrations/__pycache__/base.cpython-312.pyc b/app/integrations/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1feeb483c3470d825ada835ff69849273525a4a GIT binary patch literal 3751 zcma(UZEO_Bb!KmOZ|}pt!#;fe0v}+PEC!=A0ZJ7dz&5lLTvVJ&aX`y)ySC4q?~d7Z zz^8TPf>oK620N;H5K;jxZA@t+N3B$=v})A!N2UI`tKIrEb)-r_48MAbQ#DHe^v&*_ z?{G=dvHj+~H*ep(H}7M{f3B!-BN&ryFGc?#A#{%s#lhPMUXB5hMk-Qq3=QF4j2X_w zxL(eh9lZ{i9Wj1L=oJcUVz0;{jT@{iDl5$@e+S!Cuf(B)NEODBD&FA=Jl6;(J0CGZ z#?%vz#}h`_h$iBCH_(oK@sU(8l8DE(h%qpHsx2Bfw0>d>L~IQ<6zmBv9|t6jG}Mb# z1O{U;mQx)nuL`R82Jb>o`cb{Ab6o7@)e2P_=X(XUQgy*Bsy@{Xvs3k}YgEs;&?}7y zaus#&59?Y-EULwg;c?i`9fGlR_xkDsT}fAuF1zH>G7m7boX|NQNx z2bX{L@WSsN&Rkgf{&c9^?a=+%@4_}A+km3Ne%t@%y^q+z7N~zb-!1dhWkiRx#IT|0 z)JZghL^YlI7&4-q3P(;S`uf<)fEHFYqDKm`LhL|A_;VnHm%YGEBNGo6SMA*_sN!OL zO{A=>n;cMu7$amlfC5eSFnOQ~*#>LEIjnOSMGhH zDD+8fIGNmDo+aCvVQt9~Dk(}d9yJstRb5Kkwj#3{ROqab#!$Wr)or-$yXu>E%++q4 z;4f9=>NdhunG3Fm$(w6xo8afAMj&4sy}?$z_k)dTbIQ!EtxKPnIt>(5UD_h7;J<7-S(|HHv33>g3o7vQ%3&o|M_DWMY$79dB@^;}(7uk4mMA`~UVc zohe9Wl}|y<7HuAN87nuy;F?ut0I3RWS5q2fr@m^Kt~arfx3x3)REs{O%c&M?p)VYbX)5Evk{k*fhBlNm z_5i67vZsX*mRh%w?4xeo2opm;8#M-~K*I5U&1zVbXMN2sFStF<3~RbU9nfj07)$hn z5apxsz6ABa=f<$EL=vh-{UjWPw%C^-ip{33u5%GB$?kRPJR2tQXuO{adQ8)jR!5XQ zR>_j}U=8JyVX#3w4RZZDX<{rX7S;_#I~SqOf(!69vzPKj)05P##llHlQj4FCT7j=`f&+#wP*a~yOeJna=4-ZGRA)BKbk6LYaZfmME%Jo4 z7;L=KbN%?$$d$Z*fw!6&s9{^EY>z&cU^T|^Gpi4VC$s8I^sgGH5+Wr1dq(@ zoYk|zBk%M9nB*7Ng>E&!Dc_WT+&Uv9O9?QINJfobKYyu~7ok-j znmTmFSZI1C+w{!Lnc2;6r?O4^=K~#!fySJF?SJwWp!E}-MdR1|-5nn8U5~5tY2n?# zzN*fx!XH~PgIfh)MapkxR?!d*ynFzVG&Fpu48T?o=##jQgBJkDdeLWf{9{lC-PCcg ztLHXHSV$>9s3+pG!%m=Wz#(k_bk=Jj{8sF#)UO!|lY8tEEHd{p`Aq;I1AGk=#%1%O zd8O;dvsvF4r*{Ha@a@X_cFp;AXQbW#doirIcnCCno{K763sG#h+|O|no*4YmuOI6n z>~bW#0LXlqlNQ(Vl!qmj=(ek_WCBXX@-?_<3kk;AFSMKbA8=YBS`?Qhqwa>f^ z*n+Pk>+6{Fb!DV3%VXJVJwvDi9yQi$h6;VLMA)z&H7%gde%Z7$jFn$%ts0FOFM;^h z5P%aQGt`}kYw#@6jA0U|Jfq;=ex7}>7l15T)lc>?h80E&V;&Obp(UBBv>r`u-+-cr ze-SiU{8Ul}J?=41^e9PYAp9uv?ojzLvv2v z_JOZjaOXu-U6Xe*OhSRWyo+IORJAtmVVD=yH{~lBR*9-M=dCaN$X&U-26)R`u&{1f ztQLX{`|`g;EF8eL9c*`am4Z`!!w>*)im(cyPS(Yt8xxlQR5V7CjIa)f-QUy&MRM3` z^yCXbTVAYkk4n$k2h~o#3L@;P(suyJJ21xYp^f)Y<=>I#JydfKdBHn(!$fewRiAa$ YXBwWJbL|)t|1Ivq2k?i8LCf0z0;?K3)&Kwi literal 0 HcmV?d00001 diff --git a/app/integrations/__pycache__/base_client.cpython-312.pyc b/app/integrations/__pycache__/base_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef7708fdd47d602a162a6a1fb0df2eefafdabae0 GIT binary patch literal 263 zcmX@j%ge<81XqosGoJwI#~=<2FhLlMpOt`&=?p0hDU3M`xr|Yaj372s4pT036f=;` zoX!} None: + self.base_url = base_url.rstrip("/") + self.timeout_s = timeout_s + self.retries = retries + self.retry_backoff_s = retry_backoff_s + self.headers = headers or {} + + self._client = httpx.Client( + base_url=self.base_url, + timeout=httpx.Timeout(self.timeout_s), + headers=self.headers, + ) + + def close(self) -> None: + self._client.close() + + def request(self, method: str, path: str, **kwargs: Any) -> httpx.Response: + url = path if path.startswith("/") else f"/{path}" + last_exc: Exception | None = None + for attempt in range(self.retries + 1): + try: + start = time.time() + resp = self._client.request(method=method, url=url, **kwargs) + elapsed_ms = int((time.time() - start) * 1000) + logger.info("HTTP %s %s -> %s (%sms)", method, url, resp.status_code, elapsed_ms) + resp.raise_for_status() + return resp + except Exception as e: # noqa: BLE001 (framework-wide) + last_exc = e + logger.warning("HTTP failed (%s %s) attempt=%s err=%r", method, url, attempt + 1, e) + if attempt < self.retries: + time.sleep(self.retry_backoff_s * (2**attempt)) + continue + raise + assert last_exc is not None + raise last_exc + + def get_json(self, path: str, **kwargs: Any) -> Any: + return self.request("GET", path, **kwargs).json() + + def post_json(self, path: str, json: Any = None, **kwargs: Any) -> Any: + return self.request("POST", path, json=json, **kwargs).json() + + diff --git a/app/jobs/__init__.py b/app/jobs/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/app/jobs/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/app/jobs/__pycache__/__init__.cpython-312.pyc b/app/jobs/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15835a06e8b7c884513e9e498f13ecb0ac7dff0b GIT binary patch literal 117 zcmX@j%ge<81S|BSGg*N2V-N=&d#&sAz~U2vHJ32#;UjhQwVH5)-DwxB?vAxbob#7`cnD-#Pbw=YHq(er;$-Ay~ti z-aOABv`d4d#YY6!x`9k19qHIa7M3xloG}@Tm063ExiDr;-V$U1BL>|skZHSs+V_cS7Lm&tNXg2R&%aLRL!#t?}4MkDYnZlX^}j_xE8Km zfp{7jNX9ynnS0<%W_3p9bk^W?t`l|*K^Js>gji7b!lPMH)W?oaYzW)8qiOS4m<;A$zbN5bmfAdK!at)0b-f>lw zqbjlbVLtdg7Cf8Wn;nVudS3IOT6tqMc=<6H8x59b%4v*@Vf@xTW?HT$FLL4HoKIa-{pD#qc z`ia=SOH0ED|1_zELI+&ucI05ZnL^2DNzopti-((WJayQ63dl zGu}xkitcENLQc?2gZcIvi-gmMltmS&I1BE;Q(qAp08fNY%V*V`MmS*Mt-j!u3VFMq2s9Jk uc#g7vF8pL?)YCQ63LEtQxh(((EXH^TweO(|JLv2$p&qwSXulDBLhWCMoF@AK literal 0 HcmV?d00001 diff --git a/app/jobs/base.py b/app/jobs/base.py new file mode 100644 index 0000000..e987975 --- /dev/null +++ b/app/jobs/base.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any + + +class BaseJob(ABC): + """ + 插件 Job 基类:框架层只负责加载与调度。 + + - params: 来自 Job.public_cfg(明文 JSON) + - secrets: 来自 Job.secret_cfg 解密后的明文 dict(仅在内存中存在) + """ + + job_id: str | None = None + + @abstractmethod + def run(self, params: dict[str, Any], secrets: dict[str, Any]) -> dict[str, Any] | None: + raise NotImplementedError + + diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..922551a --- /dev/null +++ b/app/main.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import os + +from fastapi import FastAPI +from sqladmin import Admin + +from app.admin.routes import router as admin_router +from app.admin.views import JobAdmin, JobLogAdmin +from app.core.config import settings +from app.core.logging import setup_logging +from app.db.engine import engine +from app.db.schema import ensure_schema +from app.security.fernet import get_or_create_fernet_key + + +def _init_db() -> None: + ensure_schema(engine) + + +def _ensure_runtime() -> None: + # 确保 data 目录存在 + os.makedirs(settings.data_dir, exist_ok=True) + if settings.log_dir: + os.makedirs(settings.log_dir, exist_ok=True) + # 确保 Fernet key 准备好(或自动生成) + get_or_create_fernet_key(settings.fernet_key_path) + _init_db() + + +def create_app() -> FastAPI: + setup_logging() + _ensure_runtime() + + app = FastAPI(title=settings.app_name) + + app.include_router(admin_router) + + admin = Admin(app=app, engine=engine, templates_dir="app/admin/templates") + admin.add_view(JobAdmin) + admin.add_view(JobLogAdmin) + + @app.get("/health") + def health(): + return {"ok": True, "name": settings.app_name} + + return app + + +app = create_app() + + diff --git a/app/plugins/__init__.py b/app/plugins/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/app/plugins/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/app/plugins/__pycache__/__init__.cpython-312.pyc b/app/plugins/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..228883bc2dacb09c5fa64e007c1c14d1d41f1755 GIT binary patch literal 120 zcmX@j%ge<81e^4tGg*N2V-N=&dJmtkIamWj77{q763hm7(oC4 literal 0 HcmV?d00001 diff --git a/app/plugins/__pycache__/manager.cpython-312.pyc b/app/plugins/__pycache__/manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acad227c0ebe582df07561461824ca3a7d3a3564 GIT binary patch literal 2182 zcmb7F|8EpU6rb7M-feg9_FBMRp-sKEid>~f012j40}>P}^anwHIMB`Zc3bY;+dF6X zNLsJS37FIR}*K_!4`z>Qo$#XDd9#5##y8z9h)eNlNeK;Gr1%OGjH-) zAt|sm!EDI}k^#oIn8B==6fxqEA?Sgd!39jcA&}ofx_AlctqYvDvfxc9*{X+i>5`C? zbVZk8mi34pf;lwF#oO*vt2lOL&9ZWi=A?6$JqmKGt~pxD)NC6B;nZZ_fI0YvW*Z~9 zu~WbY#z}6#=iQ-qpP3oEwTkj$7=!nRZx4T!sUWgVnCV`PAa>TGzg+ z*f|p@i}lFXxsJshE0F^g@xW?OD(|cYd+JJOrEB-%&c&X})`KnWAn0}`BYfzk??F%p} z_-9|hPQ>?Ag~Y9T`L;I^i-`5bY~^q3p=*k)y6$Z~@d22?|b zc*vEHF=G#rQrd=E;%HXNa0Le{qYEqk0fZ*TZKVa1S;qy^MOS!2D;)e!E)SK>rbz|N zHbJYHIZanHxiOVplEgv#Jbd;=7)q!vbzeDKlLktM>(b`iQg2o2o$J3o@ZCU78l-FO z<^HlgTl}gxcZyj7WJ@2_m93R-0QRlSjm-SnrS_%0KMnslyp*VHJ62OhD&h#hw4D0p zl@&R5Q(D}+99-FPu&zWa+g@8Tm)@^*9 zJ_N%$k1@W3`tG8(JE->#irz!Le+CdImtR=H(Z4wkZ(2t%GfmrYJK|rZr}t?9 literal 0 HcmV?d00001 diff --git a/app/plugins/manager.py b/app/plugins/manager.py new file mode 100644 index 0000000..63f2e08 --- /dev/null +++ b/app/plugins/manager.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import importlib +from dataclasses import dataclass +from typing import Type + +from app.jobs.base import BaseJob + + +@dataclass(frozen=True) +class HandlerRef: + module: str + cls_name: str + + +def parse_handler_path(handler_path: str) -> HandlerRef: + """ + 支持两种格式: + - "pkg.mod:ClassName"(推荐) + - "pkg.mod.ClassName" + """ + if ":" in handler_path: + module, cls_name = handler_path.split(":", 1) + return HandlerRef(module=module, cls_name=cls_name) + if "." not in handler_path: + raise ValueError(f"Invalid handler_path: {handler_path}") + module, cls_name = handler_path.rsplit(".", 1) + return HandlerRef(module=module, cls_name=cls_name) + + +def load_job_class(handler_path: str) -> Type[BaseJob]: + ref = parse_handler_path(handler_path) + mod = importlib.import_module(ref.module) + cls = getattr(mod, ref.cls_name, None) + if cls is None: + raise ImportError(f"Class not found: {ref.module}.{ref.cls_name}") + if not isinstance(cls, type) or not issubclass(cls, BaseJob): + raise TypeError(f"Handler is not a BaseJob subclass: {handler_path}") + return cls + + +def instantiate(handler_path: str) -> BaseJob: + cls = load_job_class(handler_path) + return cls() + + diff --git a/app/security/__init__.py b/app/security/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/app/security/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/app/security/__pycache__/__init__.cpython-312.pyc b/app/security/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e2661855c462d4022388ffe336819d69b3c2705 GIT binary patch literal 121 zcmX@j%ge<81l#nYGg*N2V-N=&d9eO literal 0 HcmV?d00001 diff --git a/app/security/__pycache__/fernet.cpython-312.pyc b/app/security/__pycache__/fernet.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..261fe1548014c0f8af63ad45fc780e58b52c1cc8 GIT binary patch literal 3660 zcmcf^TWlN0arYj49bcjxNv-XOl3hrY^{5<0v0c}wAW}{WS8gMB{l^B`!I{#Vn#bs+Qwo7jyv0lY&1SV0O>a2Dlo9AgG^SuV~Q zBOm8s}>B55SV*cuR;&idXR|&bP$4SMe(@!1$Da z;)c<$1QidAfif3q`Ug`Wf~h3u^95bj(}lb?4s(7aUyg9pF{YBds#EVv`8VWjTA3_d zQ}aM}X{xTL^D|l!np%sqS@Py_1(P2%!S}f}OEyMjcq07xB-2lwOKWB{1jrNm<5&ScHkxiI#b)3@dFEJ=&f^4RL+pItrMJaYMWfEzt!JtrUx>%9y8 z3aX$vtOfBmSaBZfbvstDZ9Q03w^zo+c6z#H*r5-!#%eO8`)ne!v2Ee!(L7h-G69>` z7}qQ?)6~G(kWEXnn+%UF4WkL9H``p9BR13+?UnPqd;fV_8XPvX@3d_;t}$K*%kJa& zc7tL=jALHVnFoxPVGcdeD!3vj+?w^p&WroFj~!rzX+>0c`=plL+7IF48y*A89>Spr z4UU8MtMC)(1R6vdp5-u_LKvP~zn|l$&@4WPXS_B=58IlYL?i}x<^SUCBsT>fKd9Ff zYG|f+?$hg~1A6_>t!ZlV-}IZcEEo~TDMzN$3lNTx3gMF^lAs@`(l`_8266~qZ5ocffF1{${}dSb)!b(5 zK(%#X;l(YXWqEEx2!H8xzdifr?Atfqys>EVC>fT z6A}44wp)-lu<*ktU9k7MuVe2?uz~z~w+VTVeua4PurariPO}?n(l48LCT({fvq1%&qj~%AGt=_qkZV(t`pHP|Ip(C_~8*T zdffRiEJk~r55L1ROpnp`J#$gwA92sddc{X!4}*Kf*bDAQgFJ&@Xodb0|B12l;wOVH z2A>x%I^CaQM*Y+&K%azd>#d&Hne!pB*VD3Yg+qkwU~Px133LySLwaR*+PE|K5MkND zyk1W0Mv-1X^}(w#8e~tYX(QBA3-#8dzICC`Sn*VnWcL*I#5?Z;uwRqIN{kAWQwpc> z_k}ff0%(U79g1k{49T>f0_^i-+~&&teIr#KmPi}&CQ!sNJ_cnJ?xpzLWQ3<)tA3ER zWI9bfnhJ%4tQUwz-CBX@?335_#FC!sI}>qGA)^)YR8&g2qDCERK3Pyy!(!A46H+ef z)S;*h3w!W|=?oPVS(mLU#}M% z{d7}$tt!2?{<>0=)P?Al6nf|ErgW?-9fN8{>fDqfRVi|}t0tXZ7f%1XdziNk_xu$A zR>Npq8lpbr@A{Rg8@7IRfYoZ1!yQzwYxX%xvez7*s}HMEppvn)ut%1U8kGTL!=`yo zuR9#Fy~SVQeh$^kJa>be#q}(}kCW#>iR&F!Z5)PzkwDZ;re?xAtCPdsX7wD2xGb-M zvzXV&Fsvi&DpSwTTh2af9HMAd-qB${Cv$ne3OTm>kXQh zU?p=qv)Wo~iPWT?b)n~k}AihjM#GvuDu3VZ#cdnAM& zgxn*&{DY(3kuLs0mk4k~FtZL1sf{|oE+qolZoB}wjTOok`ML+l;Y!%R#{f^o>9Ve} zx}437=mD%}0p9Z~>7@QD@b`w{=j3&hA!^F{0b^#ciG7h_gVd8qq)M={nn)04I>W+7 zDy9y-T!jA@A^@5;8(t~Q5V?4@+;8SOGjJI`3c%k$f3iSS=<=!bjFIQfw9ew8;alU& zBFqWo6zdwH&UHRlP)b?#0{IaT*oD%*3mupc!x;DPxH$aWuE&W_84~{mf(_ie literal 0 HcmV?d00001 diff --git a/app/security/fernet.py b/app/security/fernet.py new file mode 100644 index 0000000..dcd6e90 --- /dev/null +++ b/app/security/fernet.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import json +import os +from typing import Any + +from cryptography.fernet import Fernet, InvalidToken + +from app.core.config import settings + + +def _ensure_parent_dir(path: str) -> None: + parent = os.path.dirname(path) + if parent: + os.makedirs(parent, exist_ok=True) + + +def get_or_create_fernet_key(path: str | None = None) -> bytes: + key_path = path or settings.fernet_key_path + _ensure_parent_dir(key_path) + + if os.path.exists(key_path): + with open(key_path, "rb") as f: + return f.read().strip() + + key = Fernet.generate_key() + # best-effort set 0o600 (not always supported on some FS) + try: + flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL + fd = os.open(key_path, flags, 0o600) + with os.fdopen(fd, "wb") as f: + f.write(key) + f.write(b"\n") + except FileExistsError: + # race: another process wrote it + with open(key_path, "rb") as f: + return f.read().strip() + except OSError: + with open(key_path, "wb") as f: + f.write(key) + f.write(b"\n") + return key + + +def _fernet() -> Fernet: + return Fernet(get_or_create_fernet_key()) + + +def encrypt_json(obj: dict[str, Any]) -> str: + data = json.dumps(obj, ensure_ascii=False, separators=(",", ":"), sort_keys=True).encode("utf-8") + return _fernet().encrypt(data).decode("utf-8") + + +def decrypt_json(token: str) -> dict[str, Any]: + if not token: + return {} + try: + raw = _fernet().decrypt(token.encode("utf-8")) + except InvalidToken as e: + raise ValueError("Invalid secret_cfg token (Fernet)") from e + return json.loads(raw.decode("utf-8")) + + diff --git a/app/tasks/__init__.py b/app/tasks/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/app/tasks/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/app/tasks/__pycache__/__init__.cpython-312.pyc b/app/tasks/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce65129f6a506d2d45b234eaafda994253b10619 GIT binary patch literal 118 zcmX@j%ge<81o!l!Gg*N2V-N=&dgjEsy$%s>_Z4a66Z literal 0 HcmV?d00001 diff --git a/app/tasks/__pycache__/celery_app.cpython-312.pyc b/app/tasks/__pycache__/celery_app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c11a3067e67d904e9df4af618ba10ecd0f9a4baa GIT binary patch literal 838 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 TqMoE~>Ja_V=PHlGFx)=@U&sN3 literal 0 HcmV?d00001 diff --git a/app/tasks/celery_app.py b/app/tasks/celery_app.py new file mode 100644 index 0000000..be7a578 --- /dev/null +++ b/app/tasks/celery_app.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from celery import Celery + +from app.core.config import settings + + +celery_app = Celery( + "connecthub", + broker=settings.redis_url, + backend=settings.redis_url, +) + +celery_app.conf.update( + task_serializer="json", + accept_content=["json"], + result_serializer="json", + enable_utc=False, + timezone="Asia/Shanghai", + # 明确包含 task 模块,避免 autodiscover 找不到(也避免导入导致循环依赖) + include=[ + "app.tasks.execute", + "app.tasks.dispatcher", + ], + beat_schedule={ + "connecthub-dispatcher-tick-every-minute": { + "task": "connecthub.dispatcher.tick", + "schedule": 60.0, + } + }, + worker_redirect_stdouts=False +) + + diff --git a/app/tasks/dispatcher.py b/app/tasks/dispatcher.py new file mode 100644 index 0000000..da8d6e0 --- /dev/null +++ b/app/tasks/dispatcher.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +import logging +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo + +from croniter import croniter + +from app.core.logging import setup_logging +from app.db import crud +from app.db.engine import get_session +from app.tasks.celery_app import celery_app +from app.tasks.execute import execute_job + + +logger = logging.getLogger("connecthub.tasks.dispatcher") + + +def _floor_to_minute(dt: datetime) -> datetime: + return dt.replace(second=0, microsecond=0) + + +@celery_app.task(name="connecthub.dispatcher.tick") +def tick() -> dict[str, int]: + """ + Beat 每分钟触发一次: + - 读取 enabled Jobs + - cron_expr 到点则触发 execute_job + - last_run_at 防止同一分钟重复触发 + """ + setup_logging() + + session = get_session() + tz = ZoneInfo("Asia/Shanghai") + now = datetime.now(tz) + now_min = _floor_to_minute(now) + triggered = 0 + + try: + for job in crud.list_enabled_jobs(session): + last = job.last_run_at + if last is not None: + # SQLite 通常存 naive datetime;按 Asia/Shanghai 解释 + if last.tzinfo is None: + last_min = _floor_to_minute(last.replace(tzinfo=tz)) + else: + last_min = _floor_to_minute(last.astimezone(tz)) + if last_min >= now_min: + continue + + # croniter 默认按传入 datetime 计算,这里用 Asia/Shanghai + base = now_min - timedelta(minutes=1) + itr = croniter(job.cron_expr, base) + nxt = itr.get_next(datetime) + if _floor_to_minute(nxt.replace(tzinfo=tz)) != now_min: + continue + + execute_job.delay(job_id=job.id) + crud.update_job_last_run_at(session, job.id, now_min.replace(tzinfo=None)) + triggered += 1 + + except Exception: # noqa: BLE001 + logger.exception("dispatcher.tick failed") + finally: + session.close() + + return {"triggered": triggered} + + + diff --git a/app/tasks/execute.py b/app/tasks/execute.py new file mode 100644 index 0000000..3517e78 --- /dev/null +++ b/app/tasks/execute.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import logging +import traceback as tb +from datetime import datetime +from typing import Any + +from app.core.log_capture import capture_logs +from app.core.logging import setup_logging +from app.db import crud +from app.db.engine import engine, get_session +from app.db.models import JobStatus +from app.db.schema import ensure_schema +from app.plugins.manager import instantiate +from app.security.fernet import decrypt_json +from app.tasks.celery_app import celery_app + + +logger = logging.getLogger("connecthub.tasks.execute") + + +@celery_app.task(bind=True, name="connecthub.execute_job") +def execute_job(self, job_id: str | None = None, snapshot_params: dict[str, Any] | None = None) -> dict[str, Any]: + """ + 通用执行入口: + - 传 job_id:从 DB 读取 Job 定义 + - 传 snapshot_params:按快照重跑(用于 Admin 一键重试) + """ + setup_logging() + + # 确保 schema 已升级(即使 worker 先启动也不会写库失败) + try: + ensure_schema(engine) + except Exception: + # schema upgrade 失败不能影响执行(最多导致 run_log 无法写入) + pass + + started_at = datetime.utcnow() + session = get_session() + status = JobStatus.SUCCESS + message = "" + traceback = "" + result: dict[str, Any] = {} + run_log_text = "" + + try: + with capture_logs(max_bytes=200_000) as get_run_log: + try: + if snapshot_params: + job_id = snapshot_params["job_id"] + handler_path = snapshot_params["handler_path"] + public_cfg = snapshot_params.get("public_cfg", {}) or {} + secret_token = snapshot_params.get("secret_cfg", "") or "" + else: + if not job_id: + raise ValueError("job_id or snapshot_params is required") + job = crud.get_job(session, job_id) + if not job: + raise ValueError(f"Job not found: {job_id}") + handler_path = job.handler_path + public_cfg = job.public_cfg or {} + secret_token = job.secret_cfg or "" + + secrets = decrypt_json(secret_token) + job_instance = instantiate(handler_path) + out = job_instance.run(params=public_cfg, secrets=secrets) + if isinstance(out, dict): + result = out + message = "OK" + + except Exception as e: # noqa: BLE001 (framework-wide) + # 如果是 Celery retry 触发,框架可在此处扩展为自动 retry;此版本先记录失败信息 + status = JobStatus.FAILURE + message = repr(e) + traceback = tb.format_exc() + logger.exception("execute_job failed job_id=%s", job_id) + finally: + try: + run_log_text = get_run_log() or "" + except Exception: + run_log_text = "" + finally: + finished_at = datetime.utcnow() + snapshot = snapshot_params or { + "job_id": job_id, + "handler_path": handler_path if "handler_path" in locals() else "", + "public_cfg": public_cfg if "public_cfg" in locals() else {}, + "secret_cfg": secret_token if "secret_token" in locals() else "", + "meta": { + "trigger": "celery", + "celery_task_id": getattr(self.request, "id", "") or "", + "started_at": started_at.isoformat(), + }, + } + crud.create_job_log( + session, + job_id=str(job_id or ""), + status=status, + snapshot_params=snapshot, + message=message, + traceback=traceback, + run_log=run_log_text, + celery_task_id=getattr(self.request, "id", "") or "", + attempt=int(getattr(self.request, "retries", 0) or 0), + started_at=started_at, + finished_at=finished_at, + ) + session.close() + + return {"status": status.value, "job_id": job_id, "result": result, "message": message} + + diff --git a/connecthub.sh b/connecthub.sh new file mode 100755 index 0000000..8923418 --- /dev/null +++ b/connecthub.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT_DIR" + +COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" + +require_env() { + if [[ ! -f ".env" ]]; then + cat <<'EOF' +缺少 .env 文件。 +请在仓库根目录创建 .env(参考 env.example),例如: + cp env.example .env +然后按需修改其中变量。 +EOF + exit 1 + fi +} + +usage() { + cat <&2 + exit 2 + fi + tail="$2" + shift 2 + ;; + --tail=*) + tail="${1#*=}" + shift + ;; + -h|--help|help) + log_usage + exit 0 + ;; + *) + if [[ -z "$service" && "${1:0:1}" != "-" ]]; then + service="$1" + shift + else + echo "未知参数: $1" >&2 + echo + log_usage + exit 2 + fi + ;; + esac + done + + args=(docker compose -f "$COMPOSE_FILE" logs) + if [[ "$follow" = "1" ]]; then + args+=(--follow) + fi + if [[ -n "$tail" ]]; then + args+=(--tail "$tail") + fi + if [[ -n "$service" ]]; then + args+=("$service") + fi + "${args[@]}" + ;; + -h|--help|help|"") + usage + ;; + *) + echo "未知命令: $cmd" >&2 + echo + usage + exit 2 + ;; +esac + + diff --git a/data/connecthub.db b/data/connecthub.db new file mode 100644 index 0000000000000000000000000000000000000000..96b09c173402fc627f7fdeb320807228a25a1b19 GIT binary patch literal 73728 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", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 03:44:18 ERROR sqladmin.application No module named 'app.integrations.base_client' +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqladmin/application.py", line 545, in create + obj = await model_view.insert_model(request, form_data_dict) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/models.py", line 1069, in insert_model + return await Query(self).insert(data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 228, in insert + return await anyio.to_thread.run_sync(self._insert_sync, data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 61, in run_sync + return await get_async_backend().run_sync_in_worker_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread + return await future + ^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 986, in run + result = context.run(func, *args) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 196, in _insert_sync + anyio.from_thread.run( + File "/usr/local/lib/python3.11/site-packages/anyio/from_thread.py", line 90, in run + return token.backend_class.run_async_from_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2572, in run_async_from_thread + return f.result() + ^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 456, in result + return self.__get_result() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result + raise self._exception + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2553, in task_wrapper + return await func(*args) + ^^^^^^^^^^^^^^^^^ + File "/app/app/admin/views.py", line 60, in on_model_change + load_job_class(str(handler_path).strip()) + File "/app/app/plugins/manager.py", line 33, in load_job_class + mod = importlib.import_module(ref.module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 03:44:32 ERROR sqladmin.application No module named 'app.integrations.base_client' +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqladmin/application.py", line 545, in create + obj = await model_view.insert_model(request, form_data_dict) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/models.py", line 1069, in insert_model + return await Query(self).insert(data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 228, in insert + return await anyio.to_thread.run_sync(self._insert_sync, data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 61, in run_sync + return await get_async_backend().run_sync_in_worker_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread + return await future + ^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 986, in run + result = context.run(func, *args) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 196, in _insert_sync + anyio.from_thread.run( + File "/usr/local/lib/python3.11/site-packages/anyio/from_thread.py", line 90, in run + return token.backend_class.run_async_from_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2572, in run_async_from_thread + return f.result() + ^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 456, in result + return self.__get_result() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result + raise self._exception + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2553, in task_wrapper + return await func(*args) + ^^^^^^^^^^^^^^^^^ + File "/app/app/admin/views.py", line 60, in on_model_change + load_job_class(str(handler_path).strip()) + File "/app/app/plugins/manager.py", line 33, in load_job_class + mod = importlib.import_module(ref.module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 03:45:45 ERROR sqladmin.application No module named 'app.integrations.base_client' +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqladmin/application.py", line 545, in create + obj = await model_view.insert_model(request, form_data_dict) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/models.py", line 1069, in insert_model + return await Query(self).insert(data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 228, in insert + return await anyio.to_thread.run_sync(self._insert_sync, data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 61, in run_sync + return await get_async_backend().run_sync_in_worker_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread + return await future + ^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 986, in run + result = context.run(func, *args) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 196, in _insert_sync + anyio.from_thread.run( + File "/usr/local/lib/python3.11/site-packages/anyio/from_thread.py", line 90, in run + return token.backend_class.run_async_from_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2572, in run_async_from_thread + return f.result() + ^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 456, in result + return self.__get_result() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result + raise self._exception + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2553, in task_wrapper + return await func(*args) + ^^^^^^^^^^^^^^^^^ + File "/app/app/admin/views.py", line 60, in on_model_change + load_job_class(str(handler_path).strip()) + File "/app/app/plugins/manager.py", line 33, in load_job_class + mod = importlib.import_module(ref.module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 03:45:47 ERROR sqladmin.application No module named 'app.integrations.base_client' +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqladmin/application.py", line 545, in create + obj = await model_view.insert_model(request, form_data_dict) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/models.py", line 1069, in insert_model + return await Query(self).insert(data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 228, in insert + return await anyio.to_thread.run_sync(self._insert_sync, data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 61, in run_sync + return await get_async_backend().run_sync_in_worker_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread + return await future + ^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 986, in run + result = context.run(func, *args) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 196, in _insert_sync + anyio.from_thread.run( + File "/usr/local/lib/python3.11/site-packages/anyio/from_thread.py", line 90, in run + return token.backend_class.run_async_from_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2572, in run_async_from_thread + return f.result() + ^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 456, in result + return self.__get_result() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result + raise self._exception + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2553, in task_wrapper + return await func(*args) + ^^^^^^^^^^^^^^^^^ + File "/app/app/admin/views.py", line 60, in on_model_change + load_job_class(str(handler_path).strip()) + File "/app/app/plugins/manager.py", line 33, in load_job_class + mod = importlib.import_module(ref.module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 03:45:49 ERROR sqladmin.application No module named 'app.integrations.base_client' +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqladmin/application.py", line 545, in create + obj = await model_view.insert_model(request, form_data_dict) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/models.py", line 1069, in insert_model + return await Query(self).insert(data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 228, in insert + return await anyio.to_thread.run_sync(self._insert_sync, data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 61, in run_sync + return await get_async_backend().run_sync_in_worker_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread + return await future + ^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 986, in run + result = context.run(func, *args) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 196, in _insert_sync + anyio.from_thread.run( + File "/usr/local/lib/python3.11/site-packages/anyio/from_thread.py", line 90, in run + return token.backend_class.run_async_from_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2572, in run_async_from_thread + return f.result() + ^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 456, in result + return self.__get_result() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result + raise self._exception + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2553, in task_wrapper + return await func(*args) + ^^^^^^^^^^^^^^^^^ + File "/app/app/admin/views.py", line 60, in on_model_change + load_job_class(str(handler_path).strip()) + File "/app/app/plugins/manager.py", line 33, in load_job_class + mod = importlib.import_module(ref.module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 03:46:28 ERROR sqladmin.application No module named 'app.integrations.base_client' +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqladmin/application.py", line 545, in create + obj = await model_view.insert_model(request, form_data_dict) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/models.py", line 1069, in insert_model + return await Query(self).insert(data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 228, in insert + return await anyio.to_thread.run_sync(self._insert_sync, data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 61, in run_sync + return await get_async_backend().run_sync_in_worker_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread + return await future + ^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 986, in run + result = context.run(func, *args) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 196, in _insert_sync + anyio.from_thread.run( + File "/usr/local/lib/python3.11/site-packages/anyio/from_thread.py", line 90, in run + return token.backend_class.run_async_from_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2572, in run_async_from_thread + return f.result() + ^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 456, in result + return self.__get_result() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result + raise self._exception + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2553, in task_wrapper + return await func(*args) + ^^^^^^^^^^^^^^^^^ + File "/app/app/admin/views.py", line 60, in on_model_change + load_job_class(str(handler_path).strip()) + File "/app/app/plugins/manager.py", line 33, in load_job_class + mod = importlib.import_module(ref.module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 03:46:30 ERROR sqladmin.application No module named 'app.integrations.base_client' +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqladmin/application.py", line 545, in create + obj = await model_view.insert_model(request, form_data_dict) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/models.py", line 1069, in insert_model + return await Query(self).insert(data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 228, in insert + return await anyio.to_thread.run_sync(self._insert_sync, data, request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 61, in run_sync + return await get_async_backend().run_sync_in_worker_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread + return await future + ^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 986, in run + result = context.run(func, *args) + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqladmin/_queries.py", line 196, in _insert_sync + anyio.from_thread.run( + File "/usr/local/lib/python3.11/site-packages/anyio/from_thread.py", line 90, in run + return token.backend_class.run_async_from_thread( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2572, in run_async_from_thread + return f.result() + ^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 456, in result + return self.__get_result() + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result + raise self._exception + File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2553, in task_wrapper + return await func(*args) + ^^^^^^^^^^^^^^^^^ + File "/app/app/admin/views.py", line 60, in on_model_change + load_job_class(str(handler_path).strip()) + File "/app/app/plugins/manager.py", line 33, in load_job_class + mod = importlib.import_module(ref.module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1204, in _gcd_import + File "", line 1176, in _find_and_load + File "", line 1147, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/app/extensions/example/job.py", line 7, in + from extensions.example.client import ExampleClient + File "/app/extensions/example/client.py", line 3, in + from app.integrations.base import BaseClient + File "/app/app/integrations/__init__.py", line 5, in + from app.integrations.base_client import BaseClient +ModuleNotFoundError: No module named 'app.integrations.base_client' +2026-01-05 04:04:28 WARNING celery.redirected 2026-01-05 04:04:28 INFO celery.app.trace Task connecthub.dispatcher.tick[9bc692b9-408d-408d-9b78-f4c8fd3bf910] succeeded in 0.0407933510005023s: {'triggered': 1} +2026-01-05 04:04:28 INFO celery.app.trace Task connecthub.dispatcher.tick[9bc692b9-408d-408d-9b78-f4c8fd3bf910] succeeded in 0.0407933510005023s: {'triggered': 1} +2026-01-05 04:04:28 WARNING celery.redirected 2026-01-05 04:04:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:04:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:04:28 WARNING celery.redirected 2026-01-05 04:04:28 INFO celery.app.trace Task connecthub.execute_job[a935d18c-a886-41b6-96ed-2b47f332c024] succeeded in 0.12210913499984599s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:04:28 INFO celery.app.trace Task connecthub.execute_job[a935d18c-a886-41b6-96ed-2b47f332c024] succeeded in 0.12210913499984599s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:05:28 WARNING celery.redirected 2026-01-05 04:05:28 INFO celery.app.trace Task connecthub.dispatcher.tick[3dce6292-6dc0-4542-90b5-6714164b0903] succeeded in 0.0315388979997806s: {'triggered': 1} +2026-01-05 04:05:28 INFO celery.app.trace Task connecthub.dispatcher.tick[3dce6292-6dc0-4542-90b5-6714164b0903] succeeded in 0.0315388979997806s: {'triggered': 1} +2026-01-05 04:05:28 WARNING celery.redirected 2026-01-05 04:05:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:05:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:05:28 WARNING celery.redirected 2026-01-05 04:05:28 INFO celery.app.trace Task connecthub.execute_job[e4cd35f4-a5c8-4f09-8c4c-febb2fe9070f] succeeded in 0.033638812999924994s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:05:28 INFO celery.app.trace Task connecthub.execute_job[e4cd35f4-a5c8-4f09-8c4c-febb2fe9070f] succeeded in 0.033638812999924994s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:06:28 WARNING celery.redirected 2026-01-05 04:06:28 INFO celery.app.trace Task connecthub.dispatcher.tick[ce923b69-80a1-465f-9f55-f5931cd6b95d] succeeded in 0.013310617000570346s: {'triggered': 1} +2026-01-05 04:06:28 INFO celery.app.trace Task connecthub.dispatcher.tick[ce923b69-80a1-465f-9f55-f5931cd6b95d] succeeded in 0.013310617000570346s: {'triggered': 1} +2026-01-05 04:06:28 WARNING celery.redirected 2026-01-05 04:06:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:06:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:06:28 WARNING celery.redirected 2026-01-05 04:06:28 INFO celery.app.trace Task connecthub.execute_job[98de0f60-76aa-4465-8d1a-89d3bdd47669] succeeded in 0.024751318000198808s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:06:28 INFO celery.app.trace Task connecthub.execute_job[98de0f60-76aa-4465-8d1a-89d3bdd47669] succeeded in 0.024751318000198808s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:07:28 WARNING celery.redirected 2026-01-05 04:07:28 INFO celery.app.trace Task connecthub.dispatcher.tick[39fcfaeb-9b76-487f-b913-a5fbc64d5f72] succeeded in 0.01909061300011672s: {'triggered': 1} +2026-01-05 04:07:28 INFO celery.app.trace Task connecthub.dispatcher.tick[39fcfaeb-9b76-487f-b913-a5fbc64d5f72] succeeded in 0.01909061300011672s: {'triggered': 1} +2026-01-05 04:07:28 WARNING celery.redirected 2026-01-05 04:07:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:07:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:07:28 WARNING celery.redirected 2026-01-05 04:07:28 INFO celery.app.trace Task connecthub.execute_job[8660dfdd-cea6-4d39-8113-bab1948b4d14] succeeded in 0.02584623399980046s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:07:28 INFO celery.app.trace Task connecthub.execute_job[8660dfdd-cea6-4d39-8113-bab1948b4d14] succeeded in 0.02584623399980046s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:08:28 WARNING celery.redirected 2026-01-05 04:08:28 INFO celery.app.trace Task connecthub.dispatcher.tick[04377e32-ddf9-4fc3-96ae-b1501be248a1] succeeded in 0.008148328000061156s: {'triggered': 1} +2026-01-05 04:08:28 INFO celery.app.trace Task connecthub.dispatcher.tick[04377e32-ddf9-4fc3-96ae-b1501be248a1] succeeded in 0.008148328000061156s: {'triggered': 1} +2026-01-05 04:08:28 WARNING celery.redirected 2026-01-05 04:08:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:08:28 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:08:28 WARNING celery.redirected 2026-01-05 04:08:28 INFO celery.app.trace Task connecthub.execute_job[911b3014-5025-417d-87d1-16f29fd04adc] succeeded in 0.01953678000063519s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:08:28 INFO celery.app.trace Task connecthub.execute_job[911b3014-5025-417d-87d1-16f29fd04adc] succeeded in 0.01953678000063519s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:09:52 WARNING celery.redirected 2026-01-05 04:09:52 INFO celery.app.trace Task connecthub.dispatcher.tick[979e1130-624f-46a0-8527-90a545d94d8d] succeeded in 0.022409070000321663s: {'triggered': 1} +2026-01-05 04:09:52 INFO celery.app.trace Task connecthub.dispatcher.tick[979e1130-624f-46a0-8527-90a545d94d8d] succeeded in 0.022409070000321663s: {'triggered': 1} +2026-01-05 04:09:52 WARNING celery.redirected 2026-01-05 04:09:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:09:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:09:52 WARNING celery.redirected 2026-01-05 04:09:52 INFO celery.app.trace Task connecthub.execute_job[a8643351-b8b3-4731-a0c9-b37e6e80d2c0] succeeded in 0.06366400299975794s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:09:52 INFO celery.app.trace Task connecthub.execute_job[a8643351-b8b3-4731-a0c9-b37e6e80d2c0] succeeded in 0.06366400299975794s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:10:52 WARNING celery.redirected 2026-01-05 04:10:52 INFO celery.app.trace Task connecthub.dispatcher.tick[b9d03cc4-9cfd-42cc-a24a-7ee65223a7b5] succeeded in 0.021748904000560287s: {'triggered': 1} +2026-01-05 04:10:52 INFO celery.app.trace Task connecthub.dispatcher.tick[b9d03cc4-9cfd-42cc-a24a-7ee65223a7b5] succeeded in 0.021748904000560287s: {'triggered': 1} +2026-01-05 04:10:52 WARNING celery.redirected 2026-01-05 04:10:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:10:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:10:52 WARNING celery.redirected 2026-01-05 04:10:52 INFO celery.app.trace Task connecthub.execute_job[2fc70227-c028-47a9-928e-fbe207426125] succeeded in 0.030770231999667885s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:10:52 INFO celery.app.trace Task connecthub.execute_job[2fc70227-c028-47a9-928e-fbe207426125] succeeded in 0.030770231999667885s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:11:52 WARNING celery.redirected 2026-01-05 04:11:52 INFO celery.app.trace Task connecthub.dispatcher.tick[de3cf52c-cae5-4c51-ab29-c4ee50bbecf9] succeeded in 0.024469609999869135s: {'triggered': 1} +2026-01-05 04:11:52 INFO celery.app.trace Task connecthub.dispatcher.tick[de3cf52c-cae5-4c51-ab29-c4ee50bbecf9] succeeded in 0.024469609999869135s: {'triggered': 1} +2026-01-05 04:11:52 WARNING celery.redirected 2026-01-05 04:11:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:11:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:11:52 WARNING celery.redirected 2026-01-05 04:11:52 INFO celery.app.trace Task connecthub.execute_job[c116ba10-ffd1-4b26-96fa-f4acd3bd6970] succeeded in 0.028366817000460287s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:11:52 INFO celery.app.trace Task connecthub.execute_job[c116ba10-ffd1-4b26-96fa-f4acd3bd6970] succeeded in 0.028366817000460287s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:12:52 WARNING celery.redirected 2026-01-05 04:12:52 INFO celery.app.trace Task connecthub.dispatcher.tick[b35d28d2-9091-40b0-868f-037eb604eba0] succeeded in 0.020914112999889767s: {'triggered': 1} +2026-01-05 04:12:52 INFO celery.app.trace Task connecthub.dispatcher.tick[b35d28d2-9091-40b0-868f-037eb604eba0] succeeded in 0.020914112999889767s: {'triggered': 1} +2026-01-05 04:12:52 WARNING celery.redirected 2026-01-05 04:12:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:12:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:12:52 WARNING celery.redirected 2026-01-05 04:12:52 INFO celery.app.trace Task connecthub.execute_job[d3f53b27-df98-4a3c-b301-cb9bb21c1ec5] succeeded in 0.023244652999892423s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:12:52 INFO celery.app.trace Task connecthub.execute_job[d3f53b27-df98-4a3c-b301-cb9bb21c1ec5] succeeded in 0.023244652999892423s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:13:52 WARNING celery.redirected 2026-01-05 04:13:52 INFO celery.app.trace Task connecthub.dispatcher.tick[4e920d4e-f8d0-4fc5-b993-48ded04e6358] succeeded in 0.013280492000376398s: {'triggered': 1} +2026-01-05 04:13:52 INFO celery.app.trace Task connecthub.dispatcher.tick[4e920d4e-f8d0-4fc5-b993-48ded04e6358] succeeded in 0.013280492000376398s: {'triggered': 1} +2026-01-05 04:13:52 WARNING celery.redirected 2026-01-05 04:13:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:13:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:13:52 WARNING celery.redirected 2026-01-05 04:13:52 INFO celery.app.trace Task connecthub.execute_job[048f0b9d-99b1-4521-aca1-5fd1f6b1c5da] succeeded in 0.02696423399993364s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:13:52 INFO celery.app.trace Task connecthub.execute_job[048f0b9d-99b1-4521-aca1-5fd1f6b1c5da] succeeded in 0.02696423399993364s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:10 WARNING celery.redirected 2026-01-05 04:14:10 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:10 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:10 WARNING celery.redirected 2026-01-05 04:14:10 INFO celery.app.trace Task connecthub.execute_job[54b32418-34f0-43b6-9177-94d0447424ad] succeeded in 0.13937025099949096s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:10 INFO celery.app.trace Task connecthub.execute_job[54b32418-34f0-43b6-9177-94d0447424ad] succeeded in 0.13937025099949096s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:25 WARNING celery.redirected 2026-01-05 04:14:25 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:25 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:25 WARNING celery.redirected 2026-01-05 04:14:25 INFO celery.app.trace Task connecthub.execute_job[872f03bb-3828-418f-ba3e-e5d1c8722967] succeeded in 0.02000432200020441s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:25 INFO celery.app.trace Task connecthub.execute_job[872f03bb-3828-418f-ba3e-e5d1c8722967] succeeded in 0.02000432200020441s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:36 WARNING celery.redirected 2026-01-05 04:14:36 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:36 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:36 WARNING celery.redirected 2026-01-05 04:14:36 INFO celery.app.trace Task connecthub.execute_job[4ff44d2e-2a1d-4883-9b00-f3abe7c47e1e] succeeded in 0.01975427999968815s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:36 INFO celery.app.trace Task connecthub.execute_job[4ff44d2e-2a1d-4883-9b00-f3abe7c47e1e] succeeded in 0.01975427999968815s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:52 WARNING celery.redirected 2026-01-05 04:14:52 INFO celery.app.trace Task connecthub.dispatcher.tick[3c3f00c6-c41c-4f21-b5e2-41836b133e71] succeeded in 0.01676345899977605s: {'triggered': 1} +2026-01-05 04:14:52 INFO celery.app.trace Task connecthub.dispatcher.tick[3c3f00c6-c41c-4f21-b5e2-41836b133e71] succeeded in 0.01676345899977605s: {'triggered': 1} +2026-01-05 04:14:52 WARNING celery.redirected 2026-01-05 04:14:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:14:52 WARNING celery.redirected 2026-01-05 04:14:52 INFO celery.app.trace Task connecthub.execute_job[1363d33d-d6a4-457c-9d2e-ec52fb141b6f] succeeded in 0.0199828750000961s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:14:52 INFO celery.app.trace Task connecthub.execute_job[1363d33d-d6a4-457c-9d2e-ec52fb141b6f] succeeded in 0.0199828750000961s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:15:52 WARNING celery.redirected 2026-01-05 04:15:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ea94b6c0-0c57-43a5-9e24-36009713b597] succeeded in 0.039750081999955s: {'triggered': 1} +2026-01-05 04:15:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ea94b6c0-0c57-43a5-9e24-36009713b597] succeeded in 0.039750081999955s: {'triggered': 1} +2026-01-05 04:15:52 WARNING celery.redirected 2026-01-05 04:15:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:15:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:15:52 WARNING celery.redirected 2026-01-05 04:15:52 INFO celery.app.trace Task connecthub.execute_job[6c71ecc2-735f-43d4-8c67-762ec088cd84] succeeded in 0.027154014999723586s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:15:52 INFO celery.app.trace Task connecthub.execute_job[6c71ecc2-735f-43d4-8c67-762ec088cd84] succeeded in 0.027154014999723586s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:16:52 WARNING celery.redirected 2026-01-05 04:16:52 INFO celery.app.trace Task connecthub.dispatcher.tick[d838ad8f-16ab-465a-8cef-a50eb6f684ac] succeeded in 0.010788003000016033s: {'triggered': 1} +2026-01-05 04:16:52 INFO celery.app.trace Task connecthub.dispatcher.tick[d838ad8f-16ab-465a-8cef-a50eb6f684ac] succeeded in 0.010788003000016033s: {'triggered': 1} +2026-01-05 04:16:52 WARNING celery.redirected 2026-01-05 04:16:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:16:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:16:52 WARNING celery.redirected 2026-01-05 04:16:52 INFO celery.app.trace Task connecthub.execute_job[e7096c78-a712-403f-82df-fdaea7af897e] succeeded in 0.02174886400007381s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:16:52 INFO celery.app.trace Task connecthub.execute_job[e7096c78-a712-403f-82df-fdaea7af897e] succeeded in 0.02174886400007381s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:17:52 WARNING celery.redirected 2026-01-05 04:17:52 INFO celery.app.trace Task connecthub.dispatcher.tick[18bf30ca-3980-4607-8bef-e69f39bdcb48] succeeded in 0.01799064599981648s: {'triggered': 1} +2026-01-05 04:17:52 INFO celery.app.trace Task connecthub.dispatcher.tick[18bf30ca-3980-4607-8bef-e69f39bdcb48] succeeded in 0.01799064599981648s: {'triggered': 1} +2026-01-05 04:17:52 WARNING celery.redirected 2026-01-05 04:17:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:17:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:17:52 WARNING celery.redirected 2026-01-05 04:17:52 INFO celery.app.trace Task connecthub.execute_job[c59c5f51-8e7d-4e86-b70e-22b2d30ab229] succeeded in 0.021925345999989077s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:17:52 INFO celery.app.trace Task connecthub.execute_job[c59c5f51-8e7d-4e86-b70e-22b2d30ab229] succeeded in 0.021925345999989077s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:18:52 WARNING celery.redirected 2026-01-05 04:18:52 INFO celery.app.trace Task connecthub.dispatcher.tick[2d1b5ff3-9939-4cb6-a807-8403c4052fea] succeeded in 0.013237076000223169s: {'triggered': 1} +2026-01-05 04:18:52 INFO celery.app.trace Task connecthub.dispatcher.tick[2d1b5ff3-9939-4cb6-a807-8403c4052fea] succeeded in 0.013237076000223169s: {'triggered': 1} +2026-01-05 04:18:52 WARNING celery.redirected 2026-01-05 04:18:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:18:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:18:52 WARNING celery.redirected 2026-01-05 04:18:52 INFO celery.app.trace Task connecthub.execute_job[cda83fcd-ee9f-4df1-b3fd-35e9a4494e56] succeeded in 0.026179608999882475s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:18:52 INFO celery.app.trace Task connecthub.execute_job[cda83fcd-ee9f-4df1-b3fd-35e9a4494e56] succeeded in 0.026179608999882475s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:19:52 WARNING celery.redirected 2026-01-05 04:19:52 INFO celery.app.trace Task connecthub.dispatcher.tick[00f813c1-b361-4697-acbb-1c5f9a455ef4] succeeded in 0.01128316199992696s: {'triggered': 1} +2026-01-05 04:19:52 INFO celery.app.trace Task connecthub.dispatcher.tick[00f813c1-b361-4697-acbb-1c5f9a455ef4] succeeded in 0.01128316199992696s: {'triggered': 1} +2026-01-05 04:19:52 WARNING celery.redirected 2026-01-05 04:19:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:19:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:19:52 WARNING celery.redirected 2026-01-05 04:19:52 INFO celery.app.trace Task connecthub.execute_job[a95a06f1-f624-4258-bb1a-14f3edf5fd23] succeeded in 0.024273901999549707s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:19:52 INFO celery.app.trace Task connecthub.execute_job[a95a06f1-f624-4258-bb1a-14f3edf5fd23] succeeded in 0.024273901999549707s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:20:52 WARNING celery.redirected 2026-01-05 04:20:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ad6b6f35-620d-4f15-885f-eed710c33afa] succeeded in 0.010243423000247276s: {'triggered': 1} +2026-01-05 04:20:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ad6b6f35-620d-4f15-885f-eed710c33afa] succeeded in 0.010243423000247276s: {'triggered': 1} +2026-01-05 04:20:52 WARNING celery.redirected 2026-01-05 04:20:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:20:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:20:52 WARNING celery.redirected 2026-01-05 04:20:52 INFO celery.app.trace Task connecthub.execute_job[05deae7a-df77-4dbe-aaa9-afb2c05ee3d8] succeeded in 0.018025465999926382s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:20:52 INFO celery.app.trace Task connecthub.execute_job[05deae7a-df77-4dbe-aaa9-afb2c05ee3d8] succeeded in 0.018025465999926382s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:21:52 WARNING celery.redirected 2026-01-05 04:21:52 INFO celery.app.trace Task connecthub.dispatcher.tick[90e7750e-071b-4521-b41e-8b9795bf58f9] succeeded in 0.021846335999725852s: {'triggered': 1} +2026-01-05 04:21:52 INFO celery.app.trace Task connecthub.dispatcher.tick[90e7750e-071b-4521-b41e-8b9795bf58f9] succeeded in 0.021846335999725852s: {'triggered': 1} +2026-01-05 04:21:52 WARNING celery.redirected 2026-01-05 04:21:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:21:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:21:52 WARNING celery.redirected 2026-01-05 04:21:52 INFO celery.app.trace Task connecthub.execute_job[42f323b6-ebdd-476e-908a-8facd47b94cc] succeeded in 0.030602228999669023s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:21:52 INFO celery.app.trace Task connecthub.execute_job[42f323b6-ebdd-476e-908a-8facd47b94cc] succeeded in 0.030602228999669023s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:22:52 WARNING celery.redirected 2026-01-05 04:22:52 INFO celery.app.trace Task connecthub.dispatcher.tick[14cd9189-733f-4b27-b2a0-07f8f943e537] succeeded in 0.028103550999730942s: {'triggered': 1} +2026-01-05 04:22:52 INFO celery.app.trace Task connecthub.dispatcher.tick[14cd9189-733f-4b27-b2a0-07f8f943e537] succeeded in 0.028103550999730942s: {'triggered': 1} +2026-01-05 04:22:52 WARNING celery.redirected 2026-01-05 04:22:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:22:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:22:52 WARNING celery.redirected 2026-01-05 04:22:52 INFO celery.app.trace Task connecthub.execute_job[7f5bb270-619c-46d6-a372-a740061f4c98] succeeded in 0.03812834600012138s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:22:52 INFO celery.app.trace Task connecthub.execute_job[7f5bb270-619c-46d6-a372-a740061f4c98] succeeded in 0.03812834600012138s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:23:52 WARNING celery.redirected 2026-01-05 04:23:52 INFO celery.app.trace Task connecthub.dispatcher.tick[19876782-ab7f-408a-92f9-e3ffb2500466] succeeded in 0.020140034000178275s: {'triggered': 1} +2026-01-05 04:23:52 INFO celery.app.trace Task connecthub.dispatcher.tick[19876782-ab7f-408a-92f9-e3ffb2500466] succeeded in 0.020140034000178275s: {'triggered': 1} +2026-01-05 04:23:52 WARNING celery.redirected 2026-01-05 04:23:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:23:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:23:52 WARNING celery.redirected 2026-01-05 04:23:52 INFO celery.app.trace Task connecthub.execute_job[7066324a-67f4-4697-8626-e71c18586633] succeeded in 0.026626016999216517s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:23:52 INFO celery.app.trace Task connecthub.execute_job[7066324a-67f4-4697-8626-e71c18586633] succeeded in 0.026626016999216517s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:24:52 WARNING celery.redirected 2026-01-05 04:24:52 INFO celery.app.trace Task connecthub.dispatcher.tick[e42a4486-48d4-4841-8035-d9cd341054f2] succeeded in 0.06983272399975249s: {'triggered': 1} +2026-01-05 04:24:52 INFO celery.app.trace Task connecthub.dispatcher.tick[e42a4486-48d4-4841-8035-d9cd341054f2] succeeded in 0.06983272399975249s: {'triggered': 1} +2026-01-05 04:24:52 WARNING celery.redirected 2026-01-05 04:24:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:24:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:24:52 WARNING celery.redirected 2026-01-05 04:24:52 INFO celery.app.trace Task connecthub.execute_job[11610950-599b-4d91-9c87-915122c6ad2d] succeeded in 0.1087242449993937s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:24:52 INFO celery.app.trace Task connecthub.execute_job[11610950-599b-4d91-9c87-915122c6ad2d] succeeded in 0.1087242449993937s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:25:52 WARNING celery.redirected 2026-01-05 04:25:52 INFO celery.app.trace Task connecthub.dispatcher.tick[90453c9f-f26f-4f1d-b766-1867b8a5c97f] succeeded in 0.028080609000426193s: {'triggered': 1} +2026-01-05 04:25:52 INFO celery.app.trace Task connecthub.dispatcher.tick[90453c9f-f26f-4f1d-b766-1867b8a5c97f] succeeded in 0.028080609000426193s: {'triggered': 1} +2026-01-05 04:25:52 WARNING celery.redirected 2026-01-05 04:25:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:25:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:25:52 WARNING celery.redirected 2026-01-05 04:25:52 INFO celery.app.trace Task connecthub.execute_job[f36fd7ea-12bd-4ce5-93b1-105708409a5e] succeeded in 0.024148027000592265s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:25:52 INFO celery.app.trace Task connecthub.execute_job[f36fd7ea-12bd-4ce5-93b1-105708409a5e] succeeded in 0.024148027000592265s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:26:52 WARNING celery.redirected 2026-01-05 04:26:52 INFO celery.app.trace Task connecthub.dispatcher.tick[1bfe4027-d3a1-4f14-9a5e-bd08d95f84a0] succeeded in 0.021505861999685294s: {'triggered': 1} +2026-01-05 04:26:52 INFO celery.app.trace Task connecthub.dispatcher.tick[1bfe4027-d3a1-4f14-9a5e-bd08d95f84a0] succeeded in 0.021505861999685294s: {'triggered': 1} +2026-01-05 04:26:52 WARNING celery.redirected 2026-01-05 04:26:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:26:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:26:52 WARNING celery.redirected 2026-01-05 04:26:52 INFO celery.app.trace Task connecthub.execute_job[a0cb31bd-78bd-4638-87f9-3b227d8597cc] succeeded in 0.039379184999233985s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:26:52 INFO celery.app.trace Task connecthub.execute_job[a0cb31bd-78bd-4638-87f9-3b227d8597cc] succeeded in 0.039379184999233985s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:27:52 WARNING celery.redirected 2026-01-05 04:27:52 INFO celery.app.trace Task connecthub.dispatcher.tick[906707f0-7349-4277-87c7-d55568ad86ca] succeeded in 0.011816539999927045s: {'triggered': 1} +2026-01-05 04:27:52 INFO celery.app.trace Task connecthub.dispatcher.tick[906707f0-7349-4277-87c7-d55568ad86ca] succeeded in 0.011816539999927045s: {'triggered': 1} +2026-01-05 04:27:52 WARNING celery.redirected 2026-01-05 04:27:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:27:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:27:52 WARNING celery.redirected 2026-01-05 04:27:52 INFO celery.app.trace Task connecthub.execute_job[f9398f7f-a666-4530-9c99-ca5021858e2f] succeeded in 0.01940625900078885s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:27:52 INFO celery.app.trace Task connecthub.execute_job[f9398f7f-a666-4530-9c99-ca5021858e2f] succeeded in 0.01940625900078885s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:28:52 WARNING celery.redirected 2026-01-05 04:28:52 INFO celery.app.trace Task connecthub.dispatcher.tick[8085bae0-61bf-4f59-bd29-a6957f734744] succeeded in 0.012278085000616556s: {'triggered': 1} +2026-01-05 04:28:52 INFO celery.app.trace Task connecthub.dispatcher.tick[8085bae0-61bf-4f59-bd29-a6957f734744] succeeded in 0.012278085000616556s: {'triggered': 1} +2026-01-05 04:28:52 WARNING celery.redirected 2026-01-05 04:28:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:28:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:28:52 WARNING celery.redirected 2026-01-05 04:28:52 INFO celery.app.trace Task connecthub.execute_job[8aea880e-5cc6-4764-9661-feb878277ac7] succeeded in 0.02511479699933261s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:28:52 INFO celery.app.trace Task connecthub.execute_job[8aea880e-5cc6-4764-9661-feb878277ac7] succeeded in 0.02511479699933261s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:29:52 WARNING celery.redirected 2026-01-05 04:29:52 INFO celery.app.trace Task connecthub.dispatcher.tick[d55f31e4-9583-4310-b33e-05e0ff6f8fc5] succeeded in 0.017971582999962266s: {'triggered': 1} +2026-01-05 04:29:52 INFO celery.app.trace Task connecthub.dispatcher.tick[d55f31e4-9583-4310-b33e-05e0ff6f8fc5] succeeded in 0.017971582999962266s: {'triggered': 1} +2026-01-05 04:29:52 WARNING celery.redirected 2026-01-05 04:29:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:29:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:29:52 WARNING celery.redirected 2026-01-05 04:29:52 INFO celery.app.trace Task connecthub.execute_job[5db185f8-a01d-4ced-b0d8-a2fa8ca00939] succeeded in 0.01592598499973974s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:29:52 INFO celery.app.trace Task connecthub.execute_job[5db185f8-a01d-4ced-b0d8-a2fa8ca00939] succeeded in 0.01592598499973974s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:30:52 WARNING celery.redirected 2026-01-05 04:30:52 INFO celery.app.trace Task connecthub.dispatcher.tick[03996f49-dba2-4bb9-8ede-90fa614ffbfe] succeeded in 0.010479280999788898s: {'triggered': 1} +2026-01-05 04:30:52 INFO celery.app.trace Task connecthub.dispatcher.tick[03996f49-dba2-4bb9-8ede-90fa614ffbfe] succeeded in 0.010479280999788898s: {'triggered': 1} +2026-01-05 04:30:52 WARNING celery.redirected 2026-01-05 04:30:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:30:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:30:52 WARNING celery.redirected 2026-01-05 04:30:52 INFO celery.app.trace Task connecthub.execute_job[b5be6b84-78aa-4700-b692-fe705feb0bdd] succeeded in 0.028096860999539786s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:30:52 INFO celery.app.trace Task connecthub.execute_job[b5be6b84-78aa-4700-b692-fe705feb0bdd] succeeded in 0.028096860999539786s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:31:52 WARNING celery.redirected 2026-01-05 04:31:52 INFO celery.app.trace Task connecthub.dispatcher.tick[5956c67b-cfed-40f3-9911-f75211554056] succeeded in 0.010633400000187976s: {'triggered': 1} +2026-01-05 04:31:52 INFO celery.app.trace Task connecthub.dispatcher.tick[5956c67b-cfed-40f3-9911-f75211554056] succeeded in 0.010633400000187976s: {'triggered': 1} +2026-01-05 04:31:52 WARNING celery.redirected 2026-01-05 04:31:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:31:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:31:52 WARNING celery.redirected 2026-01-05 04:31:52 INFO celery.app.trace Task connecthub.execute_job[67814a34-9bcc-47c2-a1c6-331650627725] succeeded in 0.027063096000347286s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:31:52 INFO celery.app.trace Task connecthub.execute_job[67814a34-9bcc-47c2-a1c6-331650627725] succeeded in 0.027063096000347286s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:32:52 WARNING celery.redirected 2026-01-05 04:32:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ed8dfe49-9fa7-464c-9174-b0f2106e0f0f] succeeded in 0.014456934000008914s: {'triggered': 1} +2026-01-05 04:32:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ed8dfe49-9fa7-464c-9174-b0f2106e0f0f] succeeded in 0.014456934000008914s: {'triggered': 1} +2026-01-05 04:32:52 WARNING celery.redirected 2026-01-05 04:32:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:32:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:32:52 WARNING celery.redirected 2026-01-05 04:32:52 INFO celery.app.trace Task connecthub.execute_job[6ccdfdad-6589-4065-a311-8aba994d283d] succeeded in 0.026617454999723122s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:32:52 INFO celery.app.trace Task connecthub.execute_job[6ccdfdad-6589-4065-a311-8aba994d283d] succeeded in 0.026617454999723122s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:33:52 WARNING celery.redirected 2026-01-05 04:33:52 INFO celery.app.trace Task connecthub.dispatcher.tick[b35b0a9e-5936-4327-979c-a76459c11b4d] succeeded in 0.018993278999914764s: {'triggered': 1} +2026-01-05 04:33:52 INFO celery.app.trace Task connecthub.dispatcher.tick[b35b0a9e-5936-4327-979c-a76459c11b4d] succeeded in 0.018993278999914764s: {'triggered': 1} +2026-01-05 04:33:52 WARNING celery.redirected 2026-01-05 04:33:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:33:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:33:52 WARNING celery.redirected 2026-01-05 04:33:52 INFO celery.app.trace Task connecthub.execute_job[1d2fe071-4163-4b6d-83f0-762ec473f70d] succeeded in 0.031460221000088495s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:33:52 INFO celery.app.trace Task connecthub.execute_job[1d2fe071-4163-4b6d-83f0-762ec473f70d] succeeded in 0.031460221000088495s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:34:52 WARNING celery.redirected 2026-01-05 04:34:52 INFO celery.app.trace Task connecthub.dispatcher.tick[4afb4dfc-585f-4d52-861c-31b3da185db9] succeeded in 0.011016321000170137s: {'triggered': 1} +2026-01-05 04:34:52 INFO celery.app.trace Task connecthub.dispatcher.tick[4afb4dfc-585f-4d52-861c-31b3da185db9] succeeded in 0.011016321000170137s: {'triggered': 1} +2026-01-05 04:34:52 WARNING celery.redirected 2026-01-05 04:34:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:34:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:34:52 WARNING celery.redirected 2026-01-05 04:34:52 INFO celery.app.trace Task connecthub.execute_job[f509d0e3-b106-4e8b-9228-60e98f0a1539] succeeded in 0.0250381309997465s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:34:52 INFO celery.app.trace Task connecthub.execute_job[f509d0e3-b106-4e8b-9228-60e98f0a1539] succeeded in 0.0250381309997465s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:35:52 WARNING celery.redirected 2026-01-05 04:35:52 INFO celery.app.trace Task connecthub.dispatcher.tick[73d382ed-cea8-4c7f-bf79-2188ee912208] succeeded in 0.027740482000808697s: {'triggered': 1} +2026-01-05 04:35:52 INFO celery.app.trace Task connecthub.dispatcher.tick[73d382ed-cea8-4c7f-bf79-2188ee912208] succeeded in 0.027740482000808697s: {'triggered': 1} +2026-01-05 04:35:52 WARNING celery.redirected 2026-01-05 04:35:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:35:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:35:52 WARNING celery.redirected 2026-01-05 04:35:52 INFO celery.app.trace Task connecthub.execute_job[6821f78b-2f55-46c1-a499-1bba055688ad] succeeded in 0.026060805000270193s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:35:52 INFO celery.app.trace Task connecthub.execute_job[6821f78b-2f55-46c1-a499-1bba055688ad] succeeded in 0.026060805000270193s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:36:52 WARNING celery.redirected 2026-01-05 04:36:52 INFO celery.app.trace Task connecthub.dispatcher.tick[3781ba5b-938f-489c-a57e-8c3d0ee0715c] succeeded in 0.02685642399956123s: {'triggered': 1} +2026-01-05 04:36:52 INFO celery.app.trace Task connecthub.dispatcher.tick[3781ba5b-938f-489c-a57e-8c3d0ee0715c] succeeded in 0.02685642399956123s: {'triggered': 1} +2026-01-05 04:36:52 WARNING celery.redirected 2026-01-05 04:36:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:36:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:36:52 WARNING celery.redirected 2026-01-05 04:36:52 INFO celery.app.trace Task connecthub.execute_job[80d7c5f7-fed2-49d5-946e-ceb39b440163] succeeded in 0.029760638000880135s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:36:52 INFO celery.app.trace Task connecthub.execute_job[80d7c5f7-fed2-49d5-946e-ceb39b440163] succeeded in 0.029760638000880135s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:37:52 WARNING celery.redirected 2026-01-05 04:37:52 INFO celery.app.trace Task connecthub.dispatcher.tick[bee5c829-b099-49ab-a46d-a9c4c2fe9408] succeeded in 0.023322675999224884s: {'triggered': 1} +2026-01-05 04:37:52 INFO celery.app.trace Task connecthub.dispatcher.tick[bee5c829-b099-49ab-a46d-a9c4c2fe9408] succeeded in 0.023322675999224884s: {'triggered': 1} +2026-01-05 04:37:52 WARNING celery.redirected 2026-01-05 04:37:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:37:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:37:52 WARNING celery.redirected 2026-01-05 04:37:52 INFO celery.app.trace Task connecthub.execute_job[58efb57a-7f96-49c9-beb7-e1dd708ce499] succeeded in 0.02516265300073428s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:37:52 INFO celery.app.trace Task connecthub.execute_job[58efb57a-7f96-49c9-beb7-e1dd708ce499] succeeded in 0.02516265300073428s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:38:52 WARNING celery.redirected 2026-01-05 04:38:52 INFO celery.app.trace Task connecthub.dispatcher.tick[16f7dfc9-3204-4c36-9ee5-bb3c6ddeb0dc] succeeded in 0.01482132800083491s: {'triggered': 1} +2026-01-05 04:38:52 INFO celery.app.trace Task connecthub.dispatcher.tick[16f7dfc9-3204-4c36-9ee5-bb3c6ddeb0dc] succeeded in 0.01482132800083491s: {'triggered': 1} +2026-01-05 04:38:52 WARNING celery.redirected 2026-01-05 04:38:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:38:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:38:52 WARNING celery.redirected 2026-01-05 04:38:52 INFO celery.app.trace Task connecthub.execute_job[ab0cf7a1-f828-4fe3-92c7-8bdfac19e33c] succeeded in 0.026130655998713337s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:38:52 INFO celery.app.trace Task connecthub.execute_job[ab0cf7a1-f828-4fe3-92c7-8bdfac19e33c] succeeded in 0.026130655998713337s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:39:52 WARNING celery.redirected 2026-01-05 04:39:52 INFO celery.app.trace Task connecthub.dispatcher.tick[e5a17e48-f1e8-4fd3-b76a-1bae0c478408] succeeded in 0.033079069000450545s: {'triggered': 1} +2026-01-05 04:39:52 INFO celery.app.trace Task connecthub.dispatcher.tick[e5a17e48-f1e8-4fd3-b76a-1bae0c478408] succeeded in 0.033079069000450545s: {'triggered': 1} +2026-01-05 04:39:52 WARNING celery.redirected 2026-01-05 04:39:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:39:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:39:52 WARNING celery.redirected 2026-01-05 04:39:52 INFO celery.app.trace Task connecthub.execute_job[52f791fa-42ce-4d59-a6b8-00ab2a3e30f1] succeeded in 0.039908691998789436s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:39:52 INFO celery.app.trace Task connecthub.execute_job[52f791fa-42ce-4d59-a6b8-00ab2a3e30f1] succeeded in 0.039908691998789436s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:40:52 WARNING celery.redirected 2026-01-05 04:40:52 INFO celery.app.trace Task connecthub.dispatcher.tick[532aa528-db60-42a6-a932-a7e00570785c] succeeded in 0.011276440000074217s: {'triggered': 1} +2026-01-05 04:40:52 INFO celery.app.trace Task connecthub.dispatcher.tick[532aa528-db60-42a6-a932-a7e00570785c] succeeded in 0.011276440000074217s: {'triggered': 1} +2026-01-05 04:40:52 WARNING celery.redirected 2026-01-05 04:40:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:40:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:40:52 WARNING celery.redirected 2026-01-05 04:40:52 INFO celery.app.trace Task connecthub.execute_job[0e54f62e-e468-48af-9ed0-c95cbba339ee] succeeded in 0.02018032300111372s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:40:52 INFO celery.app.trace Task connecthub.execute_job[0e54f62e-e468-48af-9ed0-c95cbba339ee] succeeded in 0.02018032300111372s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:41:52 WARNING celery.redirected 2026-01-05 04:41:52 INFO celery.app.trace Task connecthub.dispatcher.tick[14c2f4fa-f281-4e22-bc0f-0519084994d7] succeeded in 0.03138417900117929s: {'triggered': 1} +2026-01-05 04:41:52 INFO celery.app.trace Task connecthub.dispatcher.tick[14c2f4fa-f281-4e22-bc0f-0519084994d7] succeeded in 0.03138417900117929s: {'triggered': 1} +2026-01-05 04:41:52 WARNING celery.redirected 2026-01-05 04:41:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:41:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:41:52 WARNING celery.redirected 2026-01-05 04:41:52 INFO celery.app.trace Task connecthub.execute_job[eb26c574-0d81-4c37-a82d-e9a536e135de] succeeded in 0.05994634200033033s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:41:52 INFO celery.app.trace Task connecthub.execute_job[eb26c574-0d81-4c37-a82d-e9a536e135de] succeeded in 0.05994634200033033s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:42:52 WARNING celery.redirected 2026-01-05 04:42:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ec860011-210c-41de-898c-bb70e0bbae26] succeeded in 0.012299804999202024s: {'triggered': 1} +2026-01-05 04:42:52 INFO celery.app.trace Task connecthub.dispatcher.tick[ec860011-210c-41de-898c-bb70e0bbae26] succeeded in 0.012299804999202024s: {'triggered': 1} +2026-01-05 04:42:52 WARNING celery.redirected 2026-01-05 04:42:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:42:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:42:52 WARNING celery.redirected 2026-01-05 04:42:52 INFO celery.app.trace Task connecthub.execute_job[8a13d019-2463-4473-97bd-c7c1fd57ecb3] succeeded in 0.01719682700058911s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:42:52 INFO celery.app.trace Task connecthub.execute_job[8a13d019-2463-4473-97bd-c7c1fd57ecb3] succeeded in 0.01719682700058911s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:43:52 WARNING celery.redirected 2026-01-05 04:43:52 INFO celery.app.trace Task connecthub.dispatcher.tick[46c26e9d-fe4e-4d14-98ff-0d535c4e7c5c] succeeded in 0.025048448000234202s: {'triggered': 1} +2026-01-05 04:43:52 INFO celery.app.trace Task connecthub.dispatcher.tick[46c26e9d-fe4e-4d14-98ff-0d535c4e7c5c] succeeded in 0.025048448000234202s: {'triggered': 1} +2026-01-05 04:43:52 WARNING celery.redirected 2026-01-05 04:43:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:43:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:43:52 WARNING celery.redirected 2026-01-05 04:43:52 INFO celery.app.trace Task connecthub.execute_job[da8a24ce-6102-401e-beb4-992577d5d295] succeeded in 0.025968660000216914s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:43:52 INFO celery.app.trace Task connecthub.execute_job[da8a24ce-6102-401e-beb4-992577d5d295] succeeded in 0.025968660000216914s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:44:31 WARNING celery.redirected 2026-01-05 04:44:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:44:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:44:31 WARNING celery.redirected 2026-01-05 04:44:31 INFO celery.app.trace Task connecthub.execute_job[dac4f0b6-f21a-4144-a8d0-414e4e2888a5] succeeded in 0.21572023400040052s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:44:31 INFO celery.app.trace Task connecthub.execute_job[dac4f0b6-f21a-4144-a8d0-414e4e2888a5] succeeded in 0.21572023400040052s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:45:12 WARNING celery.redirected 2026-01-05 04:45:12 INFO celery.app.trace Task connecthub.dispatcher.tick[de336f5c-6a6a-4e50-8dce-ed61ed6d1b84] succeeded in 0.01479602600011276s: {'triggered': 1} +2026-01-05 04:45:12 INFO celery.app.trace Task connecthub.dispatcher.tick[de336f5c-6a6a-4e50-8dce-ed61ed6d1b84] succeeded in 0.01479602600011276s: {'triggered': 1} +2026-01-05 04:45:12 WARNING celery.redirected 2026-01-05 04:45:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:45:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:45:12 WARNING celery.redirected 2026-01-05 04:45:12 INFO celery.app.trace Task connecthub.execute_job[b05503f6-84a2-4783-b99b-8d7d7d6dd9f4] succeeded in 0.07361554400085879s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:45:12 INFO celery.app.trace Task connecthub.execute_job[b05503f6-84a2-4783-b99b-8d7d7d6dd9f4] succeeded in 0.07361554400085879s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:46:12 WARNING celery.redirected 2026-01-05 04:46:12 INFO celery.app.trace Task connecthub.dispatcher.tick[d40edb78-19fd-4db5-bd8d-b90097e524c8] succeeded in 0.018784460000460967s: {'triggered': 1} +2026-01-05 04:46:12 INFO celery.app.trace Task connecthub.dispatcher.tick[d40edb78-19fd-4db5-bd8d-b90097e524c8] succeeded in 0.018784460000460967s: {'triggered': 1} +2026-01-05 04:46:12 WARNING celery.redirected 2026-01-05 04:46:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:46:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:46:12 WARNING celery.redirected 2026-01-05 04:46:12 INFO celery.app.trace Task connecthub.execute_job[be0b710b-093a-4682-9f0c-62aeb393d2c0] succeeded in 0.022114642000815365s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:46:12 INFO celery.app.trace Task connecthub.execute_job[be0b710b-093a-4682-9f0c-62aeb393d2c0] succeeded in 0.022114642000815365s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:46:57 WARNING celery.redirected 2026-01-05 04:46:57 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:46:57 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:46:57 WARNING celery.redirected 2026-01-05 04:46:57 INFO celery.app.trace Task connecthub.execute_job[78f34184-d547-4309-a95c-adc700459de3] succeeded in 0.048954084999422776s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:46:57 INFO celery.app.trace Task connecthub.execute_job[78f34184-d547-4309-a95c-adc700459de3] succeeded in 0.048954084999422776s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:47:12 WARNING celery.redirected 2026-01-05 04:47:12 INFO celery.app.trace Task connecthub.dispatcher.tick[ff0e6ec9-5e8f-40df-b640-ea07bff16dc8] succeeded in 0.011512289998790948s: {'triggered': 1} +2026-01-05 04:47:12 INFO celery.app.trace Task connecthub.dispatcher.tick[ff0e6ec9-5e8f-40df-b640-ea07bff16dc8] succeeded in 0.011512289998790948s: {'triggered': 1} +2026-01-05 04:47:12 WARNING celery.redirected 2026-01-05 04:47:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:47:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:47:12 WARNING celery.redirected 2026-01-05 04:47:12 INFO celery.app.trace Task connecthub.execute_job[77e78528-0624-4598-a396-df36683b5b4f] succeeded in 0.02458914800081402s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:47:12 INFO celery.app.trace Task connecthub.execute_job[77e78528-0624-4598-a396-df36683b5b4f] succeeded in 0.02458914800081402s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:48:12 WARNING celery.redirected 2026-01-05 04:48:12 INFO celery.app.trace Task connecthub.dispatcher.tick[f1815610-425b-4d28-a9b0-114018240569] succeeded in 0.016908452000279794s: {'triggered': 1} +2026-01-05 04:48:12 INFO celery.app.trace Task connecthub.dispatcher.tick[f1815610-425b-4d28-a9b0-114018240569] succeeded in 0.016908452000279794s: {'triggered': 1} +2026-01-05 04:48:12 WARNING celery.redirected 2026-01-05 04:48:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:48:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:48:12 WARNING celery.redirected 2026-01-05 04:48:12 INFO celery.app.trace Task connecthub.execute_job[4dd5a721-e52a-4d5e-8586-f6d4bfc813ae] succeeded in 0.02303668800050218s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:48:12 INFO celery.app.trace Task connecthub.execute_job[4dd5a721-e52a-4d5e-8586-f6d4bfc813ae] succeeded in 0.02303668800050218s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:49:12 WARNING celery.redirected 2026-01-05 04:49:12 INFO celery.app.trace Task connecthub.dispatcher.tick[fa284605-4e78-4406-b295-281a9e084930] succeeded in 0.012760109999362612s: {'triggered': 1} +2026-01-05 04:49:12 INFO celery.app.trace Task connecthub.dispatcher.tick[fa284605-4e78-4406-b295-281a9e084930] succeeded in 0.012760109999362612s: {'triggered': 1} +2026-01-05 04:49:12 WARNING celery.redirected 2026-01-05 04:49:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:49:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:49:12 WARNING celery.redirected 2026-01-05 04:49:12 INFO celery.app.trace Task connecthub.execute_job[48c6f2e3-2bbe-484e-aa45-117c46df7349] succeeded in 0.02080004699928395s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:49:12 INFO celery.app.trace Task connecthub.execute_job[48c6f2e3-2bbe-484e-aa45-117c46df7349] succeeded in 0.02080004699928395s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:50:12 WARNING celery.redirected 2026-01-05 04:50:12 INFO celery.app.trace Task connecthub.dispatcher.tick[c18e5a95-b652-41be-805f-1a456df578fc] succeeded in 0.010403339001641143s: {'triggered': 1} +2026-01-05 04:50:12 INFO celery.app.trace Task connecthub.dispatcher.tick[c18e5a95-b652-41be-805f-1a456df578fc] succeeded in 0.010403339001641143s: {'triggered': 1} +2026-01-05 04:50:12 WARNING celery.redirected 2026-01-05 04:50:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:50:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:50:12 WARNING celery.redirected 2026-01-05 04:50:12 INFO celery.app.trace Task connecthub.execute_job[69e7c7d2-8000-4578-8465-e53dbb1c8c57] succeeded in 0.015631820999260526s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:50:12 INFO celery.app.trace Task connecthub.execute_job[69e7c7d2-8000-4578-8465-e53dbb1c8c57] succeeded in 0.015631820999260526s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:51:12 WARNING celery.redirected 2026-01-05 04:51:12 INFO celery.app.trace Task connecthub.dispatcher.tick[4afd800b-22a4-499f-b28a-351da5d70495] succeeded in 0.025631652999436483s: {'triggered': 1} +2026-01-05 04:51:12 INFO celery.app.trace Task connecthub.dispatcher.tick[4afd800b-22a4-499f-b28a-351da5d70495] succeeded in 0.025631652999436483s: {'triggered': 1} +2026-01-05 04:51:12 WARNING celery.redirected 2026-01-05 04:51:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:51:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:51:12 WARNING celery.redirected 2026-01-05 04:51:12 INFO celery.app.trace Task connecthub.execute_job[24a44feb-d0d5-4dfd-b2ff-e96b856c3061] succeeded in 0.028806775000703055s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:51:12 INFO celery.app.trace Task connecthub.execute_job[24a44feb-d0d5-4dfd-b2ff-e96b856c3061] succeeded in 0.028806775000703055s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:52:12 WARNING celery.redirected 2026-01-05 04:52:12 INFO celery.app.trace Task connecthub.dispatcher.tick[b90582bc-17c7-40be-85b7-6d9bf01d900a] succeeded in 0.022004341999490862s: {'triggered': 1} +2026-01-05 04:52:12 INFO celery.app.trace Task connecthub.dispatcher.tick[b90582bc-17c7-40be-85b7-6d9bf01d900a] succeeded in 0.022004341999490862s: {'triggered': 1} +2026-01-05 04:52:12 WARNING celery.redirected 2026-01-05 04:52:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:52:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:52:12 WARNING celery.redirected 2026-01-05 04:52:12 INFO celery.app.trace Task connecthub.execute_job[229faba7-e293-4843-b30d-14e90ac574f6] succeeded in 0.02439292300005036s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:52:12 INFO celery.app.trace Task connecthub.execute_job[229faba7-e293-4843-b30d-14e90ac574f6] succeeded in 0.02439292300005036s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:53:12 WARNING celery.redirected 2026-01-05 04:53:12 INFO celery.app.trace Task connecthub.dispatcher.tick[2ea064eb-1eb1-46cd-9902-7a0965975e3c] succeeded in 0.014864144000966917s: {'triggered': 1} +2026-01-05 04:53:12 INFO celery.app.trace Task connecthub.dispatcher.tick[2ea064eb-1eb1-46cd-9902-7a0965975e3c] succeeded in 0.014864144000966917s: {'triggered': 1} +2026-01-05 04:53:12 WARNING celery.redirected 2026-01-05 04:53:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:53:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:53:12 WARNING celery.redirected 2026-01-05 04:53:12 INFO celery.app.trace Task connecthub.execute_job[8e2fe03f-f0fa-47e6-9ef5-cfa81c7391dc] succeeded in 0.02118985200104362s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:53:12 INFO celery.app.trace Task connecthub.execute_job[8e2fe03f-f0fa-47e6-9ef5-cfa81c7391dc] succeeded in 0.02118985200104362s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:54:12 WARNING celery.redirected 2026-01-05 04:54:12 INFO celery.app.trace Task connecthub.dispatcher.tick[2427c89d-2ed8-497a-81fd-9c87f5956671] succeeded in 0.018913083998995717s: {'triggered': 1} +2026-01-05 04:54:12 INFO celery.app.trace Task connecthub.dispatcher.tick[2427c89d-2ed8-497a-81fd-9c87f5956671] succeeded in 0.018913083998995717s: {'triggered': 1} +2026-01-05 04:54:12 WARNING celery.redirected 2026-01-05 04:54:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:54:12 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:54:12 WARNING celery.redirected 2026-01-05 04:54:12 INFO celery.app.trace Task connecthub.execute_job[14b00b85-efc7-4413-b613-4c65a15c11b8] succeeded in 0.023627791000762954s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:54:12 INFO celery.app.trace Task connecthub.execute_job[14b00b85-efc7-4413-b613-4c65a15c11b8] succeeded in 0.023627791000762954s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:55:46 WARNING celery.redirected 2026-01-05 04:55:46 INFO celery.app.trace Task connecthub.dispatcher.tick[fc8df83a-21bc-427e-b4bf-47680dbf65a4] succeeded in 0.051510687999325455s: {'triggered': 1} +2026-01-05 04:55:46 INFO celery.app.trace Task connecthub.dispatcher.tick[fc8df83a-21bc-427e-b4bf-47680dbf65a4] succeeded in 0.051510687999325455s: {'triggered': 1} +2026-01-05 04:55:46 WARNING celery.redirected 2026-01-05 04:55:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:55:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:55:46 WARNING celery.redirected 2026-01-05 04:55:46 INFO celery.app.trace Task connecthub.execute_job[a7845eff-bc4d-4d5c-9270-1c9dde5c94bc] succeeded in 0.08874253400063026s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:55:46 INFO celery.app.trace Task connecthub.execute_job[a7845eff-bc4d-4d5c-9270-1c9dde5c94bc] succeeded in 0.08874253400063026s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:56:46 WARNING celery.redirected 2026-01-05 04:56:46 INFO celery.app.trace Task connecthub.dispatcher.tick[81bf98fd-d3a4-42f0-903d-e774739f1760] succeeded in 0.029670317999261897s: {'triggered': 1} +2026-01-05 04:56:46 INFO celery.app.trace Task connecthub.dispatcher.tick[81bf98fd-d3a4-42f0-903d-e774739f1760] succeeded in 0.029670317999261897s: {'triggered': 1} +2026-01-05 04:56:46 WARNING celery.redirected 2026-01-05 04:56:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:56:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:56:46 WARNING celery.redirected 2026-01-05 04:56:46 INFO celery.app.trace Task connecthub.execute_job[01d6f1fc-edd5-4028-8a96-f095b3d3879a] succeeded in 0.024213059001340298s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:56:46 INFO celery.app.trace Task connecthub.execute_job[01d6f1fc-edd5-4028-8a96-f095b3d3879a] succeeded in 0.024213059001340298s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:57:46 WARNING celery.redirected 2026-01-05 04:57:46 INFO celery.app.trace Task connecthub.dispatcher.tick[b4d94d0e-d5cd-49eb-aa1f-aed065a3624a] succeeded in 0.013022064000324463s: {'triggered': 1} +2026-01-05 04:57:46 INFO celery.app.trace Task connecthub.dispatcher.tick[b4d94d0e-d5cd-49eb-aa1f-aed065a3624a] succeeded in 0.013022064000324463s: {'triggered': 1} +2026-01-05 04:57:46 WARNING celery.redirected 2026-01-05 04:57:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:57:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:57:46 WARNING celery.redirected 2026-01-05 04:57:46 INFO celery.app.trace Task connecthub.execute_job[588046a4-1f16-4a32-899d-43554a3a313a] succeeded in 0.02219619100105774s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:57:46 INFO celery.app.trace Task connecthub.execute_job[588046a4-1f16-4a32-899d-43554a3a313a] succeeded in 0.02219619100105774s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:58:46 WARNING celery.redirected 2026-01-05 04:58:46 INFO celery.app.trace Task connecthub.dispatcher.tick[fc89a00f-34cf-48ea-bfb1-f7e6988390ee] succeeded in 0.02372628299963253s: {'triggered': 1} +2026-01-05 04:58:46 INFO celery.app.trace Task connecthub.dispatcher.tick[fc89a00f-34cf-48ea-bfb1-f7e6988390ee] succeeded in 0.02372628299963253s: {'triggered': 1} +2026-01-05 04:58:46 WARNING celery.redirected 2026-01-05 04:58:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:58:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:58:46 WARNING celery.redirected 2026-01-05 04:58:46 INFO celery.app.trace Task connecthub.execute_job[9b693ff9-a561-48d4-ad17-3addcaf5b93e] succeeded in 0.02154406400040898s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:58:46 INFO celery.app.trace Task connecthub.execute_job[9b693ff9-a561-48d4-ad17-3addcaf5b93e] succeeded in 0.02154406400040898s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:59:46 WARNING celery.redirected 2026-01-05 04:59:46 INFO celery.app.trace Task connecthub.dispatcher.tick[dae1a177-3221-4737-9176-d8f930e8fa18] succeeded in 0.02139074299884669s: {'triggered': 1} +2026-01-05 04:59:46 INFO celery.app.trace Task connecthub.dispatcher.tick[dae1a177-3221-4737-9176-d8f930e8fa18] succeeded in 0.02139074299884669s: {'triggered': 1} +2026-01-05 04:59:46 WARNING celery.redirected 2026-01-05 04:59:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:59:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 04:59:46 WARNING celery.redirected 2026-01-05 04:59:46 INFO celery.app.trace Task connecthub.execute_job[9b9204ce-85e1-49f8-97bf-a4f8e6b278c7] succeeded in 0.02846042000055604s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 04:59:46 INFO celery.app.trace Task connecthub.execute_job[9b9204ce-85e1-49f8-97bf-a4f8e6b278c7] succeeded in 0.02846042000055604s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:00:46 WARNING celery.redirected 2026-01-05 05:00:46 INFO celery.app.trace Task connecthub.dispatcher.tick[da9246bf-9284-40d9-a9af-14732ee087ef] succeeded in 0.01989368699832994s: {'triggered': 1} +2026-01-05 05:00:46 INFO celery.app.trace Task connecthub.dispatcher.tick[da9246bf-9284-40d9-a9af-14732ee087ef] succeeded in 0.01989368699832994s: {'triggered': 1} +2026-01-05 05:00:46 WARNING celery.redirected 2026-01-05 05:00:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:00:46 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:00:46 WARNING celery.redirected 2026-01-05 05:00:46 INFO celery.app.trace Task connecthub.execute_job[36c8fdb0-3517-4013-9619-f76dc7407154] succeeded in 0.040899174000514904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:00:46 INFO celery.app.trace Task connecthub.execute_job[36c8fdb0-3517-4013-9619-f76dc7407154] succeeded in 0.040899174000514904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:45:31 WARNING celery.redirected 2026-01-05 05:45:31 INFO celery.app.trace Task connecthub.dispatcher.tick[27ca4879-110a-4585-b2fd-b98a107e1397] succeeded in 0.2113479939998797s: {'triggered': 1} +2026-01-05 05:45:31 INFO celery.app.trace Task connecthub.dispatcher.tick[27ca4879-110a-4585-b2fd-b98a107e1397] succeeded in 0.2113479939998797s: {'triggered': 1} +2026-01-05 05:45:31 WARNING celery.redirected 2026-01-05 05:45:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:45:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:45:31 WARNING celery.redirected 2026-01-05 05:45:31 INFO celery.app.trace Task connecthub.execute_job[672f034d-be35-44ab-bbb0-8d224a649312] succeeded in 0.454256250999606s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:45:31 INFO celery.app.trace Task connecthub.execute_job[672f034d-be35-44ab-bbb0-8d224a649312] succeeded in 0.454256250999606s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:46:30 WARNING celery.redirected 2026-01-05 05:46:30 INFO celery.app.trace Task connecthub.dispatcher.tick[d620ca1e-6b90-4c80-80e6-1e6ac71c1572] succeeded in 0.03550281900061236s: {'triggered': 1} +2026-01-05 05:46:30 INFO celery.app.trace Task connecthub.dispatcher.tick[d620ca1e-6b90-4c80-80e6-1e6ac71c1572] succeeded in 0.03550281900061236s: {'triggered': 1} +2026-01-05 05:46:30 WARNING celery.redirected 2026-01-05 05:46:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:46:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:46:30 WARNING celery.redirected 2026-01-05 05:46:30 INFO celery.app.trace Task connecthub.execute_job[58573d2a-c361-4505-8162-9000993ab39e] succeeded in 0.032898728000873234s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:46:30 INFO celery.app.trace Task connecthub.execute_job[58573d2a-c361-4505-8162-9000993ab39e] succeeded in 0.032898728000873234s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:47:30 WARNING celery.redirected 2026-01-05 05:47:30 INFO celery.app.trace Task connecthub.dispatcher.tick[e428d868-3685-4bdd-8ffd-a9904da8ea08] succeeded in 0.016852472001119168s: {'triggered': 1} +2026-01-05 05:47:30 INFO celery.app.trace Task connecthub.dispatcher.tick[e428d868-3685-4bdd-8ffd-a9904da8ea08] succeeded in 0.016852472001119168s: {'triggered': 1} +2026-01-05 05:47:30 WARNING celery.redirected 2026-01-05 05:47:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:47:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:47:30 WARNING celery.redirected 2026-01-05 05:47:30 INFO celery.app.trace Task connecthub.execute_job[55f28990-97d2-4d7f-ba26-6fefe7e3dd5f] succeeded in 0.023192691000076593s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:47:30 INFO celery.app.trace Task connecthub.execute_job[55f28990-97d2-4d7f-ba26-6fefe7e3dd5f] succeeded in 0.023192691000076593s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:48:30 WARNING celery.redirected 2026-01-05 05:48:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f81f7d17-5af4-46e6-8c3f-50d87bdbb342] succeeded in 0.020918959000482573s: {'triggered': 1} +2026-01-05 05:48:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f81f7d17-5af4-46e6-8c3f-50d87bdbb342] succeeded in 0.020918959000482573s: {'triggered': 1} +2026-01-05 05:48:30 WARNING celery.redirected 2026-01-05 05:48:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:48:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:48:30 WARNING celery.redirected 2026-01-05 05:48:30 INFO celery.app.trace Task connecthub.execute_job[9a94f156-60ee-49ed-9014-e8edd886ba54] succeeded in 0.020251587999155163s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:48:30 INFO celery.app.trace Task connecthub.execute_job[9a94f156-60ee-49ed-9014-e8edd886ba54] succeeded in 0.020251587999155163s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:49:30 WARNING celery.redirected 2026-01-05 05:49:30 INFO celery.app.trace Task connecthub.dispatcher.tick[91b53351-4145-4eb0-98ee-fb8e03bc49ee] succeeded in 0.024995726998895407s: {'triggered': 1} +2026-01-05 05:49:30 INFO celery.app.trace Task connecthub.dispatcher.tick[91b53351-4145-4eb0-98ee-fb8e03bc49ee] succeeded in 0.024995726998895407s: {'triggered': 1} +2026-01-05 05:49:30 WARNING celery.redirected 2026-01-05 05:49:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:49:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:49:30 WARNING celery.redirected 2026-01-05 05:49:30 INFO celery.app.trace Task connecthub.execute_job[3b75b748-2eef-4c51-a45b-7e6b5f33c83a] succeeded in 0.03371988400067494s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:49:30 INFO celery.app.trace Task connecthub.execute_job[3b75b748-2eef-4c51-a45b-7e6b5f33c83a] succeeded in 0.03371988400067494s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:50:30 WARNING celery.redirected 2026-01-05 05:50:30 INFO celery.app.trace Task connecthub.dispatcher.tick[26a47a17-9e8d-4de0-8ed4-ad44a69b20bc] succeeded in 0.029451242000504863s: {'triggered': 1} +2026-01-05 05:50:30 INFO celery.app.trace Task connecthub.dispatcher.tick[26a47a17-9e8d-4de0-8ed4-ad44a69b20bc] succeeded in 0.029451242000504863s: {'triggered': 1} +2026-01-05 05:50:30 WARNING celery.redirected 2026-01-05 05:50:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:50:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:50:30 WARNING celery.redirected 2026-01-05 05:50:30 INFO celery.app.trace Task connecthub.execute_job[bcaad853-aa66-4766-aa54-eef8b1a009a1] succeeded in 0.03852558600010525s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:50:30 INFO celery.app.trace Task connecthub.execute_job[bcaad853-aa66-4766-aa54-eef8b1a009a1] succeeded in 0.03852558600010525s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:51:30 WARNING celery.redirected 2026-01-05 05:51:30 INFO celery.app.trace Task connecthub.dispatcher.tick[d3d455ab-d101-42d5-af05-f3fcc45a747c] succeeded in 0.02421432000119239s: {'triggered': 1} +2026-01-05 05:51:30 INFO celery.app.trace Task connecthub.dispatcher.tick[d3d455ab-d101-42d5-af05-f3fcc45a747c] succeeded in 0.02421432000119239s: {'triggered': 1} +2026-01-05 05:51:30 WARNING celery.redirected 2026-01-05 05:51:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:51:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:51:30 WARNING celery.redirected 2026-01-05 05:51:30 INFO celery.app.trace Task connecthub.execute_job[a151e738-563b-4f35-b402-5008d5c0cfa8] succeeded in 0.0239177359999303s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:51:30 INFO celery.app.trace Task connecthub.execute_job[a151e738-563b-4f35-b402-5008d5c0cfa8] succeeded in 0.0239177359999303s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:52:30 WARNING celery.redirected 2026-01-05 05:52:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4df3e07b-634a-4781-ad7a-2e72556d90e8] succeeded in 0.011886543001310201s: {'triggered': 1} +2026-01-05 05:52:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4df3e07b-634a-4781-ad7a-2e72556d90e8] succeeded in 0.011886543001310201s: {'triggered': 1} +2026-01-05 05:52:30 WARNING celery.redirected 2026-01-05 05:52:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:52:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:52:30 WARNING celery.redirected 2026-01-05 05:52:30 INFO celery.app.trace Task connecthub.execute_job[6bc8805f-ec48-4271-87ef-9595169953ca] succeeded in 0.025019463999342406s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:52:30 INFO celery.app.trace Task connecthub.execute_job[6bc8805f-ec48-4271-87ef-9595169953ca] succeeded in 0.025019463999342406s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:53:30 WARNING celery.redirected 2026-01-05 05:53:30 INFO celery.app.trace Task connecthub.dispatcher.tick[c01a1481-3a8d-4dde-8e87-654fbd6089bd] succeeded in 0.011546083000212093s: {'triggered': 1} +2026-01-05 05:53:30 INFO celery.app.trace Task connecthub.dispatcher.tick[c01a1481-3a8d-4dde-8e87-654fbd6089bd] succeeded in 0.011546083000212093s: {'triggered': 1} +2026-01-05 05:53:30 WARNING celery.redirected 2026-01-05 05:53:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:53:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:53:30 WARNING celery.redirected 2026-01-05 05:53:30 INFO celery.app.trace Task connecthub.execute_job[349a430d-9cfd-46d4-8ed2-6d2399c5ed03] succeeded in 0.02823889299907023s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:53:30 INFO celery.app.trace Task connecthub.execute_job[349a430d-9cfd-46d4-8ed2-6d2399c5ed03] succeeded in 0.02823889299907023s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:54:30 WARNING celery.redirected 2026-01-05 05:54:30 INFO celery.app.trace Task connecthub.dispatcher.tick[ab81f78d-088c-43f3-beba-78b2482d225a] succeeded in 0.017373353999573737s: {'triggered': 1} +2026-01-05 05:54:30 INFO celery.app.trace Task connecthub.dispatcher.tick[ab81f78d-088c-43f3-beba-78b2482d225a] succeeded in 0.017373353999573737s: {'triggered': 1} +2026-01-05 05:54:30 WARNING celery.redirected 2026-01-05 05:54:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:54:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:54:30 WARNING celery.redirected 2026-01-05 05:54:30 INFO celery.app.trace Task connecthub.execute_job[d7069a63-717f-4133-8637-e2757b642ffd] succeeded in 0.03503616700072598s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:54:30 INFO celery.app.trace Task connecthub.execute_job[d7069a63-717f-4133-8637-e2757b642ffd] succeeded in 0.03503616700072598s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:55:30 WARNING celery.redirected 2026-01-05 05:55:30 INFO celery.app.trace Task connecthub.dispatcher.tick[94abcaec-3182-46bc-a062-de6bede82847] succeeded in 0.013367278999794507s: {'triggered': 1} +2026-01-05 05:55:30 INFO celery.app.trace Task connecthub.dispatcher.tick[94abcaec-3182-46bc-a062-de6bede82847] succeeded in 0.013367278999794507s: {'triggered': 1} +2026-01-05 05:55:30 WARNING celery.redirected 2026-01-05 05:55:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:55:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:55:30 WARNING celery.redirected 2026-01-05 05:55:30 INFO celery.app.trace Task connecthub.execute_job[2a9633a4-b4e5-4127-a27c-c31c9a65ec4d] succeeded in 0.017370081000990467s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:55:30 INFO celery.app.trace Task connecthub.execute_job[2a9633a4-b4e5-4127-a27c-c31c9a65ec4d] succeeded in 0.017370081000990467s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:56:30 WARNING celery.redirected 2026-01-05 05:56:30 INFO celery.app.trace Task connecthub.dispatcher.tick[3b23f948-6593-46af-b3e9-ba71cef0f94f] succeeded in 0.012565402999825892s: {'triggered': 1} +2026-01-05 05:56:30 INFO celery.app.trace Task connecthub.dispatcher.tick[3b23f948-6593-46af-b3e9-ba71cef0f94f] succeeded in 0.012565402999825892s: {'triggered': 1} +2026-01-05 05:56:30 WARNING celery.redirected 2026-01-05 05:56:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:56:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:56:30 WARNING celery.redirected 2026-01-05 05:56:30 INFO celery.app.trace Task connecthub.execute_job[e487ce16-e7a6-4595-a8ad-5561957fb9b0] succeeded in 0.03014952500052459s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:56:30 INFO celery.app.trace Task connecthub.execute_job[e487ce16-e7a6-4595-a8ad-5561957fb9b0] succeeded in 0.03014952500052459s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:57:30 WARNING celery.redirected 2026-01-05 05:57:30 INFO celery.app.trace Task connecthub.dispatcher.tick[209e4885-328b-4b37-ada6-2724dc937bff] succeeded in 0.0279219309995824s: {'triggered': 1} +2026-01-05 05:57:30 INFO celery.app.trace Task connecthub.dispatcher.tick[209e4885-328b-4b37-ada6-2724dc937bff] succeeded in 0.0279219309995824s: {'triggered': 1} +2026-01-05 05:57:30 WARNING celery.redirected 2026-01-05 05:57:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:57:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:57:30 WARNING celery.redirected 2026-01-05 05:57:30 INFO celery.app.trace Task connecthub.execute_job[c3b24e32-e1c6-401d-8a0c-c723a5f4bb2d] succeeded in 0.039173844999822904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:57:30 INFO celery.app.trace Task connecthub.execute_job[c3b24e32-e1c6-401d-8a0c-c723a5f4bb2d] succeeded in 0.039173844999822904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:58:30 WARNING celery.redirected 2026-01-05 05:58:30 INFO celery.app.trace Task connecthub.dispatcher.tick[2be340dd-2654-4ac0-96e5-365d25616cb5] succeeded in 0.04025455699957092s: {'triggered': 1} +2026-01-05 05:58:30 INFO celery.app.trace Task connecthub.dispatcher.tick[2be340dd-2654-4ac0-96e5-365d25616cb5] succeeded in 0.04025455699957092s: {'triggered': 1} +2026-01-05 05:58:30 WARNING celery.redirected 2026-01-05 05:58:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:58:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:58:30 WARNING celery.redirected 2026-01-05 05:58:30 INFO celery.app.trace Task connecthub.execute_job[1f61f517-1bd5-4a8c-af48-afcf593f4aab] succeeded in 0.027227636999668903s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:58:30 INFO celery.app.trace Task connecthub.execute_job[1f61f517-1bd5-4a8c-af48-afcf593f4aab] succeeded in 0.027227636999668903s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:59:30 WARNING celery.redirected 2026-01-05 05:59:30 INFO celery.app.trace Task connecthub.dispatcher.tick[45a8cccb-f484-45d5-8014-96d540d5b4c1] succeeded in 0.008572890999857918s: {'triggered': 1} +2026-01-05 05:59:30 INFO celery.app.trace Task connecthub.dispatcher.tick[45a8cccb-f484-45d5-8014-96d540d5b4c1] succeeded in 0.008572890999857918s: {'triggered': 1} +2026-01-05 05:59:30 WARNING celery.redirected 2026-01-05 05:59:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:59:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 05:59:30 WARNING celery.redirected 2026-01-05 05:59:30 INFO celery.app.trace Task connecthub.execute_job[5097f1f5-888e-4c64-8af1-46f3a7d8a5d7] succeeded in 0.016939488999923924s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 05:59:30 INFO celery.app.trace Task connecthub.execute_job[5097f1f5-888e-4c64-8af1-46f3a7d8a5d7] succeeded in 0.016939488999923924s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:00:30 WARNING celery.redirected 2026-01-05 06:00:30 INFO celery.app.trace Task connecthub.dispatcher.tick[8069c5e7-04aa-4c8f-846e-fb7c127747a0] succeeded in 0.012104123999961303s: {'triggered': 1} +2026-01-05 06:00:30 INFO celery.app.trace Task connecthub.dispatcher.tick[8069c5e7-04aa-4c8f-846e-fb7c127747a0] succeeded in 0.012104123999961303s: {'triggered': 1} +2026-01-05 06:00:30 WARNING celery.redirected 2026-01-05 06:00:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:00:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:00:30 WARNING celery.redirected 2026-01-05 06:00:30 INFO celery.app.trace Task connecthub.execute_job[49652422-5d0a-4615-98f1-74d3a0dc807d] succeeded in 0.02802831700137176s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:00:30 INFO celery.app.trace Task connecthub.execute_job[49652422-5d0a-4615-98f1-74d3a0dc807d] succeeded in 0.02802831700137176s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:01:30 WARNING celery.redirected 2026-01-05 06:01:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4df18747-7216-43ae-a4f6-77a8c8eba94d] succeeded in 0.03370886000084283s: {'triggered': 1} +2026-01-05 06:01:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4df18747-7216-43ae-a4f6-77a8c8eba94d] succeeded in 0.03370886000084283s: {'triggered': 1} +2026-01-05 06:01:30 WARNING celery.redirected 2026-01-05 06:01:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:01:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:01:30 WARNING celery.redirected 2026-01-05 06:01:30 INFO celery.app.trace Task connecthub.execute_job[33bd16cd-69b1-4892-a85b-2612e55425aa] succeeded in 0.0320245969996904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:01:30 INFO celery.app.trace Task connecthub.execute_job[33bd16cd-69b1-4892-a85b-2612e55425aa] succeeded in 0.0320245969996904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:02:30 WARNING celery.redirected 2026-01-05 06:02:30 INFO celery.app.trace Task connecthub.dispatcher.tick[86fbee18-65b8-477a-9d3c-3826b1e5ee80] succeeded in 0.017320659999313648s: {'triggered': 1} +2026-01-05 06:02:30 INFO celery.app.trace Task connecthub.dispatcher.tick[86fbee18-65b8-477a-9d3c-3826b1e5ee80] succeeded in 0.017320659999313648s: {'triggered': 1} +2026-01-05 06:02:30 WARNING celery.redirected 2026-01-05 06:02:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:02:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:02:30 WARNING celery.redirected 2026-01-05 06:02:30 INFO celery.app.trace Task connecthub.execute_job[83552280-2a96-4fd9-a0be-99ca93901ee7] succeeded in 0.028892322998217423s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:02:30 INFO celery.app.trace Task connecthub.execute_job[83552280-2a96-4fd9-a0be-99ca93901ee7] succeeded in 0.028892322998217423s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:03:30 WARNING celery.redirected 2026-01-05 06:03:30 INFO celery.app.trace Task connecthub.dispatcher.tick[cfd8a733-90f0-4d3c-b720-2d41df04404d] succeeded in 0.017844455000158632s: {'triggered': 1} +2026-01-05 06:03:30 INFO celery.app.trace Task connecthub.dispatcher.tick[cfd8a733-90f0-4d3c-b720-2d41df04404d] succeeded in 0.017844455000158632s: {'triggered': 1} +2026-01-05 06:03:30 WARNING celery.redirected 2026-01-05 06:03:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:03:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:03:30 WARNING celery.redirected 2026-01-05 06:03:30 INFO celery.app.trace Task connecthub.execute_job[49ceefed-0b5b-4899-8759-e76dc5c74e3a] succeeded in 0.024473126000884804s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:03:30 INFO celery.app.trace Task connecthub.execute_job[49ceefed-0b5b-4899-8759-e76dc5c74e3a] succeeded in 0.024473126000884804s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:04:30 WARNING celery.redirected 2026-01-05 06:04:30 INFO celery.app.trace Task connecthub.dispatcher.tick[d373478b-45e9-4000-bffd-24b0de6bb935] succeeded in 0.016729655999370152s: {'triggered': 1} +2026-01-05 06:04:30 INFO celery.app.trace Task connecthub.dispatcher.tick[d373478b-45e9-4000-bffd-24b0de6bb935] succeeded in 0.016729655999370152s: {'triggered': 1} +2026-01-05 06:04:30 WARNING celery.redirected 2026-01-05 06:04:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:04:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:04:30 WARNING celery.redirected 2026-01-05 06:04:30 INFO celery.app.trace Task connecthub.execute_job[a71695a5-b9ee-4f1b-bd19-d831285aaa51] succeeded in 0.021148603000256117s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:04:30 INFO celery.app.trace Task connecthub.execute_job[a71695a5-b9ee-4f1b-bd19-d831285aaa51] succeeded in 0.021148603000256117s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:05:30 WARNING celery.redirected 2026-01-05 06:05:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b9f4be59-62cc-41eb-bc94-054732dce1a4] succeeded in 0.014041430000361288s: {'triggered': 1} +2026-01-05 06:05:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b9f4be59-62cc-41eb-bc94-054732dce1a4] succeeded in 0.014041430000361288s: {'triggered': 1} +2026-01-05 06:05:30 WARNING celery.redirected 2026-01-05 06:05:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:05:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:05:30 WARNING celery.redirected 2026-01-05 06:05:30 INFO celery.app.trace Task connecthub.execute_job[35188251-969c-4e41-96bf-9288ef52a973] succeeded in 0.020802560000447556s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:05:30 INFO celery.app.trace Task connecthub.execute_job[35188251-969c-4e41-96bf-9288ef52a973] succeeded in 0.020802560000447556s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:06:30 WARNING celery.redirected 2026-01-05 06:06:30 INFO celery.app.trace Task connecthub.dispatcher.tick[3c80a955-cc8c-4c39-b6e4-21ce4ac1b850] succeeded in 0.010666323001714773s: {'triggered': 1} +2026-01-05 06:06:30 INFO celery.app.trace Task connecthub.dispatcher.tick[3c80a955-cc8c-4c39-b6e4-21ce4ac1b850] succeeded in 0.010666323001714773s: {'triggered': 1} +2026-01-05 06:06:30 WARNING celery.redirected 2026-01-05 06:06:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:06:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:06:30 WARNING celery.redirected 2026-01-05 06:06:30 INFO celery.app.trace Task connecthub.execute_job[4032d14b-e2f9-4ffe-b038-b6de169c8c75] succeeded in 0.022911324000233435s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:06:30 INFO celery.app.trace Task connecthub.execute_job[4032d14b-e2f9-4ffe-b038-b6de169c8c75] succeeded in 0.022911324000233435s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:07:30 WARNING celery.redirected 2026-01-05 06:07:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4e2bd2c6-b54f-41f8-af47-4a77bae7a287] succeeded in 0.042610238000634126s: {'triggered': 1} +2026-01-05 06:07:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4e2bd2c6-b54f-41f8-af47-4a77bae7a287] succeeded in 0.042610238000634126s: {'triggered': 1} +2026-01-05 06:07:30 WARNING celery.redirected 2026-01-05 06:07:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:07:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:07:30 WARNING celery.redirected 2026-01-05 06:07:30 INFO celery.app.trace Task connecthub.execute_job[259a0a3d-55f4-48ff-ac97-65489829d694] succeeded in 0.05615256400051294s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:07:30 INFO celery.app.trace Task connecthub.execute_job[259a0a3d-55f4-48ff-ac97-65489829d694] succeeded in 0.05615256400051294s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:08:30 WARNING celery.redirected 2026-01-05 06:08:30 INFO celery.app.trace Task connecthub.dispatcher.tick[57f37a04-8e64-4700-a20f-9877151550d1] succeeded in 0.03720585700102674s: {'triggered': 1} +2026-01-05 06:08:30 INFO celery.app.trace Task connecthub.dispatcher.tick[57f37a04-8e64-4700-a20f-9877151550d1] succeeded in 0.03720585700102674s: {'triggered': 1} +2026-01-05 06:08:30 WARNING celery.redirected 2026-01-05 06:08:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:08:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:08:30 WARNING celery.redirected 2026-01-05 06:08:30 INFO celery.app.trace Task connecthub.execute_job[5b646856-176a-4096-99ac-491f0322a391] succeeded in 0.05866147000051569s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:08:30 INFO celery.app.trace Task connecthub.execute_job[5b646856-176a-4096-99ac-491f0322a391] succeeded in 0.05866147000051569s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:09:30 WARNING celery.redirected 2026-01-05 06:09:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4af79bdd-f846-43f4-a35c-9649d1ab6820] succeeded in 0.012601733998963027s: {'triggered': 1} +2026-01-05 06:09:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4af79bdd-f846-43f4-a35c-9649d1ab6820] succeeded in 0.012601733998963027s: {'triggered': 1} +2026-01-05 06:09:30 WARNING celery.redirected 2026-01-05 06:09:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:09:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:09:30 WARNING celery.redirected 2026-01-05 06:09:30 INFO celery.app.trace Task connecthub.execute_job[15a764cc-58f2-4ad5-9a1b-bb106d10d853] succeeded in 0.02483064200168883s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:09:30 INFO celery.app.trace Task connecthub.execute_job[15a764cc-58f2-4ad5-9a1b-bb106d10d853] succeeded in 0.02483064200168883s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:10:30 WARNING celery.redirected 2026-01-05 06:10:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4c821dc4-354d-4b27-9d10-fd0584b411c3] succeeded in 0.025750164999408298s: {'triggered': 1} +2026-01-05 06:10:30 INFO celery.app.trace Task connecthub.dispatcher.tick[4c821dc4-354d-4b27-9d10-fd0584b411c3] succeeded in 0.025750164999408298s: {'triggered': 1} +2026-01-05 06:10:30 WARNING celery.redirected 2026-01-05 06:10:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:10:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:10:30 WARNING celery.redirected 2026-01-05 06:10:30 INFO celery.app.trace Task connecthub.execute_job[64938c6b-c50b-462b-a319-e64853d7e738] succeeded in 0.024463608999212738s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:10:30 INFO celery.app.trace Task connecthub.execute_job[64938c6b-c50b-462b-a319-e64853d7e738] succeeded in 0.024463608999212738s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:11:30 WARNING celery.redirected 2026-01-05 06:11:30 INFO celery.app.trace Task connecthub.dispatcher.tick[8f8dfad2-c64f-4cf6-9204-700da8d460d5] succeeded in 0.01863696100008383s: {'triggered': 1} +2026-01-05 06:11:30 INFO celery.app.trace Task connecthub.dispatcher.tick[8f8dfad2-c64f-4cf6-9204-700da8d460d5] succeeded in 0.01863696100008383s: {'triggered': 1} +2026-01-05 06:11:30 WARNING celery.redirected 2026-01-05 06:11:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:11:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:11:30 WARNING celery.redirected 2026-01-05 06:11:30 INFO celery.app.trace Task connecthub.execute_job[0e58c0e7-722a-4fc2-b95c-688e3fc1a253] succeeded in 0.025985803000367014s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:11:30 INFO celery.app.trace Task connecthub.execute_job[0e58c0e7-722a-4fc2-b95c-688e3fc1a253] succeeded in 0.025985803000367014s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:12:30 WARNING celery.redirected 2026-01-05 06:12:30 INFO celery.app.trace Task connecthub.dispatcher.tick[36368e0e-7ff7-4de5-9721-067f54671c68] succeeded in 0.017128098999819485s: {'triggered': 1} +2026-01-05 06:12:30 INFO celery.app.trace Task connecthub.dispatcher.tick[36368e0e-7ff7-4de5-9721-067f54671c68] succeeded in 0.017128098999819485s: {'triggered': 1} +2026-01-05 06:12:30 WARNING celery.redirected 2026-01-05 06:12:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:12:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:12:30 WARNING celery.redirected 2026-01-05 06:12:30 INFO celery.app.trace Task connecthub.execute_job[7d7297e3-7970-44e4-857e-2c805f69070f] succeeded in 0.024267677999887383s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:12:30 INFO celery.app.trace Task connecthub.execute_job[7d7297e3-7970-44e4-857e-2c805f69070f] succeeded in 0.024267677999887383s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:13:30 WARNING celery.redirected 2026-01-05 06:13:30 INFO celery.app.trace Task connecthub.dispatcher.tick[346a0cda-08cd-4885-b91b-8ff867751d77] succeeded in 0.03172679700037406s: {'triggered': 1} +2026-01-05 06:13:30 INFO celery.app.trace Task connecthub.dispatcher.tick[346a0cda-08cd-4885-b91b-8ff867751d77] succeeded in 0.03172679700037406s: {'triggered': 1} +2026-01-05 06:13:30 WARNING celery.redirected 2026-01-05 06:13:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:13:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:13:30 WARNING celery.redirected 2026-01-05 06:13:30 INFO celery.app.trace Task connecthub.execute_job[5899afc1-2ce0-48a1-93af-6c84715551ac] succeeded in 0.027441657999588642s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:13:30 INFO celery.app.trace Task connecthub.execute_job[5899afc1-2ce0-48a1-93af-6c84715551ac] succeeded in 0.027441657999588642s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:14:30 WARNING celery.redirected 2026-01-05 06:14:30 INFO celery.app.trace Task connecthub.dispatcher.tick[bbee957f-6a26-4b47-8506-51bf24ff9a97] succeeded in 0.02345835599953716s: {'triggered': 1} +2026-01-05 06:14:30 INFO celery.app.trace Task connecthub.dispatcher.tick[bbee957f-6a26-4b47-8506-51bf24ff9a97] succeeded in 0.02345835599953716s: {'triggered': 1} +2026-01-05 06:14:30 WARNING celery.redirected 2026-01-05 06:14:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:14:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:14:30 WARNING celery.redirected 2026-01-05 06:14:30 INFO celery.app.trace Task connecthub.execute_job[3757768b-5f7f-454a-847d-d063e2658849] succeeded in 0.04374712600110797s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:14:30 INFO celery.app.trace Task connecthub.execute_job[3757768b-5f7f-454a-847d-d063e2658849] succeeded in 0.04374712600110797s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:15:30 WARNING celery.redirected 2026-01-05 06:15:30 INFO celery.app.trace Task connecthub.dispatcher.tick[2dfbd768-2aae-449f-9b7d-63928813bc10] succeeded in 0.02275668800029962s: {'triggered': 1} +2026-01-05 06:15:30 INFO celery.app.trace Task connecthub.dispatcher.tick[2dfbd768-2aae-449f-9b7d-63928813bc10] succeeded in 0.02275668800029962s: {'triggered': 1} +2026-01-05 06:15:30 WARNING celery.redirected 2026-01-05 06:15:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:15:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:15:30 WARNING celery.redirected 2026-01-05 06:15:30 INFO celery.app.trace Task connecthub.execute_job[59870fc5-d68a-425f-a1fc-3af1e352bc3b] succeeded in 0.025605107999581378s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:15:30 INFO celery.app.trace Task connecthub.execute_job[59870fc5-d68a-425f-a1fc-3af1e352bc3b] succeeded in 0.025605107999581378s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:16:30 WARNING celery.redirected 2026-01-05 06:16:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f3fcb441-af6d-4811-b683-83578f65d06a] succeeded in 0.02731711600063136s: {'triggered': 1} +2026-01-05 06:16:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f3fcb441-af6d-4811-b683-83578f65d06a] succeeded in 0.02731711600063136s: {'triggered': 1} +2026-01-05 06:16:30 WARNING celery.redirected 2026-01-05 06:16:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:16:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:16:30 WARNING celery.redirected 2026-01-05 06:16:30 INFO celery.app.trace Task connecthub.execute_job[8171cb15-18e1-4683-90d9-792953bc79ae] succeeded in 0.032392187998993904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:16:30 INFO celery.app.trace Task connecthub.execute_job[8171cb15-18e1-4683-90d9-792953bc79ae] succeeded in 0.032392187998993904s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:17:30 WARNING celery.redirected 2026-01-05 06:17:30 INFO celery.app.trace Task connecthub.dispatcher.tick[873a3f98-382b-4f55-8228-d14b53eaa27d] succeeded in 0.01927227899977879s: {'triggered': 1} +2026-01-05 06:17:30 INFO celery.app.trace Task connecthub.dispatcher.tick[873a3f98-382b-4f55-8228-d14b53eaa27d] succeeded in 0.01927227899977879s: {'triggered': 1} +2026-01-05 06:17:30 WARNING celery.redirected 2026-01-05 06:17:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:17:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:17:30 WARNING celery.redirected 2026-01-05 06:17:30 INFO celery.app.trace Task connecthub.execute_job[6f1b63c9-7733-4215-8963-4898bad700d9] succeeded in 0.03501303600023675s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:17:30 INFO celery.app.trace Task connecthub.execute_job[6f1b63c9-7733-4215-8963-4898bad700d9] succeeded in 0.03501303600023675s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:18:30 WARNING celery.redirected 2026-01-05 06:18:30 INFO celery.app.trace Task connecthub.dispatcher.tick[ab8a55b4-a1f8-42fc-abf5-5aaf2e0e8eee] succeeded in 0.0179667430002155s: {'triggered': 1} +2026-01-05 06:18:30 INFO celery.app.trace Task connecthub.dispatcher.tick[ab8a55b4-a1f8-42fc-abf5-5aaf2e0e8eee] succeeded in 0.0179667430002155s: {'triggered': 1} +2026-01-05 06:18:30 WARNING celery.redirected 2026-01-05 06:18:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:18:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:18:30 WARNING celery.redirected 2026-01-05 06:18:30 INFO celery.app.trace Task connecthub.execute_job[c1c3b21c-eae1-4f19-b1b1-f4fc51556a79] succeeded in 0.02200347799953306s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:18:30 INFO celery.app.trace Task connecthub.execute_job[c1c3b21c-eae1-4f19-b1b1-f4fc51556a79] succeeded in 0.02200347799953306s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:19:30 WARNING celery.redirected 2026-01-05 06:19:30 INFO celery.app.trace Task connecthub.dispatcher.tick[429c77cd-0eb2-432f-8fe7-f3f192338bfe] succeeded in 0.0328237989997433s: {'triggered': 1} +2026-01-05 06:19:30 INFO celery.app.trace Task connecthub.dispatcher.tick[429c77cd-0eb2-432f-8fe7-f3f192338bfe] succeeded in 0.0328237989997433s: {'triggered': 1} +2026-01-05 06:19:30 WARNING celery.redirected 2026-01-05 06:19:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:19:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:19:30 WARNING celery.redirected 2026-01-05 06:19:30 INFO celery.app.trace Task connecthub.execute_job[d110f126-af61-4622-899c-e2c857eb40f7] succeeded in 0.02007196599879535s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:19:30 INFO celery.app.trace Task connecthub.execute_job[d110f126-af61-4622-899c-e2c857eb40f7] succeeded in 0.02007196599879535s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:20:30 WARNING celery.redirected 2026-01-05 06:20:30 INFO celery.app.trace Task connecthub.dispatcher.tick[90eae2e2-05a2-45fe-a9ed-1c563f426224] succeeded in 0.012721692000923213s: {'triggered': 1} +2026-01-05 06:20:30 INFO celery.app.trace Task connecthub.dispatcher.tick[90eae2e2-05a2-45fe-a9ed-1c563f426224] succeeded in 0.012721692000923213s: {'triggered': 1} +2026-01-05 06:20:30 WARNING celery.redirected 2026-01-05 06:20:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:20:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:20:30 WARNING celery.redirected 2026-01-05 06:20:30 INFO celery.app.trace Task connecthub.execute_job[5b8790da-2912-4973-b72e-819a9023aef2] succeeded in 0.02931942000031995s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:20:30 INFO celery.app.trace Task connecthub.execute_job[5b8790da-2912-4973-b72e-819a9023aef2] succeeded in 0.02931942000031995s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:21:30 WARNING celery.redirected 2026-01-05 06:21:30 INFO celery.app.trace Task connecthub.dispatcher.tick[440c4f3d-5775-4315-95d8-6628fc419117] succeeded in 0.023760617999869282s: {'triggered': 1} +2026-01-05 06:21:30 INFO celery.app.trace Task connecthub.dispatcher.tick[440c4f3d-5775-4315-95d8-6628fc419117] succeeded in 0.023760617999869282s: {'triggered': 1} +2026-01-05 06:21:30 WARNING celery.redirected 2026-01-05 06:21:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:21:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:21:30 WARNING celery.redirected 2026-01-05 06:21:30 INFO celery.app.trace Task connecthub.execute_job[fa3315c2-aab6-44e0-bdcd-66ff7b73edcd] succeeded in 0.02010451600108354s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:21:30 INFO celery.app.trace Task connecthub.execute_job[fa3315c2-aab6-44e0-bdcd-66ff7b73edcd] succeeded in 0.02010451600108354s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:22:30 WARNING celery.redirected 2026-01-05 06:22:30 INFO celery.app.trace Task connecthub.dispatcher.tick[9a94cf95-b812-4e01-9361-829af367e990] succeeded in 0.02755342399905203s: {'triggered': 1} +2026-01-05 06:22:30 INFO celery.app.trace Task connecthub.dispatcher.tick[9a94cf95-b812-4e01-9361-829af367e990] succeeded in 0.02755342399905203s: {'triggered': 1} +2026-01-05 06:22:30 WARNING celery.redirected 2026-01-05 06:22:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:22:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:22:30 WARNING celery.redirected 2026-01-05 06:22:30 INFO celery.app.trace Task connecthub.execute_job[7a88f89f-f8ec-43d1-b76f-28c179048781] succeeded in 0.039251813001101254s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:22:30 INFO celery.app.trace Task connecthub.execute_job[7a88f89f-f8ec-43d1-b76f-28c179048781] succeeded in 0.039251813001101254s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:23:30 WARNING celery.redirected 2026-01-05 06:23:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b63c9cb5-f3a3-4df2-b556-9a162dac8d9a] succeeded in 0.026172751000558492s: {'triggered': 1} +2026-01-05 06:23:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b63c9cb5-f3a3-4df2-b556-9a162dac8d9a] succeeded in 0.026172751000558492s: {'triggered': 1} +2026-01-05 06:23:30 WARNING celery.redirected 2026-01-05 06:23:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:23:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:23:30 WARNING celery.redirected 2026-01-05 06:23:30 INFO celery.app.trace Task connecthub.execute_job[1092e43c-9569-44b7-9c41-39fc2d6032d5] succeeded in 0.044292628999755834s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:23:30 INFO celery.app.trace Task connecthub.execute_job[1092e43c-9569-44b7-9c41-39fc2d6032d5] succeeded in 0.044292628999755834s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:24:30 WARNING celery.redirected 2026-01-05 06:24:30 INFO celery.app.trace Task connecthub.dispatcher.tick[8455733b-a460-4267-9434-5920d303749f] succeeded in 0.032748575998994056s: {'triggered': 1} +2026-01-05 06:24:30 INFO celery.app.trace Task connecthub.dispatcher.tick[8455733b-a460-4267-9434-5920d303749f] succeeded in 0.032748575998994056s: {'triggered': 1} +2026-01-05 06:24:30 WARNING celery.redirected 2026-01-05 06:24:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:24:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:24:30 WARNING celery.redirected 2026-01-05 06:24:30 INFO celery.app.trace Task connecthub.execute_job[e157ff3c-a728-4c32-b8ee-55c97d28e67d] succeeded in 0.02652125500026159s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:24:30 INFO celery.app.trace Task connecthub.execute_job[e157ff3c-a728-4c32-b8ee-55c97d28e67d] succeeded in 0.02652125500026159s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:25:30 WARNING celery.redirected 2026-01-05 06:25:30 INFO celery.app.trace Task connecthub.dispatcher.tick[11084af2-2216-4415-80eb-ac2c4aab3a1c] succeeded in 0.026211419999526697s: {'triggered': 1} +2026-01-05 06:25:30 INFO celery.app.trace Task connecthub.dispatcher.tick[11084af2-2216-4415-80eb-ac2c4aab3a1c] succeeded in 0.026211419999526697s: {'triggered': 1} +2026-01-05 06:25:30 WARNING celery.redirected 2026-01-05 06:25:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:25:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:25:30 WARNING celery.redirected 2026-01-05 06:25:30 INFO celery.app.trace Task connecthub.execute_job[81e09ef5-f429-4e1a-b94e-8d3c488ee02c] succeeded in 0.029906062000009115s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:25:30 INFO celery.app.trace Task connecthub.execute_job[81e09ef5-f429-4e1a-b94e-8d3c488ee02c] succeeded in 0.029906062000009115s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:26:30 WARNING celery.redirected 2026-01-05 06:26:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b7d1281f-9ee3-4e30-82cf-eab43c276cd0] succeeded in 0.02654871300001105s: {'triggered': 1} +2026-01-05 06:26:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b7d1281f-9ee3-4e30-82cf-eab43c276cd0] succeeded in 0.02654871300001105s: {'triggered': 1} +2026-01-05 06:26:30 WARNING celery.redirected 2026-01-05 06:26:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:26:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:26:30 WARNING celery.redirected 2026-01-05 06:26:30 INFO celery.app.trace Task connecthub.execute_job[b6037b0f-a0f0-4429-a149-fe67bdf19f37] succeeded in 0.030190688999937265s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:26:30 INFO celery.app.trace Task connecthub.execute_job[b6037b0f-a0f0-4429-a149-fe67bdf19f37] succeeded in 0.030190688999937265s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:27:30 WARNING celery.redirected 2026-01-05 06:27:30 INFO celery.app.trace Task connecthub.dispatcher.tick[fc5566aa-91d5-4e74-9fc9-3466c71afbe3] succeeded in 0.025498818000414758s: {'triggered': 1} +2026-01-05 06:27:30 INFO celery.app.trace Task connecthub.dispatcher.tick[fc5566aa-91d5-4e74-9fc9-3466c71afbe3] succeeded in 0.025498818000414758s: {'triggered': 1} +2026-01-05 06:27:30 WARNING celery.redirected 2026-01-05 06:27:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:27:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:27:30 WARNING celery.redirected 2026-01-05 06:27:30 INFO celery.app.trace Task connecthub.execute_job[f6651311-8d7e-43d1-9b98-0dbf853d973f] succeeded in 0.033761173001039424s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:27:30 INFO celery.app.trace Task connecthub.execute_job[f6651311-8d7e-43d1-9b98-0dbf853d973f] succeeded in 0.033761173001039424s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:28:30 WARNING celery.redirected 2026-01-05 06:28:30 INFO celery.app.trace Task connecthub.dispatcher.tick[626bf215-0b2c-4cbf-b211-dccab018d6c1] succeeded in 0.04751823699916713s: {'triggered': 1} +2026-01-05 06:28:30 INFO celery.app.trace Task connecthub.dispatcher.tick[626bf215-0b2c-4cbf-b211-dccab018d6c1] succeeded in 0.04751823699916713s: {'triggered': 1} +2026-01-05 06:28:30 WARNING celery.redirected 2026-01-05 06:28:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:28:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:28:30 WARNING celery.redirected 2026-01-05 06:28:30 INFO celery.app.trace Task connecthub.execute_job[b2556981-7b27-4df3-b1ef-b8cf7d3f6c4d] succeeded in 0.06460515599974315s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:28:30 INFO celery.app.trace Task connecthub.execute_job[b2556981-7b27-4df3-b1ef-b8cf7d3f6c4d] succeeded in 0.06460515599974315s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:28:52 WARNING celery.redirected 2026-01-05 06:28:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:28:52 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:28:52 WARNING celery.redirected 2026-01-05 06:28:52 INFO celery.app.trace Task connecthub.execute_job[c193401c-800c-4425-9087-e87901a817bc] succeeded in 0.13303516400083026s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:28:52 INFO celery.app.trace Task connecthub.execute_job[c193401c-800c-4425-9087-e87901a817bc] succeeded in 0.13303516400083026s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:29:30 WARNING celery.redirected 2026-01-05 06:29:30 INFO celery.app.trace Task connecthub.dispatcher.tick[580ed651-503b-4d75-8a24-a8a84595f74f] succeeded in 0.03042215200002829s: {'triggered': 1} +2026-01-05 06:29:30 INFO celery.app.trace Task connecthub.dispatcher.tick[580ed651-503b-4d75-8a24-a8a84595f74f] succeeded in 0.03042215200002829s: {'triggered': 1} +2026-01-05 06:29:30 WARNING celery.redirected 2026-01-05 06:29:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:29:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:29:30 WARNING celery.redirected 2026-01-05 06:29:30 INFO celery.app.trace Task connecthub.execute_job[4ac9a96c-2a7e-47cc-9ae0-6f332b465acf] succeeded in 0.033314542000880465s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:29:30 INFO celery.app.trace Task connecthub.execute_job[4ac9a96c-2a7e-47cc-9ae0-6f332b465acf] succeeded in 0.033314542000880465s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:30:30 WARNING celery.redirected 2026-01-05 06:30:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b87c598a-59c9-4b7d-9114-20f088511c61] succeeded in 0.06358310900031938s: {'triggered': 1} +2026-01-05 06:30:30 INFO celery.app.trace Task connecthub.dispatcher.tick[b87c598a-59c9-4b7d-9114-20f088511c61] succeeded in 0.06358310900031938s: {'triggered': 1} +2026-01-05 06:30:31 WARNING celery.redirected 2026-01-05 06:30:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:30:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:30:31 WARNING celery.redirected 2026-01-05 06:30:31 INFO celery.app.trace Task connecthub.execute_job[96822244-ab0e-406a-9f93-5145615f2364] succeeded in 0.11812609000116936s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:30:31 INFO celery.app.trace Task connecthub.execute_job[96822244-ab0e-406a-9f93-5145615f2364] succeeded in 0.11812609000116936s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:31:30 WARNING celery.redirected 2026-01-05 06:31:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f6a21c3c-c15a-4c72-bc85-a9af82ecaa84] succeeded in 0.03461962300025334s: {'triggered': 1} +2026-01-05 06:31:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f6a21c3c-c15a-4c72-bc85-a9af82ecaa84] succeeded in 0.03461962300025334s: {'triggered': 1} +2026-01-05 06:31:30 WARNING celery.redirected 2026-01-05 06:31:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:31:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:31:30 WARNING celery.redirected 2026-01-05 06:31:30 INFO celery.app.trace Task connecthub.execute_job[882a6ef4-5c7f-4485-9ac5-8ea62451010d] succeeded in 0.03289823999875807s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:31:30 INFO celery.app.trace Task connecthub.execute_job[882a6ef4-5c7f-4485-9ac5-8ea62451010d] succeeded in 0.03289823999875807s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:32:30 WARNING celery.redirected 2026-01-05 06:32:30 INFO celery.app.trace Task connecthub.dispatcher.tick[62bc0c85-0639-4e9c-8fe6-bda9846651ba] succeeded in 0.029510264999771607s: {'triggered': 1} +2026-01-05 06:32:30 INFO celery.app.trace Task connecthub.dispatcher.tick[62bc0c85-0639-4e9c-8fe6-bda9846651ba] succeeded in 0.029510264999771607s: {'triggered': 1} +2026-01-05 06:32:30 WARNING celery.redirected 2026-01-05 06:32:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:32:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:32:30 WARNING celery.redirected 2026-01-05 06:32:30 INFO celery.app.trace Task connecthub.execute_job[281fda63-1885-4aad-be38-5d72d7058d98] succeeded in 0.031486481999309035s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:32:30 INFO celery.app.trace Task connecthub.execute_job[281fda63-1885-4aad-be38-5d72d7058d98] succeeded in 0.031486481999309035s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:33:30 WARNING celery.redirected 2026-01-05 06:33:30 INFO celery.app.trace Task connecthub.dispatcher.tick[05bfd48f-987e-4806-bb99-632c6af0762a] succeeded in 0.009836547000304563s: {'triggered': 1} +2026-01-05 06:33:30 INFO celery.app.trace Task connecthub.dispatcher.tick[05bfd48f-987e-4806-bb99-632c6af0762a] succeeded in 0.009836547000304563s: {'triggered': 1} +2026-01-05 06:33:30 WARNING celery.redirected 2026-01-05 06:33:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:33:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:33:30 WARNING celery.redirected 2026-01-05 06:33:30 INFO celery.app.trace Task connecthub.execute_job[b7328527-b698-4d65-96f0-b095448d0ffa] succeeded in 0.020123803000387852s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:33:30 INFO celery.app.trace Task connecthub.execute_job[b7328527-b698-4d65-96f0-b095448d0ffa] succeeded in 0.020123803000387852s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:34:30 WARNING celery.redirected 2026-01-05 06:34:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f9ce71dd-ba27-46dd-9af8-b1b76b76ad11] succeeded in 0.02779163199920731s: {'triggered': 1} +2026-01-05 06:34:30 INFO celery.app.trace Task connecthub.dispatcher.tick[f9ce71dd-ba27-46dd-9af8-b1b76b76ad11] succeeded in 0.02779163199920731s: {'triggered': 1} +2026-01-05 06:34:30 WARNING celery.redirected 2026-01-05 06:34:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:34:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:34:30 WARNING celery.redirected 2026-01-05 06:34:30 INFO celery.app.trace Task connecthub.execute_job[00519ef6-2be1-47e5-b034-02b77d6faa26] succeeded in 0.023834196999814594s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:34:30 INFO celery.app.trace Task connecthub.execute_job[00519ef6-2be1-47e5-b034-02b77d6faa26] succeeded in 0.023834196999814594s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:35:31 WARNING celery.redirected 2026-01-05 06:35:31 INFO celery.app.trace Task connecthub.dispatcher.tick[1e3ff92b-b16b-4e05-a0f0-145b100a92db] succeeded in 0.1084168569996109s: {'triggered': 1} +2026-01-05 06:35:31 INFO celery.app.trace Task connecthub.dispatcher.tick[1e3ff92b-b16b-4e05-a0f0-145b100a92db] succeeded in 0.1084168569996109s: {'triggered': 1} +2026-01-05 06:35:31 WARNING celery.redirected 2026-01-05 06:35:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:35:31 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:35:31 WARNING celery.redirected 2026-01-05 06:35:31 INFO celery.app.trace Task connecthub.execute_job[e140544c-1ee9-4674-abb1-f5a730158a98] succeeded in 0.09931235399926663s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:35:31 INFO celery.app.trace Task connecthub.execute_job[e140544c-1ee9-4674-abb1-f5a730158a98] succeeded in 0.09931235399926663s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:36:30 WARNING celery.redirected 2026-01-05 06:36:30 INFO celery.app.trace Task connecthub.dispatcher.tick[fef082cb-e0d9-4e83-b552-aa7ac0dd4c44] succeeded in 0.04090903100041032s: {'triggered': 1} +2026-01-05 06:36:30 INFO celery.app.trace Task connecthub.dispatcher.tick[fef082cb-e0d9-4e83-b552-aa7ac0dd4c44] succeeded in 0.04090903100041032s: {'triggered': 1} +2026-01-05 06:36:30 WARNING celery.redirected 2026-01-05 06:36:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:36:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:36:30 WARNING celery.redirected 2026-01-05 06:36:30 INFO celery.app.trace Task connecthub.execute_job[24103207-47ad-4749-a459-606da3ae912a] succeeded in 0.06330509800136497s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:36:30 INFO celery.app.trace Task connecthub.execute_job[24103207-47ad-4749-a459-606da3ae912a] succeeded in 0.06330509800136497s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:37:21 WARNING celery.redirected 2026-01-05 06:37:21 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:37:21 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:37:21 WARNING celery.redirected 2026-01-05 06:37:21 INFO celery.app.trace Task connecthub.execute_job[ff067cc1-a57d-4a7a-ad3c-b1b02114e4e8] succeeded in 0.13438932200006093s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:37:21 INFO celery.app.trace Task connecthub.execute_job[ff067cc1-a57d-4a7a-ad3c-b1b02114e4e8] succeeded in 0.13438932200006093s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:37:30 WARNING celery.redirected 2026-01-05 06:37:30 INFO celery.app.trace Task connecthub.dispatcher.tick[c89ec037-53b3-4c3b-bae0-719ad1f0a4ba] succeeded in 0.016763164998337743s: {'triggered': 1} +2026-01-05 06:37:30 INFO celery.app.trace Task connecthub.dispatcher.tick[c89ec037-53b3-4c3b-bae0-719ad1f0a4ba] succeeded in 0.016763164998337743s: {'triggered': 1} +2026-01-05 06:37:30 WARNING celery.redirected 2026-01-05 06:37:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:37:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:37:30 WARNING celery.redirected 2026-01-05 06:37:30 INFO celery.app.trace Task connecthub.execute_job[8fc94f33-cf30-407f-b3be-6176c4018314] succeeded in 0.020661017999373144s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:37:30 INFO celery.app.trace Task connecthub.execute_job[8fc94f33-cf30-407f-b3be-6176c4018314] succeeded in 0.020661017999373144s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:38:30 WARNING celery.redirected 2026-01-05 06:38:30 INFO celery.app.trace Task connecthub.dispatcher.tick[41d0056f-c1c7-489c-a4be-d7b2c0f123f3] succeeded in 0.008013996999579831s: {'triggered': 1} +2026-01-05 06:38:30 INFO celery.app.trace Task connecthub.dispatcher.tick[41d0056f-c1c7-489c-a4be-d7b2c0f123f3] succeeded in 0.008013996999579831s: {'triggered': 1} +2026-01-05 06:38:30 WARNING celery.redirected 2026-01-05 06:38:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:38:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:38:30 WARNING celery.redirected 2026-01-05 06:38:30 INFO celery.app.trace Task connecthub.execute_job[18dca46f-541d-4e73-a1e3-6fbc9605b1af] succeeded in 0.01644616400153609s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:38:30 INFO celery.app.trace Task connecthub.execute_job[18dca46f-541d-4e73-a1e3-6fbc9605b1af] succeeded in 0.01644616400153609s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:39:30 WARNING celery.redirected 2026-01-05 06:39:30 INFO celery.app.trace Task connecthub.dispatcher.tick[a9621a07-ab74-430e-a398-98f226cb9789] succeeded in 0.02474335999977484s: {'triggered': 1} +2026-01-05 06:39:30 INFO celery.app.trace Task connecthub.dispatcher.tick[a9621a07-ab74-430e-a398-98f226cb9789] succeeded in 0.02474335999977484s: {'triggered': 1} +2026-01-05 06:39:30 WARNING celery.redirected 2026-01-05 06:39:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:39:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:39:31 WARNING celery.redirected 2026-01-05 06:39:31 ERROR celery.app.trace Task connecthub.execute_job[e20bbd35-f05f-4990-a452-a519b08fd445] raised unexpected: OperationalError('(sqlite3.OperationalError) table job_logs has no column named run_log') +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1967, in _exec_single_context + self.dialect.do_execute( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) +sqlite3.OperationalError: table job_logs has no column named run_log + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 479, in trace_task + R = retval = fun(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 779, in __protected_call__ + return self.run(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/app/app/tasks/execute.py", line 78, in execute_job + crud.create_job_log( + File "/app/app/db/crud.py", line 54, in create_job_log + session.commit() + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2030, in commit + trans.commit(_to_root=True) + File "", line 2, in commit + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go + ret_value = fn(self, *arg, **kw) + ^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 1311, in commit + self._prepare_impl() + File "", line 2, in _prepare_impl + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go + ret_value = fn(self, *arg, **kw) + ^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 1286, in _prepare_impl + self.session.flush() + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 4331, in flush + self._flush(objects) + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 4466, in _flush + with util.safe_reraise(): + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py", line 224, in __exit__ + raise exc_value.with_traceback(exc_tb) + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 4427, in _flush + flush_context.execute() + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute + rec.execute(self) + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute + util.preloaded.orm_persistence.save_obj( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py", line 93, in save_obj + _emit_insert_statements( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py", line 1233, in _emit_insert_statements + result = connection.execute( + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1419, in execute + return meth( + ^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 527, in _execute_on_connection + return connection._execute_clauseelement( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1641, in _execute_clauseelement + ret = self._execute_context( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1846, in _execute_context + return self._exec_single_context( + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1986, in _exec_single_context + self._handle_dbapi_exception( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2363, in _handle_dbapi_exception + raise sqlalchemy_exception.with_traceback(exc_info[2]) from e + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1967, in _exec_single_context + self.dialect.do_execute( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) +sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) table job_logs has no column named run_log +[SQL: INSERT INTO job_logs (job_id, status, snapshot_params, message, traceback, run_log, celery_task_id, attempt, started_at, finished_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)] +[parameters: ('demo.test', 'SUCCESS', '{"job_id": "demo.test", "handler_path": "extensions.example.job:ExampleJob", "public_cfg": {}, "secret_cfg": "", "meta": {"trigger": "celery", "celery_task_id": "e20bbd35-f05f-4990-a452-a519b08fd445", "started_at": "2026-01-05T06:39:30.925320"}}', 'OK', '', '', 'e20bbd35-f05f-4990-a452-a519b08fd445', 0, '2026-01-05 06:39:30.925320', '2026-01-05 06:39:30.988602')] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +2026-01-05 06:39:31 ERROR celery.app.trace Task connecthub.execute_job[e20bbd35-f05f-4990-a452-a519b08fd445] raised unexpected: OperationalError('(sqlite3.OperationalError) table job_logs has no column named run_log') +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1967, in _exec_single_context + self.dialect.do_execute( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) +sqlite3.OperationalError: table job_logs has no column named run_log + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 479, in trace_task + R = retval = fun(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/celery/app/trace.py", line 779, in __protected_call__ + return self.run(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/app/app/tasks/execute.py", line 78, in execute_job + crud.create_job_log( + File "/app/app/db/crud.py", line 54, in create_job_log + session.commit() + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2030, in commit + trans.commit(_to_root=True) + File "", line 2, in commit + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go + ret_value = fn(self, *arg, **kw) + ^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 1311, in commit + self._prepare_impl() + File "", line 2, in _prepare_impl + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go + ret_value = fn(self, *arg, **kw) + ^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 1286, in _prepare_impl + self.session.flush() + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 4331, in flush + self._flush(objects) + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 4466, in _flush + with util.safe_reraise(): + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py", line 224, in __exit__ + raise exc_value.with_traceback(exc_tb) + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 4427, in _flush + flush_context.execute() + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute + rec.execute(self) + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute + util.preloaded.orm_persistence.save_obj( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py", line 93, in save_obj + _emit_insert_statements( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/persistence.py", line 1233, in _emit_insert_statements + result = connection.execute( + ^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1419, in execute + return meth( + ^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 527, in _execute_on_connection + return connection._execute_clauseelement( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1641, in _execute_clauseelement + ret = self._execute_context( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1846, in _execute_context + return self._exec_single_context( + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1986, in _exec_single_context + self._handle_dbapi_exception( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2363, in _handle_dbapi_exception + raise sqlalchemy_exception.with_traceback(exc_info[2]) from e + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1967, in _exec_single_context + self.dialect.do_execute( + File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute + cursor.execute(statement, parameters) +sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) table job_logs has no column named run_log +[SQL: INSERT INTO job_logs (job_id, status, snapshot_params, message, traceback, run_log, celery_task_id, attempt, started_at, finished_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)] +[parameters: ('demo.test', 'SUCCESS', '{"job_id": "demo.test", "handler_path": "extensions.example.job:ExampleJob", "public_cfg": {}, "secret_cfg": "", "meta": {"trigger": "celery", "celery_task_id": "e20bbd35-f05f-4990-a452-a519b08fd445", "started_at": "2026-01-05T06:39:30.925320"}}', 'OK', '', '', 'e20bbd35-f05f-4990-a452-a519b08fd445', 0, '2026-01-05 06:39:30.925320', '2026-01-05 06:39:30.988602')] +(Background on this error at: https://sqlalche.me/e/20/e3q8) +2026-01-05 06:40:30 WARNING celery.redirected 2026-01-05 06:40:30 INFO celery.app.trace Task connecthub.dispatcher.tick[416c5cbb-fc83-4b86-84a4-7c865fb38fc5] succeeded in 0.028786763999960385s: {'triggered': 1} +2026-01-05 06:40:30 INFO celery.app.trace Task connecthub.dispatcher.tick[416c5cbb-fc83-4b86-84a4-7c865fb38fc5] succeeded in 0.028786763999960385s: {'triggered': 1} +2026-01-05 06:40:30 WARNING celery.redirected 2026-01-05 06:40:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:40:30 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:40:31 WARNING celery.redirected 2026-01-05 06:40:31 INFO celery.app.trace Task connecthub.execute_job[a97b077e-f751-4c1f-b054-0b1ddd4f24e6] succeeded in 0.07191970299936656s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:40:31 INFO celery.app.trace Task connecthub.execute_job[a97b077e-f751-4c1f-b054-0b1ddd4f24e6] succeeded in 0.07191970299936656s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:47:35 WARNING celery.redirected 2026-01-05 06:47:35 INFO celery.app.trace Task connecthub.dispatcher.tick[d4a2b347-35d3-415c-8fd0-ca42ccd7adcf] succeeded in 0.02985567900032038s: {'triggered': 1} +2026-01-05 06:47:35 INFO celery.app.trace Task connecthub.dispatcher.tick[d4a2b347-35d3-415c-8fd0-ca42ccd7adcf] succeeded in 0.02985567900032038s: {'triggered': 1} +2026-01-05 06:47:35 WARNING celery.redirected 2026-01-05 06:47:35 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:47:35 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:47:35 WARNING celery.redirected 2026-01-05 06:47:35 INFO celery.app.trace Task connecthub.execute_job[b689e3fb-7215-476a-af7f-18fe56560538] succeeded in 0.07674065700120991s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:47:35 INFO celery.app.trace Task connecthub.execute_job[b689e3fb-7215-476a-af7f-18fe56560538] succeeded in 0.07674065700120991s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:48:34 WARNING celery.redirected 2026-01-05 06:48:34 INFO celery.app.trace Task connecthub.dispatcher.tick[1410bb85-aada-4be4-8ee2-23597a82ba6b] succeeded in 0.031015997999929823s: {'triggered': 1} +2026-01-05 06:48:34 INFO celery.app.trace Task connecthub.dispatcher.tick[1410bb85-aada-4be4-8ee2-23597a82ba6b] succeeded in 0.031015997999929823s: {'triggered': 1} +2026-01-05 06:48:34 WARNING celery.redirected 2026-01-05 06:48:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:48:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:48:34 WARNING celery.redirected 2026-01-05 06:48:34 INFO celery.app.trace Task connecthub.execute_job[4966c84e-db3e-4c20-9b64-ca9c2dcdc2b2] succeeded in 0.07551704900106415s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:48:34 INFO celery.app.trace Task connecthub.execute_job[4966c84e-db3e-4c20-9b64-ca9c2dcdc2b2] succeeded in 0.07551704900106415s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:49:34 WARNING celery.redirected 2026-01-05 06:49:34 INFO celery.app.trace Task connecthub.dispatcher.tick[82c16974-155f-4758-a33d-4ad31d3b8bf0] succeeded in 0.01834348900047189s: {'triggered': 1} +2026-01-05 06:49:34 INFO celery.app.trace Task connecthub.dispatcher.tick[82c16974-155f-4758-a33d-4ad31d3b8bf0] succeeded in 0.01834348900047189s: {'triggered': 1} +2026-01-05 06:49:34 WARNING celery.redirected 2026-01-05 06:49:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:49:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:49:34 WARNING celery.redirected 2026-01-05 06:49:34 INFO celery.app.trace Task connecthub.execute_job[5870892b-a902-43fe-8354-4a8afb87afeb] succeeded in 0.02981461799936369s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:49:34 INFO celery.app.trace Task connecthub.execute_job[5870892b-a902-43fe-8354-4a8afb87afeb] succeeded in 0.02981461799936369s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:50:34 WARNING celery.redirected 2026-01-05 06:50:34 INFO celery.app.trace Task connecthub.dispatcher.tick[d16fc9c7-236d-438f-a85a-b5e18478f7ec] succeeded in 0.01405238100051065s: {'triggered': 1} +2026-01-05 06:50:34 INFO celery.app.trace Task connecthub.dispatcher.tick[d16fc9c7-236d-438f-a85a-b5e18478f7ec] succeeded in 0.01405238100051065s: {'triggered': 1} +2026-01-05 06:50:34 WARNING celery.redirected 2026-01-05 06:50:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:50:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:50:34 WARNING celery.redirected 2026-01-05 06:50:34 INFO celery.app.trace Task connecthub.execute_job[cd501a64-fbf8-469d-b350-d6a965f4dd47] succeeded in 0.030117811000309302s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:50:34 INFO celery.app.trace Task connecthub.execute_job[cd501a64-fbf8-469d-b350-d6a965f4dd47] succeeded in 0.030117811000309302s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:51:34 WARNING celery.redirected 2026-01-05 06:51:34 INFO celery.app.trace Task connecthub.dispatcher.tick[fdb68381-fc87-4fa1-b21d-e066250b3163] succeeded in 0.034325909000472166s: {'triggered': 1} +2026-01-05 06:51:34 INFO celery.app.trace Task connecthub.dispatcher.tick[fdb68381-fc87-4fa1-b21d-e066250b3163] succeeded in 0.034325909000472166s: {'triggered': 1} +2026-01-05 06:51:34 WARNING celery.redirected 2026-01-05 06:51:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:51:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:51:34 WARNING celery.redirected 2026-01-05 06:51:34 INFO celery.app.trace Task connecthub.execute_job[381a62de-4919-4703-b1a8-08851807e469] succeeded in 0.07938585500050976s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:51:34 INFO celery.app.trace Task connecthub.execute_job[381a62de-4919-4703-b1a8-08851807e469] succeeded in 0.07938585500050976s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:52:34 INFO celery.app.trace Task connecthub.dispatcher.tick[61695435-4e38-486f-b957-6bb0eb362964] succeeded in 0.034709497000221745s: {'triggered': 1} +2026-01-05 06:52:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:52:34 INFO celery.app.trace Task connecthub.execute_job[cefcf6a0-12c6-4602-a9ae-d3b7a6c5fc08] succeeded in 0.08768268300082127s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:52:37 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:52:37 INFO celery.app.trace Task connecthub.execute_job[d11f5928-0226-4462-8ed1-1b7b50cd8538] succeeded in 0.13126200300030177s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:53:34 INFO celery.app.trace Task connecthub.dispatcher.tick[ecc206a4-b328-4c56-b802-5df9fa55f565] succeeded in 0.018851400000130525s: {'triggered': 1} +2026-01-05 06:53:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:53:34 INFO celery.app.trace Task connecthub.execute_job[99945b54-2f4e-486d-b63c-7209c6ee7522] succeeded in 0.02669546899960551s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:54:34 INFO celery.app.trace Task connecthub.dispatcher.tick[23deecc3-768f-4d86-bd1e-c1e2382ec039] succeeded in 0.021215824001046713s: {'triggered': 1} +2026-01-05 06:54:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:54:34 INFO celery.app.trace Task connecthub.execute_job[3ac26e16-f0b8-431b-b79f-7d634493754a] succeeded in 0.035003455999685684s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:55:34 INFO celery.app.trace Task connecthub.dispatcher.tick[84b8a3c8-df58-405c-be21-5d0e01f6a1a8] succeeded in 0.05902058200081228s: {'triggered': 1} +2026-01-05 06:55:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:55:34 INFO celery.app.trace Task connecthub.execute_job[fa92d18c-7c68-489c-8c20-7af1ffe2736a] succeeded in 0.14188391500101716s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:56:34 INFO celery.app.trace Task connecthub.dispatcher.tick[9ff5237c-d685-465f-82b5-03cb3874c302] succeeded in 0.017588061000424204s: {'triggered': 1} +2026-01-05 06:56:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:56:34 INFO celery.app.trace Task connecthub.execute_job[ac76fb62-55a8-4f8b-a5f6-0e097c73afdc] succeeded in 0.023962750999999116s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:57:34 INFO celery.app.trace Task connecthub.dispatcher.tick[bede20a1-0595-41d9-9740-f67823afd365] succeeded in 0.031636444000469055s: {'triggered': 1} +2026-01-05 06:57:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:57:34 INFO celery.app.trace Task connecthub.execute_job[54f40c83-7540-4ecf-8aca-09c47e9c5c49] succeeded in 0.06714790099977108s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:57:43 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:57:43 INFO celery.app.trace Task connecthub.execute_job[6b0cd59a-b946-494c-ae11-2d775c02a179] succeeded in 0.09546945800138928s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} +2026-01-05 06:58:34 INFO celery.app.trace Task connecthub.dispatcher.tick[143002e6-4b46-4a31-b1f4-70675d23b19f] succeeded in 0.03738421500020195s: {'triggered': 1} +2026-01-05 06:58:34 INFO connecthub.extensions.example ExampleJob ran name=World pong={'ok': True} +2026-01-05 06:58:34 INFO celery.app.trace Task connecthub.execute_job[084d9592-8969-4ab9-b708-22df5f7c68c4] succeeded in 0.048200376999375294s: {'status': 'SUCCESS', 'job_id': 'demo.test', 'result': {'hello': 'World', 'pong': {'ok': True}}, 'message': 'OK'} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..59f2ca9 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,27 @@ +services: + backend: + environment: + DEV_MODE: "1" + volumes: + - ./app:/app/app + - ./extensions:/app/extensions + + worker: + environment: + DEV_MODE: "1" + volumes: + - ./app:/app/app + - ./extensions:/app/extensions + command: > + sh -c "watchfiles --filter python 'celery -A app.tasks.celery_app:celery_app worker --loglevel=INFO' /app/app /app/extensions" + + beat: + environment: + DEV_MODE: "1" + volumes: + - ./app:/app/app + - ./extensions:/app/extensions + command: > + sh -c "watchfiles --filter python 'celery -A app.tasks.celery_app:celery_app beat --loglevel=INFO' /app/app /app/extensions" + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..58bda61 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +services: + redis: + image: redis:7-alpine + ports: + - "6379:6379" + + backend: + build: + context: . + dockerfile: docker/Dockerfile + env_file: + - .env + volumes: + - ./data:/data + ports: + - "8000:8000" + command: > + sh -c "if [ \"${DEV_MODE:-0}\" = \"1\" ]; then uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload; else uvicorn app.main:app --host 0.0.0.0 --port 8000; fi" + depends_on: + - redis + + worker: + build: + context: . + dockerfile: docker/Dockerfile + env_file: + - .env + volumes: + - ./data:/data + command: > + sh -c "celery -A app.tasks.celery_app:celery_app worker --loglevel=INFO" + depends_on: + - redis + + beat: + build: + context: . + dockerfile: docker/Dockerfile + env_file: + - .env + volumes: + - ./data:/data + command: > + sh -c "celery -A app.tasks.celery_app:celery_app beat --loglevel=INFO" + depends_on: + - redis + + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..9efe533 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.11-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +COPY pyproject.toml /app/pyproject.toml + +RUN pip install --no-cache-dir -U pip && \ + pip install --no-cache-dir . + +COPY app /app/app +COPY extensions /app/extensions + +ENV PYTHONPATH=/app + + diff --git a/env.example b/env.example new file mode 100644 index 0000000..2903281 --- /dev/null +++ b/env.example @@ -0,0 +1,9 @@ +APP_NAME=ConnectHub +DATA_DIR=/data +DB_URL=sqlite:////data/connecthub.db +REDIS_URL=redis://redis:6379/0 +FERNET_KEY_PATH=/data/fernet.key +DEV_MODE=1 +LOG_DIR=/data/logs + + diff --git a/extensions/__init__.py b/extensions/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/extensions/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/extensions/__pycache__/__init__.cpython-312.pyc b/extensions/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41683f6fe3b9e8ad0be3ca9a82d1416f0be2606f GIT binary patch literal 119 zcmX@j%ge<81dsKiGg&}1h=UG3gJh;NR5EBX`mJOrVgm9$gVg*IORXqL%`48#&nwoC rkI&4@EQycTE2zB1VUwGmQks)$SHucb#t6j4AjU^#Mn=XWW*`dyK>Qd_ literal 0 HcmV?d00001 diff --git a/extensions/example/__init__.py b/extensions/example/__init__.py new file mode 100644 index 0000000..2c2a87c --- /dev/null +++ b/extensions/example/__init__.py @@ -0,0 +1,3 @@ +# + + diff --git a/extensions/example/__pycache__/__init__.cpython-312.pyc b/extensions/example/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e973ad3db1351bf253df453ae169096615435711 GIT binary patch literal 127 zcmX@j%ge<81TXZWGg*N2V-N=&d2KczG$)vkyYsGJdqi$RQ!%#4hTMa)1J08U98 AcK`qY literal 0 HcmV?d00001 diff --git a/extensions/example/__pycache__/client.cpython-312.pyc b/extensions/example/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac97ad4cac9f3f77e5c3f106f7fdfbffc9e708ce GIT binary patch literal 732 zcmZ8f&ubGw6rP#g#F#c9;x90V#aloU@GL?r#UF=Qs;BiZteI(9+-%a_4Yb8WnK4+ytnh-_w9VV&xu4FL3~f& z)XNy5T~Q1o5+=BJ4w6;G5W@z_V-;hO6NBU>RlU&YG<0q{xv)J%5+1mB3X)aCk%}2o3B#(yWJW47uu7?jn;4tX z@lJB(|!Lt|syEU)b@bAC$ns5EBn!mZwc~w=ejbtuQ zWX2}Om7q~kM#it~&X@Znot=93?vmGh;4d^_v|F$G>nq*Gjn4az?GNR>){?hf^>&^I zPwT<SUAd^6Q*%fEF(EC!4a2Z4U> zx(7ANridH0xKpxBS7v(70T*MGnp&Py>c%O}7g)&<`7ou^CCxavIYcQd7cW>RphehZp9J_JVT`|`Gd?=IAC2MU^3`7mj=uOGThHF| literal 0 HcmV?d00001 diff --git a/extensions/example/__pycache__/job.cpython-312.pyc b/extensions/example/__pycache__/job.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..027ffaba2173adb99fb19848416261776c103de5 GIT binary patch literal 1443 zcmaJ>O=ufO6rS0CNtSInO&!N^P@Dd#b!1B2OPmDcrVX@4J@im26PC4RB(1vIRcBX; zwJ2bU3o^90p$7%pg0ICr^q6aJfnF3Gs2MN>k{)_9IDz6*->kHj52bT>?|buR-p{=E z=8tqbiD13RuiD?p2t5@-ywm}}N4Ej=5J3bxsDVou3oJQONdhiAaziO8(VpU{4Xva_ zSatMAq-vZ_^Pg$<(1y*2t zu3rRMF1T%A`m*KIkG(a3=?~YfM$@4mI5u^IaIWmRE-eSO)>@vf2h{bℜfEYr6e4XxdTlwU^5oD1_zTlRL-`tY9Irf#IWnXH1sNYrL4G~d z!S!LD12+RoFp+kLj_=AH=|9ha`;#+DaN>igL?e1f`4Rmr+4xuEByOxhBbY7baJ@z?LYW_49F;4}EH^8BUVGioHa)ir94@I*@wg&(avGPbG+?57 zumf>$wd{C4<(lJFtCVrYb}L>^=Za6A3fGzzvl>3veOhKT@R=ASHYRRs<){T&1_WQc zNp`S~XGV<}r?yb{*7D6Zms!gV$43$8L7o)%zXq~_o=lwG4!6SlZ|uCeH}Q5?In+?* zTrYJmb^qqh>fYqLUG-V!V*jne!%zEWrC)XWl}7)T+yBISoN0EoLrG4|?w^_IE!|uC z;ac~`ld-Aom93R;i_eVl?bKGP|I*@bv>buQO!KRjawubr574Vm(f9#+`7iX!0eTN=n4IlS e|Cu=dC~>|&du1=Nw4puMGWhcM(=QOP$n3v dict: + # 真实情况:return self.get_json("/ping") + return {"ok": True} + + diff --git a/extensions/example/job.py b/extensions/example/job.py new file mode 100644 index 0000000..f1de62f --- /dev/null +++ b/extensions/example/job.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import logging +from typing import Any + +from app.jobs.base import BaseJob +from extensions.example.client import ExampleClient + + +logger = logging.getLogger("connecthub.extensions.example") + + +class ExampleJob(BaseJob): + job_id = "example.hello" + + def run(self, params: dict[str, Any], secrets: dict[str, Any]) -> dict[str, Any]: + # params: 明文配置,例如 {"name": "Mars"} + name = params.get("name", "World") + + # secrets: 解密后的明文,例如 {"token": "..."} + token = secrets.get("token", "") + + client = ExampleClient(base_url="https://baidu.com", headers={"Authorization": f"Bearer {token}"}) + try: + pong = client.ping() + + finally: + client.close() + + logger.info("ExampleJob ran name=%s pong=%s", name, pong) + return {"hello": name, "pong": pong} + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e37f559 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "connecthub" +version = "0.1.0" +description = "ConnectHub - lightweight enterprise integration middleware" +requires-python = ">=3.10" +dependencies = [ + "fastapi>=0.110", + "uvicorn[standard]>=0.27", + "sqladmin>=0.16.1", + "sqlalchemy>=2.0", + "pydantic>=2.6", + "pydantic-settings>=2.1", + "cryptography>=41", + "celery>=5.3,<6", + "redis>=5", + "croniter>=2.0", + "httpx>=0.26", + "jinja2>=3.1", + "watchfiles>=0.21", +] + +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] +include = ["app*", "extensions*"] + +[tool.uvicorn] +factory = false + +