dc-python-sdk 1.5.23__tar.gz → 1.5.24__tar.gz

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.
Files changed (35) hide show
  1. {dc_python_sdk-1.5.23/src/dc_python_sdk.egg-info → dc_python_sdk-1.5.24}/PKG-INFO +1 -1
  2. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/pyproject.toml +1 -1
  3. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/setup.cfg +1 -1
  4. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24/src/dc_python_sdk.egg-info}/PKG-INFO +1 -1
  5. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/cli.py +3 -4
  6. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/ai_http.py +197 -168
  7. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/server.py +17 -7
  8. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/LICENSE +0 -0
  9. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/README.md +0 -0
  10. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/SOURCES.txt +0 -0
  11. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/dependency_links.txt +0 -0
  12. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/entry_points.txt +0 -0
  13. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/requires.txt +0 -0
  14. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/top_level.txt +0 -0
  15. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/__init__.py +0 -0
  16. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/app.py +0 -0
  17. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/errors.py +0 -0
  18. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/handler.py +0 -0
  19. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/__init__.py +0 -0
  20. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/ai.py +0 -0
  21. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/mapping.py +0 -0
  22. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/__init__.py +0 -0
  23. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/enums.py +0 -0
  24. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/errors.py +0 -0
  25. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/log_templates.py +0 -0
  26. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/pipeline_details.py +0 -0
  27. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/pipeline.py +0 -0
  28. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/__init__.py +0 -0
  29. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/api.py +0 -0
  30. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/aws.py +0 -0
  31. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/environment.py +0 -0
  32. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/loader.py +0 -0
  33. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/logger.py +0 -0
  34. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/session.py +0 -0
  35. {dc_python_sdk-1.5.23 → dc_python_sdk-1.5.24}/src/dc_sdk/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dc-python-sdk
3
- Version: 1.5.23
3
+ Version: 1.5.24
4
4
  Summary: Data Connector Python SDK
5
5
  Home-page: https://github.com/data-connector/dc-python-sdk
6
6
  Author: DataConnector
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dc-python-sdk"
7
- version = "1.5.23"
7
+ version = "1.5.24"
8
8
  description = "Data Connector Python SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.6"
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = dc-python-sdk
3
- version = 1.5.23
3
+ version = 1.5.24
4
4
  author = DataConnector
5
5
  author_email = josh@dataconnector.com
6
6
  description = A small example package
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dc-python-sdk
3
- Version: 1.5.23
3
+ Version: 1.5.24
4
4
  Summary: Data Connector Python SDK
5
5
  Home-page: https://github.com/data-connector/dc-python-sdk
6
6
  Author: DataConnector
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  from importlib.metadata import version
3
+ import os
3
4
 
4
5
  def get_arg(name, default=None):
5
6
  if name in sys.argv:
@@ -15,15 +16,13 @@ def main():
15
16
 
16
17
  command = sys.argv[1]
17
18
 
18
- port = int(get_arg("--port", 5000))
19
-
20
19
  print("version: ", version("dc-python-sdk"))
21
20
 
22
21
  print(f"[DC SDK] Command: {command}")
23
22
 
24
23
  if command == "http":
25
24
  from dc_sdk.src.server import start_server
26
- start_server(port)
25
+ start_server()
27
26
 
28
27
  elif command == "ai":
29
28
  from dc_sdk.src.ai import start_ai
@@ -31,7 +30,7 @@ def main():
31
30
 
32
31
  elif command == "ai-http":
33
32
  from dc_sdk.src.ai_http import start_ai_http
34
- start_ai_http(connector_port=port)
33
+ start_ai_http()
35
34
 
36
35
  else:
37
36
  print(f"Unknown command: {command}")
@@ -20,15 +20,23 @@ app = FastAPI()
20
20
 
21
21
  _DEFAULT_PROCESS_TTL_SECONDS = 600.0
22
22
 
23
+ # Default ports: connector HTTP (dc-sdk http) 5000, AI FastAPI 5001, code-server 5002.
24
+ CONNECTOR_PORT = os.getenv("CONNECTOR_PORT", 5000)
25
+ AI_PORT = os.getenv("AI_PORT", 5001)
26
+ CODE_SERVER_PORT = os.getenv("CODE_SERVER_PORT", 5002)
27
+
23
28
  client = None
