recurvedata-lib 0.1.487__py2.py3-none-any.whl → 0.1.489__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.

@@ -1 +1 @@
1
- __version__ = "0.1.487"
1
+ __version__ = "0.1.489"
@@ -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
- self._client = httpx.Client(
30
- base_url=config.server_url,
31
- timeout=config.request_timeout,
32
- headers={"User-Agent": f"RecurveLib/{__version__}"},
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.close()
230
+ if self._client:
231
+ self._client.close()
147
232
 
148
233
  @property
149
234
  def base_url(self) -> str:
150
- return str(self._client.base_url)
235
+ if self.is_offline_mode:
236
+ return "offline://localhost"
237
+ return str(self._client.base_url) if self._client else ""
@@ -26,7 +26,7 @@ class Config:
26
26
  """load default config from env file or environment variables
27
27
 
28
28
  file format:
29
- RECURVE_HOST = https://recurve.test.recurvedata.com
29
+ RECURVE_HOST = https://abc.test.domain.com
30
30
  RECURVE_USERNAME = foo
31
31
  RECURVE_PASSWORD = pwd123
32
32
  """
@@ -181,7 +181,7 @@ class BaseTask(Configurable, LineageTaskMixin):
181
181
 
182
182
  @property
183
183
  def node_url(self) -> str:
184
- # https://dev-test.recurve.test.recurvedata.com/datawork/workflow?p_id=257942399102349312&wf_id=258282502478635008&open_drawer=true&node_key=D2f0I
184
+ # https://abc.env.name.domain.com/datawork/workflow?p_id=257942399102349312&wf_id=258282502478635008&open_drawer=true&node_key=D2f0I
185
185
  host = context.client.base_url # todo: correct it
186
186
  query_string = urllib.parse.urlencode(
187
187
  {"p_id": self.dag.project_id, "job_id": self.dag.id, "node_key": self.node.node_key, "open_drawer": "true"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: recurvedata-lib
3
- Version: 0.1.487
3
+ Version: 0.1.489
4
4
  Summary: Common Library for ReOrc Data Platform
5
5
  Author-email: Reorc Team <contact@recurvedata.com>
6
6
  Requires-Dist: croniter
@@ -475,7 +475,7 @@ Description-Content-Type: text/markdown
475
475
  # Recurve Libraries
476
476
 
477
477
  For the unified maintenance of public components of the Recurve platform,
478
- these codes may be used in both Server and Executor (Worker) environments.
478
+ these codes may be used in both Server and Executor (Worker) environments.
479
479
 
480
480
  Only Python 3.11+ are supported.
481
481
 
@@ -1,5 +1,5 @@
1
1
  recurvedata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- recurvedata/__version__.py,sha256=yhvi5Voc2mSIouYoit9zrP7L08HTVpHsrqWbnKjfeJc,24
2
+ recurvedata/__version__.py,sha256=KcgDrc3wKsVLpqRlWw_1e6TjrE8IKowZeZI1oUhbiQ8,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,7 +7,7 @@ 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=Z3JccGzQbRoWr0qnfvUam2_7zxOnyFSn1wV5A5F6jUI,5613
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
@@ -61,7 +61,7 @@ recurvedata/connectors/connectors/starrocks.py,sha256=IdrlcKz0vUASokdR4QVjZqCXoV
61
61
  recurvedata/connectors/connectors/tencent_cos.py,sha256=1f_31aNW8lznuygE_N7tQbK9PToGIRUkFnmhHSRmq54,1392
62
62
  recurvedata/connectors/connectors/tidb.py,sha256=2VkZ8x6fhmbcGRc7ekTwOADGgWiRFVHr4tnzuQVfqDU,1924
63
63
  recurvedata/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
- recurvedata/core/config.py,sha256=UG0Wp5_wT7S4YjMwR0kcCtSPC60zQomEmBXCjQly6iw,1445
64
+ recurvedata/core/config.py,sha256=_wmsWpPNgZE-mUSn1u3C7m4Zf5j2XEhZLhtUY5llbYo,1436
65
65
  recurvedata/core/configurable.py,sha256=RekN9mY46Sb_IPLW5aciNg0xF8E47CtMbHPOsar3tfU,719
66
66
  recurvedata/core/consts.py,sha256=JkfBRC5CDHEt_TrCOc1p0qKOHKmVMlaSMMme9pubRSs,64
67
67
  recurvedata/core/templating.py,sha256=jHS7P-83Hh3P2WonaYWvXzgrjRQPvtd7AIzMngkrV0Q,7835
@@ -104,7 +104,7 @@ recurvedata/operators/config.py,sha256=kVTo654oztn3qFSwQjYNkNjPMJgWa1AxkbuvfyF8g
104
104
  recurvedata/operators/context.py,sha256=-qIvV9fJj0yK4UjBMKcn5q84nRH5SR5X7AD5IeZB-08,10470
105
105
  recurvedata/operators/models.py,sha256=MAwzBcYId5uD1WaZzfSd-M06nMl6E4P5d42OL23dH6Y,1389
106
106
  recurvedata/operators/operator.py,sha256=YnJ8xhof7jTcsx5IYgKv4il6c475Y1urtaxsRntW0cw,4001
107
- recurvedata/operators/task.py,sha256=O0DsxvGEK6mTCZ-gyY1BVcXGVVrOT9Qt1WKsFDcV8QI,7093
107
+ recurvedata/operators/task.py,sha256=98X-PzkGjuGYqFpSxhxVh_c7u6c4tx7R2r_sxhyRmyQ,7079
108
108
  recurvedata/operators/ui.py,sha256=n74j-mMtg4UhuDoqU1GWzGmjayFFL7yWgKGNLiQulNw,2488
109
109
  recurvedata/operators/web_init.py,sha256=evZHlPAp5orVsq8OrB6wwCWEzioYrpB5Id0JpQqYQwA,470
110
110
  recurvedata/operators/dbt_operator/__init__.py,sha256=svrFCLtewN3V8536sgt7QPQoloDy6XkpV1wnmdEVFUg,170
@@ -327,7 +327,7 @@ recurvedata/utils/singleton.py,sha256=15PaK2nP9H5PyO26IZzQPpfzlW5h_Bp1NHA6QPb4H0
327
327
  recurvedata/utils/sql.py,sha256=u3XRPv8_vsrMFMm-O1xyV63ZXChAFVHmJj2_xbRwcNg,264
328
328
  recurvedata/utils/timeout.py,sha256=U5ssSgoyVRqop9P8vmyI3BJI-OnMH2k22PdzTh-JN4c,780
329
329
  recurvedata/utils/tracing.py,sha256=gpK8q00ZjZmI81YpgQtDBPLzBvVSYpPA0sIq4wqnvBc,472
330
- recurvedata_lib-0.1.487.dist-info/METADATA,sha256=HVJ5n1h7CETueBwCfCgKDFPd0J8FstKITxCEG-1R20A,27744
331
- recurvedata_lib-0.1.487.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
332
- recurvedata_lib-0.1.487.dist-info/entry_points.txt,sha256=4KBBIfooz3wqXBoLlidRRP4_r36JUCnIF4BFn4igtms,209
333
- recurvedata_lib-0.1.487.dist-info/RECORD,,
330
+ recurvedata_lib-0.1.489.dist-info/METADATA,sha256=Tym9XRSo1-9lyjptdFdeUHPlg6GVWd3WiZmPRZPWjJQ,27743
331
+ recurvedata_lib-0.1.489.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
332
+ recurvedata_lib-0.1.489.dist-info/entry_points.txt,sha256=4KBBIfooz3wqXBoLlidRRP4_r36JUCnIF4BFn4igtms,209
333
+ recurvedata_lib-0.1.489.dist-info/RECORD,,