mlrun 1.7.0rc43__py3-none-any.whl → 1.7.0rc55__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 mlrun might be problematic. Click here for more details.

Files changed (68) hide show
  1. mlrun/__main__.py +4 -2
  2. mlrun/artifacts/manager.py +3 -1
  3. mlrun/common/formatters/__init__.py +1 -0
  4. mlrun/{model_monitoring/application.py → common/formatters/feature_set.py} +20 -6
  5. mlrun/common/formatters/run.py +3 -0
  6. mlrun/common/schemas/__init__.py +1 -0
  7. mlrun/common/schemas/alert.py +11 -11
  8. mlrun/common/schemas/auth.py +5 -0
  9. mlrun/common/schemas/client_spec.py +0 -1
  10. mlrun/common/schemas/model_monitoring/__init__.py +2 -1
  11. mlrun/common/schemas/model_monitoring/constants.py +23 -9
  12. mlrun/common/schemas/model_monitoring/model_endpoints.py +24 -47
  13. mlrun/common/schemas/notification.py +12 -2
  14. mlrun/common/schemas/workflow.py +10 -2
  15. mlrun/config.py +28 -21
  16. mlrun/data_types/data_types.py +6 -1
  17. mlrun/datastore/base.py +4 -4
  18. mlrun/datastore/s3.py +12 -9
  19. mlrun/datastore/storeytargets.py +9 -6
  20. mlrun/db/base.py +3 -0
  21. mlrun/db/httpdb.py +28 -16
  22. mlrun/db/nopdb.py +24 -4
  23. mlrun/errors.py +7 -1
  24. mlrun/execution.py +40 -7
  25. mlrun/feature_store/api.py +1 -0
  26. mlrun/feature_store/retrieval/spark_merger.py +7 -7
  27. mlrun/frameworks/_common/plan.py +3 -3
  28. mlrun/frameworks/_ml_common/plan.py +1 -1
  29. mlrun/frameworks/parallel_coordinates.py +2 -3
  30. mlrun/launcher/client.py +6 -6
  31. mlrun/model.py +29 -0
  32. mlrun/model_monitoring/api.py +1 -12
  33. mlrun/model_monitoring/applications/__init__.py +1 -2
  34. mlrun/model_monitoring/applications/_application_steps.py +5 -1
  35. mlrun/model_monitoring/applications/base.py +2 -182
  36. mlrun/model_monitoring/applications/context.py +2 -9
  37. mlrun/model_monitoring/applications/evidently_base.py +0 -74
  38. mlrun/model_monitoring/applications/histogram_data_drift.py +2 -2
  39. mlrun/model_monitoring/applications/results.py +4 -4
  40. mlrun/model_monitoring/controller.py +46 -209
  41. mlrun/model_monitoring/db/stores/base/store.py +1 -0
  42. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +15 -1
  43. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +12 -0
  44. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +17 -16
  45. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +49 -39
  46. mlrun/model_monitoring/helpers.py +13 -15
  47. mlrun/model_monitoring/writer.py +3 -1
  48. mlrun/projects/operations.py +11 -8
  49. mlrun/projects/pipelines.py +35 -16
  50. mlrun/projects/project.py +52 -24
  51. mlrun/render.py +3 -3
  52. mlrun/runtimes/daskjob.py +1 -1
  53. mlrun/runtimes/kubejob.py +6 -6
  54. mlrun/runtimes/nuclio/api_gateway.py +12 -0
  55. mlrun/runtimes/nuclio/application/application.py +3 -3
  56. mlrun/runtimes/nuclio/function.py +41 -0
  57. mlrun/runtimes/nuclio/serving.py +2 -2
  58. mlrun/runtimes/pod.py +19 -13
  59. mlrun/serving/server.py +2 -0
  60. mlrun/utils/helpers.py +62 -16
  61. mlrun/utils/version/version.json +2 -2
  62. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/METADATA +126 -44
  63. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/RECORD +67 -68
  64. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/WHEEL +1 -1
  65. mlrun/model_monitoring/evidently_application.py +0 -20
  66. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/LICENSE +0 -0
  67. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/entry_points.txt +0 -0
  68. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/top_level.txt +0 -0
@@ -15,28 +15,22 @@
15
15
  import concurrent.futures
16
16
  import datetime
17
17
  import json
18
- import multiprocessing
19
18
  import os
20
19
  import re
21
20
  from collections.abc import Iterator