24
29
  workspace = os.getenv("WORKSPACE", "/workspace")
25
- port_for_connector = 5000
26
30
 
27
31
  # Process-wide TTL for ai-http (see DC_SDK_TTL_SECONDS); exposed in /session-info.
28
32
  process_ttl_seconds: Optional[float] = None
29
33
  process_ttl_deadline_unix: Optional[float] = None
30
34
  _process_ttl_timer: Optional[threading.Timer] = None
31
35
 
36
+ # Long-lived `dc-sdk http` subprocess (uvicorn with CONNECTOR_HTTP_RELOAD reloads connector.py).
37
+ _connector_proc: Optional[subprocess.Popen] = None
38
+ _connector_lock = threading.Lock()
39
+
32
40
  # -----------------------------
33
41
  # MODELS
34
42
  # -----------------------------
@@ -91,40 +99,68 @@ def _parse_process_ttl_seconds() -> float:
91
99
  return 0.0
92
100
  return v
93
101
 
94
- def start_connector():
95
- global workspace
96
- global port_for_connector
102
+ def _wait_connector_healthy() -> None:
103
+ for _ in range(50):
104
+ try:
105
+ res = requests.get(f"http://localhost:{CONNECTOR_PORT}/health", timeout=2)
106
+ if res.status_code == 200:
107
+ logger.info("Connector ready on port %s", CONNECTOR_PORT)
108
+ return
109
+ except Exception:
110
+ logger.debug("Health check not ready yet on port %s", CONNECTOR_PORT)
111
+ time.sleep(0.1)
112
+ raise RuntimeError(
113
+ f"Connector failed to become healthy on port {CONNECTOR_PORT} within timeout"
114
+ )
115
+
116
+
117
+ def _start_connector_subprocess_unlocked() -> None:
118
+ global _connector_proc, workspace
119
+ env = os.environ.copy()
120
+ env["CONNECTOR_HTTP_RELOAD"] = "1"
97
121
  proc = subprocess.Popen(
98
- ["dc-sdk", "http", "--port", str(port_for_connector)],
122
+ ["dc-sdk", "http"],
99
123
  cwd=workspace,
124
+ env=env,
100
125
  stdout=subprocess.DEVNULL,
101
- stderr=subprocess.DEVNULL
126
+ stderr=subprocess.DEVNULL,
102
127
  )
103
-
104
- # wait for health
105
- for _ in range(50):
128
+ try:
129
+ _wait_connector_healthy()
130
+ except Exception:
131
+ proc.terminate()
106
132
  try:
107
- res = requests.get(f"http://localhost:{port_for_connector}/health")
108
- if res.status_code == 200:
109
- logger.info("Connector ready on port %s", port_for_connector)
110
- return {"process": proc, "port": port_for_connector}
133
+ proc.wait(timeout=5)
111
134
  except Exception:
112
- logger.debug("Health check not ready yet on port %s", port_for_connector)
113
- time.sleep(0.1)
135
+ pass
136
+ raise
137
+ _connector_proc = proc
114
138
 
115
- logger.error("Connector failed to become healthy on port %s within timeout", port_for_connector)
116
- proc.terminate()
117
- raise Exception("Failed to start connector")
118
139
 
119
- def stop_connector(runtime):
140
+ def _stop_connector_subprocess_unlocked() -> None:
141
+ global _connector_proc
142
+ proc = _connector_proc
143
+ _connector_proc = None
144
+ if proc is None:
145
+ return
120
146
  try:
121
- runtime["process"].terminate()
122
- runtime["process"].wait()
147
+ proc.terminate()
148
+ proc.wait(timeout=15)
123
149
  logger.debug("Connector subprocess stopped")
124
150
  except Exception:
125
151
  logger.debug("Error stopping connector subprocess", exc_info=True)
126
152
 
127
- def invoke(port, method, session_id=None, credentials=None, params=None):
153
+
154
+ def start_connector_http_server() -> None:
155
+ """Start the connector HTTP child once; restart if the previous child exited."""
156
+ with _connector_lock:
157
+ if _connector_proc is not None and _connector_proc.poll() is None:
158
+ return
159
+ _stop_connector_subprocess_unlocked()
160
+ _start_connector_subprocess_unlocked()
161
+
162
+
163
+ def invoke(method, session_id=None, credentials=None, params=None):
128
164
  payload = {
129
165
  "method": method,
130
166
  "session_id": session_id,
@@ -132,8 +168,8 @@ def invoke(port, method, session_id=None, credentials=None, params=None):
132
168
  "params": params or {}
133
169
  }
