recurvedata-lib 0.1.491__py2.py3-none-any.whl → 0.1.492__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of recurvedata-lib might be problematic. Click here for more details.
- recurvedata/__version__.py +1 -1
- recurvedata/client/client.py +94 -7
- recurvedata/connectors/config_schema.py +78 -0
- recurvedata/connectors/connectors/n8n.py +141 -0
- recurvedata/schedulers/airflow_db_process.py +15 -25
- {recurvedata_lib-0.1.491.dist-info → recurvedata_lib-0.1.492.dist-info}/METADATA +1 -1
- {recurvedata_lib-0.1.491.dist-info → recurvedata_lib-0.1.492.dist-info}/RECORD +9 -8
- {recurvedata_lib-0.1.491.dist-info → recurvedata_lib-0.1.492.dist-info}/WHEEL +0 -0
- {recurvedata_lib-0.1.491.dist-info → recurvedata_lib-0.1.492.dist-info}/entry_points.txt +0 -0
recurvedata/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.492"
|
recurvedata/client/client.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
3
4
|
import time
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from typing import Any, TypeVar, overload
|
|
5
7
|
|
|
6
8
|
import httpx
|
|
@@ -24,13 +26,90 @@ class Client:
|
|
|
24
26
|
config = AgentConfig.load()
|
|
25
27
|
self.set_config(config)
|
|
26
28
|
|
|
29
|
+
@property
|
|
30
|
+
def is_offline_mode(self) -> bool:
|
|
31
|
+
"""Check if offline mode is enabled via environment variable"""
|
|
32
|
+
return os.environ.get("RECURVE_OFFLINE_MODE", "").lower() in ("true", "1")
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def offline_data_path(self) -> Path:
|
|
36
|
+
"""Get the offline data directory path"""
|
|
37
|
+
offline_path = os.environ.get("RECURVE_OFFLINE_DATA_PATH", "offline_data")
|
|
38
|
+
return Path(offline_path)
|
|
39
|
+
|
|
27
40
|
def set_config(self, config: AgentConfig):
|
|
28
41
|
self._config = config
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
# Only create HTTP client if not in offline mode
|
|
43
|
+
if not self.is_offline_mode:
|
|
44
|
+
self._client = httpx.Client(
|
|
45
|
+
base_url=config.server_url,
|
|
46
|
+
timeout=config.request_timeout,
|
|
47
|
+
headers={"User-Agent": f"RecurveLib/{__version__}"},
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
self._client = None
|
|
51
|
+
|
|
52
|
+
def _resolve_offline_file_path(self, path: str, **kwargs) -> Path:
|
|
53
|
+
"""Convert API path to local file path with parameterized support"""
|
|
54
|
+
# Remove leading /api/ prefix: /api/executor/connection -> executor/connection
|
|
55
|
+
if path.startswith("/api/"):
|
|
56
|
+
clean_path = path[5:] # Remove "/api/" prefix
|
|
57
|
+
else:
|
|
58
|
+
clean_path = path.lstrip("/")
|
|
59
|
+
|
|
60
|
+
# Extract parameters from kwargs
|
|
61
|
+
params = kwargs.get("params", {})
|
|
62
|
+
|
|
63
|
+
# CORE OPERATOR APIs with parameter-based file structure:
|
|
64
|
+
|
|
65
|
+
# 1. get_connection() API - parameterized by project_id + connection_name
|
|
66
|
+
if clean_path == "executor/connection":
|
|
67
|
+
project_id = params.get("project_id", "0")
|
|
68
|
+
connection_name = params.get("name", "default")
|
|
69
|
+
return self.offline_data_path / "executor/connection" / str(project_id) / f"{connection_name}.json"
|
|
70
|
+
|
|
71
|
+
# 2. get_py_conn_configs() API - parameterized by project_id + project_connection_name
|
|
72
|
+
elif clean_path == "executor/python-conn-configs":
|
|
73
|
+
project_id = params.get("project_id", "0")
|
|
74
|
+
# Python configs use project_connection_name as the key (fallback to other param names for compatibility)
|
|
75
|
+
# Handle empty strings properly - treat them as None/missing
|
|
76
|
+
project_connection_name = (params.get("project_connection_name") or
|
|
77
|
+
params.get("project_conn_name") or
|
|
78
|
+
params.get("pyenv_name") or
|
|
79
|
+
"default")
|
|
80
|
+
return self.offline_data_path / "executor/python-conn-configs" / str(project_id) / f"{project_connection_name}.json"
|
|
81
|
+
|
|
82
|
+
# For any other APIs, raise error do not support offline mode
|
|
83
|
+
raise APIError(f"Offline mode: {path} is not supported")
|
|
84
|
+
|
|
85
|
+
def _read_offline_data(self, method: str, path: str, response_model_class: type[ResponseModelType] | None = None, **kwargs) -> Any:
|
|
86
|
+
"""Read API response from local JSON file"""
|
|
87
|
+
file_path = self._resolve_offline_file_path(path, **kwargs)
|
|
88
|
+
|
|
89
|
+
logger.info(f"🔌 Offline mode: Reading from {file_path}")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
if not file_path.exists():
|
|
93
|
+
logger.error(f"Offline data file not found: {file_path}")
|
|
94
|
+
raise APIError(f"Offline mode: Required data file not found: {file_path}")
|
|
95
|
+
|
|
96
|
+
with open(file_path, 'r') as f:
|
|
97
|
+
resp_content = json.load(f)
|
|
98
|
+
|
|
99
|
+
# Handle response model validation (same logic as online mode)
|
|
100
|
+
if response_model_class is not None:
|
|
101
|
+
if "code" in resp_content:
|
|
102
|
+
return response_model_class.model_validate(resp_content["data"])
|
|
103
|
+
return response_model_class.model_validate(resp_content)
|
|
104
|
+
|
|
105
|
+
return resp_content.get("data", resp_content)
|
|
106
|
+
|
|
107
|
+
except APIError:
|
|
108
|
+
raise # Re-raise APIError as-is
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"Error reading offline data from {file_path}: {e}")
|
|
111
|
+
raise APIError(f"Offline mode: Failed to read data file {file_path}: {e}")
|
|
112
|
+
|
|
34
113
|
|
|
35
114
|
@overload
|
|
36
115
|
def request(self, method: str, path: str, response_model_class: None = None, retries: int = 3, **kwargs) -> Any:
|
|
@@ -56,6 +135,11 @@ class Client:
|
|
|
56
135
|
retries: int = 1,
|
|
57
136
|
**kwargs,
|
|
58
137
|
) -> Any:
|
|
138
|
+
# Route to offline mode if enabled
|
|
139
|
+
if self.is_offline_mode:
|
|
140
|
+
return self._read_offline_data(method, path, response_model_class, **kwargs)
|
|
141
|
+
|
|
142
|
+
# Original online mode logic
|
|
59
143
|
self.prepare_header(kwargs)
|
|
60
144
|
pre_err: httpx.HTTPStatusError | None = None
|
|
61
145
|
for attempt in range(retries):
|
|
@@ -143,8 +227,11 @@ class Client:
|
|
|
143
227
|
)
|
|
144
228
|
|
|
145
229
|
def close(self):
|
|
146
|
-
self._client
|
|
230
|
+
if self._client:
|
|
231
|
+
self._client.close()
|
|
147
232
|
|
|
148
233
|
@property
|
|
149
234
|
def base_url(self) -> str:
|
|
150
|
-
|
|
235
|
+
if self.is_offline_mode:
|
|
236
|
+
return "offline://localhost"
|
|
237
|
+
return str(self._client.base_url) if self._client else ""
|
|
@@ -1073,6 +1073,84 @@ ALL_CONFIG_SCHEMA_DCT = {
|
|
|
1073
1073
|
"group": ["destination"],
|
|
1074
1074
|
"test_required": True,
|
|
1075
1075
|
},
|
|
1076
|
+
"recurvedata.connectors.connectors.n8n": {
|
|
1077
|
+
"type": "n8n",
|
|
1078
|
+
"ui_type": "n8n",
|
|
1079
|
+
"category": ["others"],
|
|
1080
|
+
"config_schema": {
|
|
1081
|
+
"type": "object",
|
|
1082
|
+
"properties": {
|
|
1083
|
+
"url": {
|
|
1084
|
+
"type": "string",
|
|
1085
|
+
"title": _l("API Address"),
|
|
1086
|
+
"description": _l("The URL of the n8n API, e.g. https://localhost:5678/api/v1"),
|
|
1087
|
+
},
|
|
1088
|
+
"api_key": {"type": "string", "title": _l("API KEY")},
|
|
1089
|
+
"timeout": {
|
|
1090
|
+
"type": "number",
|
|
1091
|
+
"title": _l("Timeout"),
|
|
1092
|
+
"description": _l("The timeout of the n8n API, e.g. 60"),
|
|
1093
|
+
"default": 60,
|
|
1094
|
+
},
|
|
1095
|
+
"webhook_credential": {
|
|
1096
|
+
"type": "object",
|
|
1097
|
+
"title": _l("Webhook Trigger Node Credential"),
|
|
1098
|
+
"description": _l("The credential of the n8n webhook trigger node"),
|
|
1099
|
+
"properties": {
|
|
1100
|
+
"credential_type": {
|
|
1101
|
+
"type": "string",
|
|
1102
|
+
"title": _l("Credential Type"),
|
|
1103
|
+
"enum": ["Basic Auth", "Header Auth", "JWT Auth", "None"],
|
|
1104
|
+
"enumNames": ["Basic Auth", "Header Auth", "JWT Auth", "None"],
|
|
1105
|
+
"default": "None",
|
|
1106
|
+
},
|
|
1107
|
+
"basic_auth": {
|
|
1108
|
+
"ui:hidden": '{{parentFormData.credential_type !== "Basic Auth"}}',
|
|
1109
|
+
"type": "object",
|
|
1110
|
+
"title": _l("Basic Auth"),
|
|
1111
|
+
"description": _l("The basic auth of the n8n webhook trigger node"),
|
|
1112
|
+
"properties": {
|
|
1113
|
+
"username": {"type": "string", "title": _l("Username")},
|
|
1114
|
+
"password": {"type": "string", "title": _l("Password")},
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
1117
|
+
"header_auth": {
|
|
1118
|
+
"ui:hidden": '{{parentFormData.credential_type !== "Header Auth"}}',
|
|
1119
|
+
"type": "object",
|
|
1120
|
+
"title": _l("Header Auth"),
|
|
1121
|
+
"description": _l("The header auth of the n8n webhook trigger node"),
|
|
1122
|
+
"properties": {
|
|
1123
|
+
"header_name": {"type": "string", "title": _l("Header Name")},
|
|
1124
|
+
"header_value": {"type": "string", "title": _l("Header Value")},
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
"jwt_auth": {
|
|
1128
|
+
"ui:hidden": '{{parentFormData.credential_type !== "JWT Auth"}}',
|
|
1129
|
+
"type": "object",
|
|
1130
|
+
"title": _l("JWT Auth"),
|
|
1131
|
+
"description": _l("The jwt auth of the n8n webhook trigger node"),
|
|
1132
|
+
"properties": {
|
|
1133
|
+
"jwt_token": {"type": "string", "title": _l("JWT Token")},
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
},
|
|
1137
|
+
"order": ["credential_type", "basic_auth", "header_auth", "jwt_auth"],
|
|
1138
|
+
},
|
|
1139
|
+
"proxies": HTTP_PROXY_CONFIG_SCHEMA["proxies"],
|
|
1140
|
+
},
|
|
1141
|
+
"order": ["url", "api_key", "timeout", "webhook_credential", "proxies"],
|
|
1142
|
+
"required": ["url", "api_key"],
|
|
1143
|
+
"secret": [
|
|
1144
|
+
"api_key",
|
|
1145
|
+
"webhook_credential.basic_auth.password",
|
|
1146
|
+
"webhook_credential.header_auth.header_value",
|
|
1147
|
+
"webhook_credential.jwt_auth.jwt_token",
|
|
1148
|
+
],
|
|
1149
|
+
},
|
|
1150
|
+
"enabled": True,
|
|
1151
|
+
"group": ["destination"],
|
|
1152
|
+
"test_required": True,
|
|
1153
|
+
},
|
|
1076
1154
|
"recurvedata.connectors.connectors.oss": {
|
|
1077
1155
|
"type": "oss",
|
|
1078
1156
|
"ui_type": "Aliyun OSS",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from recurvedata.connectors._register import register_connector_class
|
|
8
|
+
from recurvedata.connectors.base import RecurveConnectorBase
|
|
9
|
+
from recurvedata.connectors.proxy import HTTP_PROXY_CONFIG_SCHEMA, HttpProxyMixin
|
|
10
|
+
from recurvedata.consts import ConnectorGroup
|
|
11
|
+
from recurvedata.core.translation import _l
|
|
12
|
+
|
|
13
|
+
CONNECTION_TYPE = "n8n"
|
|
14
|
+
UI_CONNECTION_TYPE = "n8n"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register_connector_class([CONNECTION_TYPE, UI_CONNECTION_TYPE])
|
|
18
|
+
class N8N(HttpProxyMixin, RecurveConnectorBase):
|
|
19
|
+
connection_type = CONNECTION_TYPE
|
|
20
|
+
ui_connection_type = UI_CONNECTION_TYPE
|
|
21
|
+
group = [ConnectorGroup.DESTINATION]
|
|
22
|
+
|
|
23
|
+
config_schema = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"url": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"title": _l("API Address"),
|
|
29
|
+
"description": _l("The URL of the n8n API, e.g. https://localhost:5678/api/v1"),
|
|
30
|
+
},
|
|
31
|
+
"api_key": {"type": "string", "title": _l("API KEY")},
|
|
32
|
+
"timeout": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"title": _l("Timeout"),
|
|
35
|
+
"description": _l("The timeout of the n8n API, e.g. 60"),
|
|
36
|
+
"default": 60,
|
|
37
|
+
},
|
|
38
|
+
"webhook_credential": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"title": _l("Webhook Trigger Node Credential"),
|
|
41
|
+
"description": _l("The credential of the n8n webhook trigger node"),
|
|
42
|
+
"properties": {
|
|
43
|
+
"credential_type": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"title": _l("Credential Type"),
|
|
46
|
+
"enum": ["Basic Auth", "Header Auth", "JWT Auth", "None"],
|
|
47
|
+
"enumNames": ["Basic Auth", "Header Auth", "JWT Auth", "None"],
|
|
48
|
+
"default": "None",
|
|
49
|
+
},
|
|
50
|
+
"basic_auth": {
|
|
51
|
+
"ui:hidden": '{{parentFormData.credential_type !== "Basic Auth"}}',
|
|
52
|
+
"type": "object",
|
|
53
|
+
"title": _l("Basic Auth"),
|
|
54
|
+
"description": _l("The basic auth of the n8n webhook trigger node"),
|
|
55
|
+
"properties": {
|
|
56
|
+
"username": {"type": "string", "title": _l("Username")},
|
|
57
|
+
"password": {"type": "string", "title": _l("Password")},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
"header_auth": {
|
|
61
|
+
"ui:hidden": '{{parentFormData.credential_type !== "Header Auth"}}',
|
|
62
|
+
"type": "object",
|
|
63
|
+
"title": _l("Header Auth"),
|
|
64
|
+
"description": _l("The header auth of the n8n webhook trigger node"),
|
|
65
|
+
"properties": {
|
|
66
|
+
"header_name": {"type": "string", "title": _l("Header Name")},
|
|
67
|
+
"header_value": {"type": "string", "title": _l("Header Value")},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
"jwt_auth": {
|
|
71
|
+
"ui:hidden": '{{parentFormData.credential_type !== "JWT Auth"}}',
|
|
72
|
+
"type": "object",
|
|
73
|
+
"title": _l("JWT Auth"),
|
|
74
|
+
"description": _l("The jwt auth of the n8n webhook trigger node"),
|
|
75
|
+
"properties": {
|
|
76
|
+
"jwt_token": {"type": "string", "title": _l("JWT Token")},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
"order": ["credential_type", "basic_auth", "header_auth", "jwt_auth"],
|
|
81
|
+
},
|
|
82
|
+
"proxies": HTTP_PROXY_CONFIG_SCHEMA["proxies"],
|
|
83
|
+
},
|
|
84
|
+
"order": ["url", "api_key", "timeout", "webhook_credential", "proxies"],
|
|
85
|
+
"required": ["url", "api_key"],
|
|
86
|
+
"secret": [
|
|
87
|
+
"api_key",
|
|
88
|
+
"webhook_credential.basic_auth.password",
|
|
89
|
+
"webhook_credential.header_auth.header_value",
|
|
90
|
+
"webhook_credential.jwt_auth.jwt_token",
|
|
91
|
+
],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
def test_connection(self):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
@contextmanager
|
|
98
|
+
def _n8n_client(self) -> httpx.Client:
|
|
99
|
+
with self._init_proxy_manager():
|
|
100
|
+
yield httpx.Client(
|
|
101
|
+
base_url=f"{self.url}", headers={"X-N8N-API-KEY": f"{self.api_key}"}, timeout=self.timeout
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def get_workflows(self) -> list[dict]:
|
|
105
|
+
path = "/workflows"
|
|
106
|
+
workflows = []
|
|
107
|
+
cursor = None
|
|
108
|
+
with self._n8n_client() as client:
|
|
109
|
+
response = client.get(path)
|
|
110
|
+
workflows.extend(response.json()["data"])
|
|
111
|
+
if response.json()["nextCursor"] and response.json()["nextCursor"] != cursor:
|
|
112
|
+
cursor = response.json()["nextCursor"]
|
|
113
|
+
while cursor:
|
|
114
|
+
response = client.get(path, params={"cursor": cursor})
|
|
115
|
+
workflows.extend(response.json()["data"])
|
|
116
|
+
cursor = response.json()["nextCursor"]
|
|
117
|
+
return workflows
|
|
118
|
+
|
|
119
|
+
def _trigger_workflow_via_webhook(self, webhook_id: str, payload: dict) -> dict:
|
|
120
|
+
main_url = f"{urlparse(self.url).scheme}://{urlparse(self.url).netloc}"
|
|
121
|
+
webhook_url = f"{main_url}/webhook/{webhook_id}"
|
|
122
|
+
headers = {}
|
|
123
|
+
credential_type = self.webhook_credential.get("credential_type")
|
|
124
|
+
basic_auth = self.webhook_credential.get("basic_auth", {})
|
|
125
|
+
header_auth = self.webhook_credential.get("header_auth", {})
|
|
126
|
+
jwt_auth = self.webhook_credential.get("jwt_auth", {})
|
|
127
|
+
with self._init_proxy_manager():
|
|
128
|
+
if credential_type == "Basic Auth":
|
|
129
|
+
username = basic_auth.get("username", "")
|
|
130
|
+
password = basic_auth.get("password", "")
|
|
131
|
+
headers["Authorization"] = f'Basic {base64.b64encode(f"{username}:{password}".encode()).decode()}'
|
|
132
|
+
elif credential_type == "Header Auth":
|
|
133
|
+
header_name = header_auth.get("header_name", "")
|
|
134
|
+
header_value = header_auth.get("header_value", "")
|
|
135
|
+
headers[header_name] = header_value
|
|
136
|
+
elif credential_type == "JWT Auth":
|
|
137
|
+
jwt_token = jwt_auth.get("jwt_token", "")
|
|
138
|
+
headers["Authorization"] = f"Bearer {jwt_token}"
|
|
139
|
+
response = httpx.post(url=webhook_url, headers=headers, timeout=self.timeout, json=payload)
|
|
140
|
+
|
|
141
|
+
return response.json()
|
|
@@ -148,15 +148,12 @@ class AirflowDbService:
|
|
|
148
148
|
max_execution_date: datetime.datetime | None,
|
|
149
149
|
failed_only: bool,
|
|
150
150
|
):
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if min_execution_date or max_execution_date:
|
|
158
|
-
kwargs.pop("run_id")
|
|
159
|
-
drs: list[DagRun] = DagRun.find(**kwargs)
|
|
151
|
+
drs: list[DagRun] = DagRun.find(
|
|
152
|
+
dag_id=dag.dag_id,
|
|
153
|
+
run_id=run_id,
|
|
154
|
+
execution_start_date=min_execution_date,
|
|
155
|
+
execution_end_date=max_execution_date,
|
|
156
|
+
)
|
|
160
157
|
if not drs:
|
|
161
158
|
logger.info(f"skip rerun, no dag_run found for {dag.dag_id} at {run_id}")
|
|
162
159
|
return
|
|
@@ -185,19 +182,14 @@ class AirflowDbService:
|
|
|
185
182
|
include_downstream: bool,
|
|
186
183
|
failed_only: bool,
|
|
187
184
|
):
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if min_execution_date or max_execution_date:
|
|
195
|
-
kwargs.pop("run_id")
|
|
196
|
-
drs: list[DagRun] = DagRun.find(**kwargs)
|
|
185
|
+
drs: list[DagRun] = DagRun.find(
|
|
186
|
+
dag_id=dag.dag_id,
|
|
187
|
+
run_id=run_id,
|
|
188
|
+
execution_start_date=min_execution_date,
|
|
189
|
+
execution_end_date=max_execution_date,
|
|
190
|
+
)
|
|
197
191
|
if not drs:
|
|
198
|
-
logger.info(
|
|
199
|
-
f"skip rerun, no dag_run found for {dag.dag_id} at {run_id}, or between {min_execution_date} and {max_execution_date}"
|
|
200
|
-
)
|
|
192
|
+
logger.info(f"skip rerun, no dag_run found for {dag.dag_id} at {run_id}")
|
|
201
193
|
return
|
|
202
194
|
clear_start_date = min([dr.execution_date for dr in drs])
|
|
203
195
|
clear_end_date = max([dr.execution_date for dr in drs])
|
|
@@ -213,16 +205,14 @@ class AirflowDbService:
|
|
|
213
205
|
for task_id in clear_task_ids:
|
|
214
206
|
if task_id in dag.task_dict:
|
|
215
207
|
task = dag.task_dict[task_id]
|
|
216
|
-
|
|
217
|
-
upstream_task_ids = task.get_flat_relative_ids(upstream=True)
|
|
208
|
+
upstream_task_ids = [t.task_id for t in task.upstream_list]
|
|
218
209
|
expanded_task_ids.update(upstream_task_ids)
|
|
219
210
|
|
|
220
211
|
if include_downstream:
|
|
221
212
|
for task_id in clear_task_ids:
|
|
222
213
|
if task_id in dag.task_dict:
|
|
223
214
|
task = dag.task_dict[task_id]
|
|
224
|
-
|
|
225
|
-
downstream_task_ids = task.get_flat_relative_ids(upstream=False)
|
|
215
|
+
downstream_task_ids = [t.task_id for t in task.downstream_list]
|
|
226
216
|
expanded_task_ids.update(downstream_task_ids)
|
|
227
217
|
|
|
228
218
|
clear_task_ids = list(expanded_task_ids)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
recurvedata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
recurvedata/__version__.py,sha256=
|
|
2
|
+
recurvedata/__version__.py,sha256=nLCZ9XH8tY-NsC6qhMGzxaKDlG3iuUYkf6rh-HS8yLo,24
|
|
3
3
|
recurvedata/config.py,sha256=rbpccM6qr8ekdEC5p7XtsivayxmL64-Nb9ogrcWMgX8,3848
|
|
4
4
|
recurvedata/consts.py,sha256=y5BuAHBrz1jAcS5NgZxnrkfomQv3_5hvgafYwpLKpV8,1224
|
|
5
5
|
recurvedata/error_codes.py,sha256=y4OLrs0_2iLWdvQJEV10m-414uPkUdm4v0D7bE8iWOM,2303
|
|
@@ -7,12 +7,12 @@ recurvedata/exceptions.py,sha256=-Jtm1MXk06ViNOP176MRELFOujjYkZI_IkZY4hzwhRo,187
|
|
|
7
7
|
recurvedata/provider_manager.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
recurvedata/schema.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
recurvedata/client/__init__.py,sha256=FnX9HH-2dXADluNfucg98JPMfruMoBpN9ER9lZkVQvQ,49
|
|
10
|
-
recurvedata/client/client.py,sha256=
|
|
10
|
+
recurvedata/client/client.py,sha256=UPbHZxb8q6l_pnwEZaD8nEFj_HMejBlhiLlWZAEqE3U,9806
|
|
11
11
|
recurvedata/client/server_client.py,sha256=bZ55S_tk_fI3JDLU3txha2HKbS4hKUG6jLehj3HnQc0,3033
|
|
12
12
|
recurvedata/connectors/__init__.py,sha256=1VpGyGu9FA7lAvKZv0Z8j9ZzSi4i-L3_PyLcxdbrfs4,577
|
|
13
13
|
recurvedata/connectors/_register.py,sha256=7NYVIJk9PufhTJWyj7JkPt9p66Cc1ieCBPpFi24cMwo,1639
|
|
14
14
|
recurvedata/connectors/base.py,sha256=tuCUq2hij8GknWUKs1sP8HC_JZQie2csxoy3s-7Bb_4,3714
|
|
15
|
-
recurvedata/connectors/config_schema.py,sha256=
|
|
15
|
+
recurvedata/connectors/config_schema.py,sha256=iGiZ-uKwMHM4LGlrlHAVdIpHoJ148wDa1L53HHKSH6k,62286
|
|
16
16
|
recurvedata/connectors/const.py,sha256=3Zl4wZ0AP6t9x63QoRM6GMG6ZDgTQW-JacOv7lJBcwI,10153
|
|
17
17
|
recurvedata/connectors/datasource.py,sha256=w09SskSeNJjNc7qUEp0N3UV-YnMX2flzTutg1BaLqO0,5573
|
|
18
18
|
recurvedata/connectors/dbapi.py,sha256=tmmk7JdecI6NMu_HiLdxWeRpZjX2B4t1RY10SytwBNU,14173
|
|
@@ -46,6 +46,7 @@ recurvedata/connectors/connectors/microsoft_fabric.py,sha256=NgEDZp1hRV0r_bg67HM
|
|
|
46
46
|
recurvedata/connectors/connectors/mongo.py,sha256=5-ql_2o0BP7SpRg2dN5mQbG40Eb0nMqXH0getXuaw6c,2468
|
|
47
47
|
recurvedata/connectors/connectors/mssql.py,sha256=jScErON-mZ04atRqcoOE9AChvdAELRrzI2NHf8Gg4JI,4353
|
|
48
48
|
recurvedata/connectors/connectors/mysql.py,sha256=NYVkTKqDEQA4_Vtp9L3RUFWiM1B3ON7KSq0zoquXHCQ,6438
|
|
49
|
+
recurvedata/connectors/connectors/n8n.py,sha256=dJ_J0HSvEChNtfLYZenwQyIb6v2WxKVbh2NggSptGUU,6419
|
|
49
50
|
recurvedata/connectors/connectors/oss.py,sha256=CTb8_zkXhc_Y6uJc5HkP87Ea5lA7pKUe_iIeDyz0WSM,2779
|
|
50
51
|
recurvedata/connectors/connectors/owncloud.py,sha256=hvi0sr164SpXaQ4GrIVjBKZeZPJ9mhO82dzohiYd1SI,1261
|
|
51
52
|
recurvedata/connectors/connectors/phoenix.py,sha256=v4lAdyDaot5nIYg-2V-uqzHnn6AAp5EINTNpzW_USVM,1069
|
|
@@ -255,7 +256,7 @@ recurvedata/providers/flywheel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
255
256
|
recurvedata/providers/mysql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
256
257
|
recurvedata/schedulers/__init__.py,sha256=euiId3-BC5JpPmXb2gSzXBycfLMCJyvjn3c5D2ZqK8U,21
|
|
257
258
|
recurvedata/schedulers/airflow.py,sha256=lX3nc-m2vrwuNuSABL560oaMR6t9G5mwhrKCExmrQDw,36198
|
|
258
|
-
recurvedata/schedulers/airflow_db_process.py,sha256=
|
|
259
|
+
recurvedata/schedulers/airflow_db_process.py,sha256=_WxtWg5uFQrGV3JeXWWl2jxAZQ7bnXmvMCyXSCNqycw,13459
|
|
259
260
|
recurvedata/schedulers/airflow_operators.py,sha256=-AfLwT9Oi8_JDCclc2XhspZ13cbTcu6ftj6yBw2-AWw,2011
|
|
260
261
|
recurvedata/schedulers/airflow_plugin.py,sha256=aUE1YxXJuMR4hL2hJ1XailBw3wA-V_ljYBVQz39MvYE,242
|
|
261
262
|
recurvedata/schedulers/airflow_trigger_dag_patch.py,sha256=bUVYRYDozgYeLcpQEmLZsNKoFbVS2PvuSwnpbIyTS0U,4300
|
|
@@ -326,7 +327,7 @@ recurvedata/utils/singleton.py,sha256=15PaK2nP9H5PyO26IZzQPpfzlW5h_Bp1NHA6QPb4H0
|
|
|
326
327
|
recurvedata/utils/sql.py,sha256=u3XRPv8_vsrMFMm-O1xyV63ZXChAFVHmJj2_xbRwcNg,264
|
|
327
328
|
recurvedata/utils/timeout.py,sha256=U5ssSgoyVRqop9P8vmyI3BJI-OnMH2k22PdzTh-JN4c,780
|
|
328
329
|
recurvedata/utils/tracing.py,sha256=gpK8q00ZjZmI81YpgQtDBPLzBvVSYpPA0sIq4wqnvBc,472
|
|
329
|
-
recurvedata_lib-0.1.
|
|
330
|
-
recurvedata_lib-0.1.
|
|
331
|
-
recurvedata_lib-0.1.
|
|
332
|
-
recurvedata_lib-0.1.
|
|
330
|
+
recurvedata_lib-0.1.492.dist-info/METADATA,sha256=4t9YLZ1Kg0mYym7vIsUURwNgtxqTH5FXx-V2HlIMqLU,27743
|
|
331
|
+
recurvedata_lib-0.1.492.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
332
|
+
recurvedata_lib-0.1.492.dist-info/entry_points.txt,sha256=4KBBIfooz3wqXBoLlidRRP4_r36JUCnIF4BFn4igtms,209
|
|
333
|
+
recurvedata_lib-0.1.492.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|