22
- from typing import Any, NamedTuple, Optional, Union, cast
21
+ from typing import NamedTuple, Optional, Union, cast
23
22
 
24
23
  import nuclio
25
24
 
26
25
  import mlrun
27
26
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
28
27
  import mlrun.data_types.infer
29
- import mlrun.feature_store as fstore
30
28
  import mlrun.model_monitoring.db.stores
31
- from mlrun.common.model_monitoring.helpers import FeatureStats, pad_features_hist
32
29
  from mlrun.datastore import get_stream_pusher
33
- from mlrun.datastore.targets import ParquetTarget
34
30
  from mlrun.errors import err_to_str
35
31
  from mlrun.model_monitoring.helpers import (
36
32
  _BatchDict,
37
33
  batch_dict2timedelta,
38
- calculate_inputs_statistics,
39
- get_monitoring_parquet_path,
40
34
  get_stream_path,
41
35
  )
42
36
  from mlrun.utils import datetime_now, logger
@@ -219,7 +213,7 @@ class _BatchWindowGenerator:
219
213
  # If the endpoint does not have a stream, `last_updated` should be
220
214
  # the minimum between the current time and the last updated time.
221
215
  # This compensates for the bumping mechanism - see
222
- # `bump_model_endpoint_last_request`.
216
+ # `update_model_endpoint_last_request`.
223
217
  last_updated = min(int(datetime_now().timestamp()), last_updated)
224
218
  logger.debug(
225
219
  "The endpoint does not have a stream", last_updated=last_updated
@@ -292,15 +286,9 @@ class MonitoringApplicationController:
292
286
  )
293
287
 
294
288
  self.model_monitoring_access_key = self._get_model_monitoring_access_key()