134
170
 
135
- logger.debug("invoke port=%s method=%s session_id=%s", port, method, session_id)
136
- res = requests.post(f"http://localhost:{port}/invoke", json=payload)
171
+ logger.debug("invoke port=%s method=%s session_id=%s", CONNECTOR_PORT, method, session_id)
172
+ res = requests.post(f"http://localhost:{CONNECTOR_PORT}/invoke", json=payload)
137
173
  if res.status_code != 200:
138
174
  logger.warning(
139
175
  "invoke returned HTTP %s for method=%s", res.status_code, method
@@ -208,166 +244,159 @@ def run_test(
208
244
  connector_context: ConnectorContext,
209
245
  test_object_ids: Optional[List[str]] = None,
210
246
  ):
211
- runtime = start_connector()
212
247
  requested_ids = [_canon_object_id(x) for x in (test_object_ids or []) if _canon_object_id(x)]
213
248
  print(requested_ids)
214
249
 
215
- try:
216
- session_id = None
250
+ session_id = None
217
251
 
218
- # authenticate
219
- res = invoke(runtime["port"], "authenticate", None, credentials)
220
- if res.get("results") is not True:
221
- logger.warning("run_test failed at stage=authenticate")
222
- return False, "authenticate", res
252
+ # authenticate
253
+ res = invoke("authenticate", None, credentials)
254
+ if res.get("results") is not True:
255
+ logger.warning("run_test failed at stage=authenticate")
256
+ return False, "authenticate", res
223
257
 
224
- session_id = res.get("session_id")
258
+ session_id = res.get("session_id")
225
259
 
226
- # get_objects
227
- res = invoke(runtime["port"], "get_objects", session_id)
228
- if not isinstance(res.get("results"), list):
229
- logger.warning("run_test failed at stage=get_objects (invalid results type)")
230
- return False, "get_objects", res
231
-
232
- objects = res["results"]
233
- api_ids = _object_ids_from_get_objects_results(objects)
234
- dynamic = connector_context.objects_dynamic
235
-
236
- if dynamic:
237
- if api_ids:
238
- if requested_ids:
239
- api_set = set(api_ids)
240
- wanted = [x for x in requested_ids if x in api_set]
241
- if not wanted:
242
- logger.warning(
243
- "run_test failed: test_object_ids disjoint from get_objects"
244
- )
245
- return False, "get_objects", {
246
- "message": (
247
- f"objects_dynamic is true: none of test_object_ids {requested_ids} "
248
- f"appear in get_objects results {api_ids}."
249
- ),
250
- }
251
- ids_for_fields = wanted
252
- else:
253
- ids_for_fields = list(api_ids)
254
- else:
255
- if not requested_ids:
260
+ # get_objects
261
+ res = invoke("get_objects", session_id)
262
+ if not isinstance(res.get("results"), list):
263
+ logger.warning("run_test failed at stage=get_objects (invalid results type)")
264
+ return False, "get_objects", res
265
+
266
+ objects = res["results"]
267
+ api_ids = _object_ids_from_get_objects_results(objects)
268
+ dynamic = connector_context.objects_dynamic
269
+
270
+ if dynamic:
271
+ if api_ids:
272
+ if requested_ids:
273
+ api_set = set(api_ids)
274
+ wanted = [x for x in requested_ids if x in api_set]
275
+ if not wanted:
256
276
  logger.warning(
257
- "run_test failed: objects_dynamic with empty get_objects and no test_object_ids"
277
+ "run_test failed: test_object_ids disjoint from get_objects"
258
278
  )
259
279
  return False, "get_objects", {
260
280
  "message": (
261
- "objects_dynamic is true: get_objects returned no objects. "
262
- "Provide test_object_ids in the request to probe get_fields and get_data."
281
+ f"objects_dynamic is true: none of test_object_ids {requested_ids} "
282
+ f"appear in get_objects results {api_ids}."
263
283
  ),
264
284
  }
265
- ids_for_fields = list(requested_ids)
285
+ ids_for_fields = wanted
286
+ else:
287
+ ids_for_fields = list(api_ids)
266
288
  else:
267
- print(requested_ids)
268
289
  if not requested_ids:
269
- logger.warning("run_test failed: static objects require test_object_ids")
290
+ logger.warning(
291
+ "run_test failed: objects_dynamic with empty get_objects and no test_object_ids"
292
+ )
270
293
  return False, "get_objects", {
271
294
  "message": (
272
- "objects_dynamic is false: test_object_ids is required and must list "
273
- "object ids you expect get_objects to return."
295
+ "objects_dynamic is true: get_objects returned no objects. "
296
+ "Provide test_object_ids in the request to probe get_fields and get_data."
274
297
  ),
275
298
  }
276
- if not objects:
277
- logger.warning("run_test failed at stage=get_objects (empty list)")
278
- return False, "get_objects", {"message": "No objects returned from get_objects"}
279
-
280
299
  ids_for_fields = list(requested_ids)
281
-
282
- if not ids_for_fields:
283
- logger.warning("run_test failed: no object ids to probe")
300
+ else:
301
+ print(requested_ids)
302
+ if not requested_ids:
303
+ logger.warning("run_test failed: static objects require test_object_ids")
284
304
  return False, "get_objects", {
285
- "message": "No object ids available to test get_fields (empty candidate list).",
286
- }
287
-
288
- objects_with_fields: List[Tuple[str, List[Any]]] = []
289
- last_fields_payload: Any = None
290
-
291
- print(ids_for_fields)
292
-
293
- for object_id in ids_for_fields:
294
- res = invoke(
295
- runtime["port"],
296
- "get_fields",
297
- session_id,
298
- params={"object_id": object_id},
299
- )
300
- if not isinstance(res.get("results"), list):
301
- logger.warning(
302
- "run_test failed at stage=get_fields (invalid results type) object_id=%s",
303
- object_id,
304
- )
305
- return False, "get_fields", res
306
-
307
- fields = res["results"]
308
- last_fields_payload = res
309
- logger.info("get_fields results for object_id=%s: %s", object_id, len(fields))
310
- if fields:
311
- fids = [
312
- f.get("field_id") or f.get("id")
313
- for f in fields[:5]
314
- if (f.get("field_id") or f.get("id")) is not None
315
- ]
316
- if fids:
317
- objects_with_fields.append((object_id, fids))
318
-
319
- if not objects_with_fields:
320
- logger.warning(
321
- "run_test failed: no fields on any object tried ids=%s",
322
- ids_for_fields,
323
- )
324
- return False, "get_fields", {
325
305
  "message": (
326
- f"get_fields returned no usable fields for any tested object_id(s): {ids_for_fields}. "
327
- "Verify object ids and connector field discovery."
306
+ "objects_dynamic is false: test_object_ids is required and must list "
307
+ "object ids you expect get_objects to return."
328
308
  ),
329
- "last_invoke": last_fields_payload,
330
309
  }
310
+ if not objects:
311
+ logger.warning("run_test failed at stage=get_objects (empty list)")
312
+ return False, "get_objects", {"message": "No objects returned from get_objects"}
331
313
 
332
- last_data_res: Any = None
333
- for object_id, field_ids in objects_with_fields:
334
- res = invoke(
335
- runtime["port"],
336
- "get_data",
337
- session_id,
338
- params={
339
- "object_id": object_id,
340
- "field_ids": field_ids,
341
- "n_rows": 5,
342
- },
343
- )
344
- last_data_res = res
345
- if not isinstance(res.get("results"), dict):
346
- logger.warning(
347
- "run_test failed at stage=get_data (invalid results type) object_id=%s",
348
- object_id,
349
- )
350
- return False, "get_data", res
314
+ ids_for_fields = list(requested_ids)
315
+
316
+ if not ids_for_fields:
317
+ logger.warning("run_test failed: no object ids to probe")
318
+ return False, "get_objects", {
319
+ "message": "No object ids available to test get_fields (empty candidate list).",
320
+ }
321
+
322
+ objects_with_fields: List[Tuple[str, List[Any]]] = []
323
+ last_fields_payload: Any = None
351
324
 
352
- payload = res["results"]
353
- rows = payload.get("data")
354
- if isinstance(rows, list) and len(rows) > 0:
355
- logger.info("run_test completed successfully object_id=%s, rows retrieved=%s", object_id, len(rows))
356
- return True, None, None
325
+ print(ids_for_fields)
357
326
 
327
+ for object_id in ids_for_fields:
328
+ res = invoke(
329
+ "get_fields",
330
+ session_id,
331
+ params={"object_id": object_id},
332
+ )
333
+ if not isinstance(res.get("results"), list):
334
+ logger.warning(
335
+ "run_test failed at stage=get_fields (invalid results type) object_id=%s",
336
+ object_id,
337
+ )
338
+ return False, "get_fields", res
339
+
340
+ fields = res["results"]
341
+ last_fields_payload = res
342
+ logger.info("get_fields results for object_id=%s: %s", object_id, len(fields))
343
+ if fields:
344
+ fids = [
345
+ f.get("field_id") or f.get("id")
346
+ for f in fields[:5]
347
+ if (f.get("field_id") or f.get("id")) is not None
348
+ ]
349
+ if fids:
350
+ objects_with_fields.append((object_id, fids))
351
+
352
+ if not objects_with_fields:
358
353
  logger.warning(
359
- "run_test failed: get_data returned no rows for any object with fields"
354
+ "run_test failed: no fields on any object tried ids=%s",
355
+ ids_for_fields,
360
356
  )
361
- return False, "get_data", {
357
+ return False, "get_fields", {
362
358
  "message": (
363
- f"get_data returned no rows for any object that had fields "
364
- f"(tried {len(objects_with_fields)} object(s))."
359
+ f"get_fields returned no usable fields for any tested object_id(s): {ids_for_fields}. "
360
+ "Verify object ids and connector field discovery."
365
361
  ),
366
- "last_invoke": last_data_res,
362
+ "last_invoke": last_fields_payload,
367
363
  }
368
364
 
369
- finally:
370
- stop_connector(runtime)
365
+ last_data_res: Any = None
366
+ for object_id, field_ids in objects_with_fields:
367
+ res = invoke(
368
+ "get_data",
369
+ session_id,
370
+ params={
371
+ "object_id": object_id,
372
+ "field_ids": field_ids,
373
+ "n_rows": 5,
374
+ },
375
+ )
376
+ last_data_res = res
377
+ if not isinstance(res.get("results"), dict):
378
+ logger.warning(
379
+ "run_test failed at stage=get_data (invalid results type) object_id=%s",
380
+ object_id,
381
+ )
382
+ return False, "get_data", res
383
+
384
+ payload = res["results"]
385
+ rows = payload.get("data")
386
+ if isinstance(rows, list) and len(rows) > 0:
387
+ logger.info("run_test completed successfully object_id=%s, rows retrieved=%s", object_id, len(rows))
388
+ return True, None, None
389
+
390
+ logger.warning(
391
+ "run_test failed: get_data returned no rows for any object with fields"
392
+ )
393
+ return False, "get_data", {
394
+ "message": (
395
+ f"get_data returned no rows for any object that had fields "
396
+ f"(tried {len(objects_with_fields)} object(s))."
397
+ ),
398
+ "last_invoke": last_data_res,
399
+ }
371
400
 
372
401
 
373
402
  def _validate_static_requires_object_ids(
@@ -710,8 +739,7 @@ def clone_repo():
710
739
  subprocess.run(["git", "-C", workspace, "checkout", branch], check=True)
711
740
  subprocess.run(["git", "-C", workspace, "pull"], check=True)
712
741
 
713
- def run_code_server(port):
714
- global port_for_connector
742
+ def run_code_server():
715
743
  logger.info("code-server path: %s", shutil.which("code-server"))
716
744
 
717
745
  # ✅ Ensure config dir exists
@@ -720,7 +748,7 @@ def run_code_server(port):
720
748
  # ✅ Write code-server config
721
749
  with open("/root/.config/code-server/config.yaml", "w") as f:
722
750
  f.write(
723
- f"bind-addr: 0.0.0.0:{port}\n"
751
+ f"bind-addr: 0.0.0.0:{CODE_SERVER_PORT}\n"
724
752
  "auth: none\n"
725
753
  )
726
754
 
@@ -755,15 +783,12 @@ def run_code_server(port):
755
783
 
756
784
  return code_server_proc
757
785
 
758
- def start_ai_http(connector_port=5000, start_code_server=True):
786
+ def start_ai_http(start_code_server=True):
759
787
  global client
760
788
  global workspace
761
789
  global process_ttl_seconds
762
790
  global process_ttl_deadline_unix
763
791
  global _process_ttl_timer
764
- global port_for_connector
765
-
766
- port_for_connector = connector_port
767
792
 
768
793
  process_ttl_seconds = None
769
794
  process_ttl_deadline_unix = None
@@ -803,13 +828,13 @@ def start_ai_http(connector_port=5000, start_code_server=True):
803
828
  )
804
829
 
805
830
  api_key = os.getenv("OPENAI_API_KEY")
806
- ai_port = int(os.getenv("AI_PORT", 5050))
807
- connector_server_port = int(os.getenv("CONNECTOR_SERVER_PORT", 5001))
808
831
 
809
832
  clone_repo()
833
+ start_connector_http_server()
810
834
 
835
+ code_server_proc = None
811
836
  if start_code_server:
812
- code_server_proc = run_code_server(connector_server_port)
837
+ code_server_proc = run_code_server()
813
838
 
814
839
  def shutdown():
815
840
  global _process_ttl_timer
@@ -818,8 +843,11 @@ def start_ai_http(connector_port=5000, start_code_server=True):
818
843
  if tm is not None:
819
844
  tm.cancel()
820
845
  logger.info("Shutting down code-server...")
821
- if code_server_proc:
846
+ if code_server_proc is not None:
822
847
  code_server_proc.terminate()
848
+ logger.info("Shutting down connector HTTP...")
849
+ with _connector_lock:
850
+ _stop_connector_subprocess_unlocked()
823
851
 
824
852
  ttl_shutdown_holder["fn"] = shutdown
825
853
 
@@ -837,18 +865,19 @@ def start_ai_http(connector_port=5000, start_code_server=True):
837
865
 
838
866
  client = OpenAI(api_key=api_key)
839
867
 
840
- logger.info("Starting AI HTTP controller on 0.0.0.0:%s", ai_port)
868
+ logger.info("Starting AI HTTP controller on 0.0.0.0:%s", AI_PORT)
841
869
 
842
870
  import uvicorn
843
- uvicorn.run(app, host="0.0.0.0", port=ai_port)
871
+ uvicorn.run(app, host="0.0.0.0", port=AI_PORT)
844
872
 
845
873
  @app.get("/session-info")
846
874
  def session_info():
847
875
  out: Dict[str, Any] = {
848
876
  "workspace": workspace,
849
- "ai_port": int(os.getenv("AI_PORT", 5050)),
850
- "code_server_port": int(os.getenv("CODE_SERVER_PORT", 5002)),
851
- "api_base": f"http://localhost:{os.getenv('AI_PORT', 5050)}",
877
+ "connector_port": CONNECTOR_PORT,
878
+ "ai_port": AI_PORT,
879
+ "code_server_port": CODE_SERVER_PORT,
880
+ "api_base": f"http://localhost:{AI_PORT}",
852
881
  }
853
882
  if process_ttl_seconds is not None and process_ttl_deadline_unix is not None:
854
883
  out["ttl_seconds"] = process_ttl_seconds
@@ -88,11 +88,21 @@ def invoke(req: InvokeRequest):
88
88
  }
89
89
 
90
90
 
91
- def start_server(port: Optional[int] = None):
91
+ def start_server():
92
92
  import uvicorn
93
- listen = int(os.getenv("PORT", 5000)) if port is None else int(port)
94
- uvicorn.run(
95
- "dc_sdk.src.server:app",
96
- host="0.0.0.0",
97
- port=listen,
98
- )
93
+
94
+ listen = int(os.getenv("CONNECTOR_PORT", 5000))
95
+ workspace = os.getenv("WORKSPACE", os.getcwd())
96
+ connector_src = os.path.join(workspace, "src")
97
+ use_reload = os.getenv("CONNECTOR_HTTP_RELOAD", "0").lower() in ("1", "true", "yes")
98
+
99
+ kw: Dict[str, Any] = {
100
+ "host": "0.0.0.0",
101
+ "port": listen,
102
+ }
103
+ if use_reload:
104
+ kw["reload"] = True
105
+ watch = connector_src if os.path.isdir(connector_src) else workspace
106
+ kw["reload_dirs"] = [os.path.abspath(watch)]
107
+
108
+ uvicorn.run("dc_sdk.src.server:app", **kw)
File without changes
File without changes