295
- self.parquet_directory = get_monitoring_parquet_path(
296
- self.project_obj,
297
- kind=mm_constants.FileTargetKind.APPS_PARQUET,
289
+ self.tsdb_connector = mlrun.model_monitoring.get_tsdb_connector(
290
+ project=self.project
298
291
  )
299
- self.storage_options = None
300
- if not mlrun.mlconf.is_ce_mode():
301
- self._initialize_v3io_configurations()
302
- elif self.parquet_directory.startswith("s3://"):
303
- self.storage_options = mlrun.mlconf.get_s3_storage_options()
304
292
 
305
293
  @staticmethod
306
294
  def _get_model_monitoring_access_key() -> Optional[str]:
@@ -310,12 +298,6 @@ class MonitoringApplicationController:
310
298
  access_key = mlrun.mlconf.get_v3io_access_key()
311
299
  return access_key
312
300
 
313
- def _initialize_v3io_configurations(self) -> None:
314
- self.storage_options = dict(
315
- v3io_access_key=self.model_monitoring_access_key,
316
- v3io_api=mlrun.mlconf.v3io_api,
317
- )
318
-
319
301
  def run(self) -> None:
320
302
  """
321
303
  Main method for run all the relevant monitoring applications on each endpoint.
@@ -367,11 +349,8 @@ class MonitoringApplicationController:
367
349
  )
368
350
  return
369
351
  # Initialize a process pool that will be used to run each endpoint applications on a dedicated process
370
- with concurrent.futures.ProcessPoolExecutor(
352
+ with concurrent.futures.ThreadPoolExecutor(
371
353
  max_workers=min(len(endpoints), 10),
372
- # On Linux, the default is "fork" (this is set to change in Python 3.14), which inherits the current heap
373
- # and resources (such as sockets), which is not what we want (ML-7160)
374
- mp_context=multiprocessing.get_context("spawn"),
375
354
  ) as pool:
376
355
  for endpoint in endpoints:
377
356
  if (
@@ -395,13 +374,10 @@ class MonitoringApplicationController:
395
374
  applications_names=applications_names,
396
375
  batch_window_generator=self._batch_window_generator,
397
376
  project=self.project,
398
- parquet_directory=self.parquet_directory,
399
- storage_options=self.storage_options,
400
377
  model_monitoring_access_key=self.model_monitoring_access_key,
378
+ tsdb_connector=self.tsdb_connector,
401
379
  )
402
380
 
403
- self._delete_old_parquet(endpoints=endpoints)
404
-
405
381
  @classmethod
406
382
  def model_endpoint_process(
407
383
  cls,
@@ -409,9 +385,8 @@ class MonitoringApplicationController:
409
385
  applications_names: list[str],
410
386
  batch_window_generator: _BatchWindowGenerator,
411
387
  project: str,
412
- parquet_directory: str,
413
- storage_options: dict,
414
388
  model_monitoring_access_key: str,
389
+ tsdb_connector: mlrun.model_monitoring.db.tsdb.TSDBConnector,
415
390
  ) -> None:
416
391
  """
417
392
  Process a model endpoint and trigger the monitoring applications. This function running on different process
@@ -422,16 +397,13 @@ class MonitoringApplicationController:
422
397
  :param applications_names: (list[str]) List of application names to push results to.
423
398
  :param batch_window_generator: (_BatchWindowGenerator) An object that generates _BatchWindow objects.
424
399
  :param project: (str) Project name.
425
- :param parquet_directory: (str) Directory to store application parquet files
426
- :param storage_options: (dict) Storage options for writing ParquetTarget.
427
400
  :param model_monitoring_access_key: (str) Access key to apply the model monitoring process.
401
+ :param tsdb_connector: (mlrun.model_monitoring.db.tsdb.TSDBConnector) TSDB connector
428
402
  """
429
403
  endpoint_id = endpoint[mm_constants.EventFieldType.UID]
404
+ # if false the endpoint represent batch infer step.
405
+ has_stream = endpoint[mm_constants.EventFieldType.STREAM_PATH] != ""
430
406
  try:
431
- m_fs = fstore.get_feature_set(
432
- endpoint[mm_constants.EventFieldType.FEATURE_SET_URI]
433
- )
434
-
435
407
  for application in applications_names:
436
408
  batch_window = batch_window_generator.get_batch_window(
437
409
  project=project,
@@ -439,158 +411,70 @@ class MonitoringApplicationController:
439
411
  application=application,
440
412
  first_request=endpoint[mm_constants.EventFieldType.FIRST_REQUEST],
441
413
  last_request=endpoint[mm_constants.EventFieldType.LAST_REQUEST],
442
- has_stream=endpoint[mm_constants.EventFieldType.STREAM_PATH] != "",
414
+ has_stream=has_stream,
443
415
  )
444
416
 
445
417
  for start_infer_time, end_infer_time in batch_window.get_intervals():
446
- # start - TODO : delete in 1.9.0 (V1 app deprecation)
447
- try:
448
- # Get application sample data
449
- offline_response = cls._get_sample_df(
450
- feature_set=m_fs,
418
+ prediction_metric = tsdb_connector.read_predictions(
419
+ endpoint_id=endpoint_id,
420
+ start=start_infer_time,
421
+ end=end_infer_time,
422
+ )
423
+ if not prediction_metric.data and has_stream:
424
+ logger.info(
425
+ "No data found for the given interval",
426
+ start=start_infer_time,
427
+ end=end_infer_time,
428
+ endpoint_id=endpoint_id,
429
+ )
430
+ else:
431
+ logger.info(
432
+ "Data found for the given interval",
433
+ start=start_infer_time,
434
+ end=end_infer_time,
451
435
  endpoint_id=endpoint_id,
436
+ )
437
+ cls._push_to_applications(
452
438
  start_infer_time=start_infer_time,
453
439
  end_infer_time=end_infer_time,
454
- parquet_directory=parquet_directory,
455
- storage_options=storage_options,
456
- application_name=application,
457
- )
458
-
459
- df = offline_response.to_dataframe()
460
- parquet_target_path = offline_response.vector.get_target_path()
461
-
462
- if len(df) == 0:
463
- logger.info(
464
- "During this time window, the endpoint has not received any data",
465
- endpoint=endpoint[mm_constants.EventFieldType.UID],
466
- start_time=start_infer_time,
467
- end_time=end_infer_time,
468
- )
469
- continue
470
-
471
- except FileNotFoundError:
472
- logger.warn(
473
- "No parquets were written yet",
474
- endpoint=endpoint[mm_constants.EventFieldType.UID],
440
+ endpoint_id=endpoint_id,
441
+ project=project,
442
+ applications_names=[application],
443
+ model_monitoring_access_key=model_monitoring_access_key,
475
444
  )
476
- continue
477
-
478
- # Get the timestamp of the latest request:
479
- latest_request = df[mm_constants.EventFieldType.TIMESTAMP].iloc[-1]
480
-
481
- # Get the feature stats from the model endpoint for reference data
482
- feature_stats = json.loads(
483
- endpoint[mm_constants.EventFieldType.FEATURE_STATS]
484
- )
485
-
486
- # Pad the original feature stats to accommodate current
487
- # data out of the original range (unless already padded)
488
- pad_features_hist(FeatureStats(feature_stats))
489
-
490
- # Get the current stats:
491
- current_stats = calculate_inputs_statistics(
492
- sample_set_statistics=feature_stats, inputs=df
493
- )
494
- # end - TODO : delete in 1.9.0 (V1 app deprecation)
495
- cls._push_to_applications(
496
- current_stats=current_stats,
497
- feature_stats=feature_stats,
498
- start_infer_time=start_infer_time,
499
- end_infer_time=end_infer_time,
500
- endpoint_id=endpoint_id,
501
- latest_request=latest_request,
502
- project=project,
503
- applications_names=[application],
504
- model_monitoring_access_key=model_monitoring_access_key,
505
- parquet_target_path=parquet_target_path,
506
- )
507
445
  except Exception:
508
446
  logger.exception(
509
447
  "Encountered an exception",
510
448
  endpoint_id=endpoint[mm_constants.EventFieldType.UID],
511
449
  )
512
450
 
513
- def _delete_old_parquet(self, endpoints: list[dict[str, Any]], days: int = 1):
514
- """
515
- Delete application parquets older than the argument days.
516
-
517
- :param endpoints: A list of dictionaries of model endpoints records.
518
- """
519
- if self.parquet_directory.startswith("v3io:///"):
520
- # create fs with access to the user side (under projects)
521
- store, _, _ = mlrun.store_manager.get_or_create_store(
522
- self.parquet_directory,
523
- {"V3IO_ACCESS_KEY": self.model_monitoring_access_key},
524
- )
525
- fs = store.filesystem
526
-
527
- # calculate time threshold (keep only files from the last 24 hours)
528
- time_to_keep = (
529
- datetime.datetime.now(tz=datetime.timezone.utc)
530
- - datetime.timedelta(days=days)
531
- ).timestamp()
532
-
533
- for endpoint in endpoints:
534
- try:
535
- apps_parquet_directories = fs.listdir(
536
- path=f"{self.parquet_directory}"
537
- f"/key={endpoint[mm_constants.EventFieldType.UID]}"
538
- )
539
- for directory in apps_parquet_directories:
540
- if directory["mtime"] < time_to_keep:
541
- # Delete files
542
- fs.rm(path=directory["name"], recursive=True)
543
- # Delete directory
544
- fs.rmdir(path=directory["name"])
545
- except FileNotFoundError:
546
- logger.info(
547
- "Application parquet directory is empty, "
548
- "probably parquets have not yet been created for this app",
549
- endpoint=endpoint[mm_constants.EventFieldType.UID],
550
- path=f"{self.parquet_directory}"
551
- f"/key={endpoint[mm_constants.EventFieldType.UID]}",
552
- )
553
-
554
451
  @staticmethod
555
452
  def _push_to_applications(
556
- current_stats,
557
- feature_stats,
558
- start_infer_time,
559
- end_infer_time,
560
- endpoint_id,
561
- latest_request,
562
- project,
563
- applications_names,
564
- model_monitoring_access_key,
565
- parquet_target_path,
453
+ start_infer_time: datetime.datetime,
454
+ end_infer_time: datetime.datetime,
455
+ endpoint_id: str,
456
+ project: str,
457
+ applications_names: list[str],
458
+ model_monitoring_access_key: str,
566
459
  ):
567
460
  """
568
461
  Pushes data to multiple stream applications.
569
462
 
570
- :param current_stats: Current statistics of input data.
571
- :param feature_stats: Statistics of train features.
572
- :param start_infer_time: The beginning of the infer interval window.
573
- :param end_infer_time: The end of the infer interval window.
574
- :param endpoint_id: Identifier for the model endpoint.
575
- :param latest_request: Timestamp of the latest model request.
576
- :param project: mlrun Project name.
577
- :param applications_names: List of application names to which data will be pushed.
463
+ :param start_infer_time: The beginning of the infer interval window.
464
+ :param end_infer_time: The end of the infer interval window.
465
+ :param endpoint_id: Identifier for the model endpoint.
466
+ :param project: mlrun Project name.
467
+ :param applications_names: List of application names to which data will be pushed.
468
+ :param model_monitoring_access_key: Access key to apply the model monitoring process.
578
469
 
579
470
  """
580
-
581
471
  data = {
582
- mm_constants.ApplicationEvent.CURRENT_STATS: json.dumps(current_stats),
583
- mm_constants.ApplicationEvent.FEATURE_STATS: json.dumps(feature_stats),
584
- mm_constants.ApplicationEvent.SAMPLE_PARQUET_PATH: parquet_target_path,
585
472
  mm_constants.ApplicationEvent.START_INFER_TIME: start_infer_time.isoformat(
586
473
  sep=" ", timespec="microseconds"
587
474
  ),
588
475
  mm_constants.ApplicationEvent.END_INFER_TIME: end_infer_time.isoformat(
589
476
  sep=" ", timespec="microseconds"
590
477
  ),
591
- mm_constants.ApplicationEvent.LAST_REQUEST: latest_request.isoformat(
592
- sep=" ", timespec="microseconds"
593
- ),
594
478
  mm_constants.ApplicationEvent.ENDPOINT_ID: endpoint_id,
595
479
  mm_constants.ApplicationEvent.OUTPUT_STREAM_URI: get_stream_path(
596
480
  project=project,
@@ -608,53 +492,6 @@ class MonitoringApplicationController:
608
492
  [data]
609
493
  )
610
494
 
611
- @staticmethod
612
- def _get_sample_df(
613
- feature_set: mlrun.common.schemas.FeatureSet,
614
- endpoint_id: str,
615
- start_infer_time: datetime.datetime,
616
- end_infer_time: datetime.datetime,
617
- parquet_directory: str,
618
- storage_options: dict,
619
- application_name: str,
620
- ) -> mlrun.feature_store.OfflineVectorResponse:
621
- """
622
- Retrieves a sample DataFrame of the current input according to the provided infer interval window.
623
-
624
- :param feature_set: The main feature set.
625
- :param endpoint_id: Identifier for the model endpoint.
626
- :param start_infer_time: The beginning of the infer interval window.
627
- :param end_infer_time: The end of the infer interval window.
628
- :param parquet_directory: Directory where Parquet files are stored.
629
- :param storage_options: Storage options for accessing the data.
630
- :param application_name: Current application name.
631
-
632
- :return: OfflineVectorResponse that can be used for generating a sample DataFrame for the specified endpoint.
633
-
634
- """
635
- features = [f"{feature_set.metadata.name}.*"]
636
- vector = fstore.FeatureVector(
637
- name=f"{endpoint_id}_vector",
638
- features=features,
639
- with_indexes=True,
640
- )
641
- vector.metadata.tag = application_name
642
- vector.feature_set_objects = {feature_set.metadata.name: feature_set}
643
-
644
- # get offline features based on application start and end time.
645
- # store the result parquet by partitioning by controller end processing time
646
- offline_response = vector.get_offline_features(
647
- start_time=start_infer_time,
648
- end_time=end_infer_time,
649
- timestamp_for_filtering=mm_constants.EventFieldType.TIMESTAMP,
650
- target=ParquetTarget(
651
- path=parquet_directory
652
- + f"/key={endpoint_id}/{int(start_infer_time.timestamp())}/{application_name}.parquet",
653
- storage_options=storage_options,
654
- ),
655
- )
656
- return offline_response
657
-
658
495
 
659
496
  def handler(context: nuclio.Context, event: nuclio.Event) -> None:
660
497
  """
@@ -11,6 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+
14
15
  import json
15
16
  import typing
16
17
  from abc import ABC, abstractmethod
@@ -588,7 +588,11 @@ class SQLStoreBase(StoreBase):
588
588
 
589
589
  for endpoint_dict in endpoints:
590
590
  endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
591
-
591
+ logger.debug(
592
+ "Deleting model endpoint resources from the SQL tables",
593
+ endpoint_id=endpoint_id,
594
+ project=self.project,
595
+ )
592
596
  # Delete last analyzed records
593
597
  self._delete_last_analyzed(endpoint_id=endpoint_id)
594
598
 
@@ -598,6 +602,16 @@ class SQLStoreBase(StoreBase):
598
602
 
599
603
  # Delete model endpoint record
600
604
  self.delete_model_endpoint(endpoint_id=endpoint_id)
605
+ logger.debug(
606
+ "Successfully deleted model endpoint resources",
607
+ endpoint_id=endpoint_id,
608
+ project=self.project,
609
+ )
610
+
611
+ logger.debug(
612
+ "Successfully deleted model monitoring endpoints resources from the SQL tables",
613
+ project=self.project,
614
+ )
601
615
 
602
616
  def get_model_endpoint_metrics(
603
617
  self, endpoint_id: str, type: mm_schemas.ModelEndpointMonitoringMetricType
@@ -305,10 +305,22 @@ class KVStoreBase(StoreBase):
305
305
  endpoint_id = endpoint_dict[mm_schemas.EventFieldType.ENDPOINT_ID]
306
306
  else:
307
307
  endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
308
+
309
+ logger.debug(
310
+ "Deleting model endpoint resources from the V3IO KV table",
311
+ endpoint_id=endpoint_id,
312
+ project=self.project,
313
+ )
314
+
308
315
  self.delete_model_endpoint(
309
316
  endpoint_id,
310
317
  )
311
318
 
319
+ logger.debug(
320
+ "Successfully deleted model monitoring endpoints from the V3IO KV table",
321
+ project=self.project,
322
+ )
323
+
312
324
  # Delete remain records in the KV
313
325
  all_records = self.client.kv.new_cursor(
314
326
  container=self.container,
@@ -94,38 +94,39 @@ class TDEngineSchema:
94
94
  tags = ", ".join(f"{col} {val}" for col, val in self.tags.items())
95
95
  return f"CREATE STABLE if NOT EXISTS {self.database}.{self.super_table} ({columns}) TAGS ({tags});"
96
96
 
97
- def _create_subtable_query(
97
+ def _create_subtable_sql(
98
98
  self,
99
99
  subtable: str,
100
100
  values: dict[str, Union[str, int, float, datetime.datetime]],
101
101
  ) -> str:
102
102
  try:
103
- values = ", ".join(f"'{values[val]}'" for val in self.tags)
103
+ tags = ", ".join(f"'{values[val]}'" for val in self.tags)
104
104
  except KeyError:
105
105
  raise mlrun.errors.MLRunInvalidArgumentError(
106
106
  f"values must contain all tags: {self.tags.keys()}"
107
107
  )
108
- return f"CREATE TABLE if NOT EXISTS {self.database}.{subtable} USING {self.super_table} TAGS ({values});"
108
+ return f"CREATE TABLE if NOT EXISTS {self.database}.{subtable} USING {self.super_table} TAGS ({tags});"
109
109
 
110
- def _insert_subtable_query(
111
- self,
112
- connection: taosws.Connection,
110
+ @staticmethod
111
+ def _insert_subtable_stmt(
112
+ statement: taosws.TaosStmt,
113
+ columns: dict[str, _TDEngineColumn],
113
114
  subtable: str,
114
115
  values: dict[str, Union[str, int, float, datetime.datetime]],
115
116
  ) -> taosws.TaosStmt:
116
- stmt = connection.statement()
117
- question_marks = ", ".join("?" * len(self.columns))
118
- stmt.prepare(f"INSERT INTO ? VALUES ({question_marks});")
119
- stmt.set_tbname_tags(subtable, [])
117
+ question_marks = ", ".join("?" * len(columns))
118
+ statement.prepare(f"INSERT INTO ? VALUES ({question_marks});")
119
+ statement.set_tbname(subtable)
120
120
 
121
121
  bind_params = []
122
122
 
123
- for col_name, col_type in self.columns.items():
123
+ for col_name, col_type in columns.items():
124
124
  val = values[col_name]
125
125
  bind_params.append(values_to_column([val], col_type))
126
126
 
127
- stmt.bind_param(bind_params)
128
- return stmt
127
+ statement.bind_param(bind_params)
128
+ statement.add_batch()
129
+ return statement
129
130
 
130
131
  def _delete_subtable_query(
131
132
  self,
@@ -163,8 +164,8 @@ class TDEngineSchema:
163
164
  @staticmethod
164
165
  def _get_records_query(
165
166
  table: str,
166
- start: datetime,
167
- end: datetime,
167
+ start: datetime.datetime,
168
+ end: datetime.datetime,
168
169
  columns_to_filter: list[str] = None,
169
170
  filter_query: Optional[str] = None,
170
171
  interval: Optional[str] = None,
@@ -211,7 +212,7 @@ class TDEngineSchema:
211
212
  if filter_query:
212
213
  query.write(f"{filter_query} AND ")
213
214
  if start:
214
- query.write(f"{timestamp_column} >= '{start}'" + " AND ")
215
+ query.write(f"{timestamp_column} >= '{start}' AND ")
215
216
  if end:
216
217
  query.write(f"{timestamp_column} <= '{end}'")
217
218
  if interval: