moose-lib 0.6.119__tar.gz → 0.6.121__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.

Potentially problematic release.


This version of moose-lib might be problematic. Click here for more details.

Files changed (48) hide show
  1. {moose_lib-0.6.119 → moose_lib-0.6.121}/PKG-INFO +1 -1
  2. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/olap_table.py +77 -60
  3. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/internal.py +16 -8
  4. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib.egg-info/PKG-INFO +1 -1
  5. {moose_lib-0.6.119 → moose_lib-0.6.121}/README.md +0 -0
  6. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/__init__.py +0 -0
  7. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/blocks.py +0 -0
  8. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/clients/__init__.py +0 -0
  9. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/clients/redis_client.py +0 -0
  10. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/commons.py +0 -0
  11. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/config/__init__.py +0 -0
  12. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/config/config_file.py +0 -0
  13. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/config/runtime.py +0 -0
  14. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/data_models.py +0 -0
  15. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/__init__.py +0 -0
  16. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/_registry.py +0 -0
  17. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/consumption.py +0 -0
  18. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/ingest_api.py +0 -0
  19. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/ingest_pipeline.py +0 -0
  20. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/life_cycle.py +0 -0
  21. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/materialized_view.py +0 -0
  22. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/registry.py +0 -0
  23. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/sql_resource.py +0 -0
  24. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/stream.py +0 -0
  25. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/types.py +0 -0
  26. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/view.py +0 -0
  27. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2/workflow.py +0 -0
  28. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/dmv2_serializer.py +0 -0
  29. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/main.py +0 -0
  30. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/query_builder.py +0 -0
  31. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/query_param.py +0 -0
  32. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/streaming/__init__.py +0 -0
  33. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/streaming/streaming_function_runner.py +0 -0
  34. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/utilities/__init__.py +0 -0
  35. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib/utilities/sql.py +0 -0
  36. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib.egg-info/SOURCES.txt +0 -0
  37. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib.egg-info/dependency_links.txt +0 -0
  38. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib.egg-info/requires.txt +0 -0
  39. {moose_lib-0.6.119 → moose_lib-0.6.121}/moose_lib.egg-info/top_level.txt +0 -0
  40. {moose_lib-0.6.119 → moose_lib-0.6.121}/setup.cfg +0 -0
  41. {moose_lib-0.6.119 → moose_lib-0.6.121}/setup.py +0 -0
  42. {moose_lib-0.6.119 → moose_lib-0.6.121}/tests/__init__.py +0 -0
  43. {moose_lib-0.6.119 → moose_lib-0.6.121}/tests/conftest.py +0 -0
  44. {moose_lib-0.6.119 → moose_lib-0.6.121}/tests/test_moose.py +0 -0
  45. {moose_lib-0.6.119 → moose_lib-0.6.121}/tests/test_olap_table_versioning.py +0 -0
  46. {moose_lib-0.6.119 → moose_lib-0.6.121}/tests/test_query_builder.py +0 -0
  47. {moose_lib-0.6.119 → moose_lib-0.6.121}/tests/test_redis_client.py +0 -0
  48. {moose_lib-0.6.119 → moose_lib-0.6.121}/tests/test_s3queue_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moose_lib
3
- Version: 0.6.119
3
+ Version: 0.6.121
4
4
  Home-page: https://www.fiveonefour.com/moose
5
5
  Author: Fiveonefour Labs Inc.
6
6
  Author-email: support@fiveonefour.com
@@ -21,6 +21,7 @@ from ._registry import _tables
21
21
  from ..data_models import Column, is_array_nested_type, is_nested_type, _to_columns
22
22
  from .life_cycle import LifeCycle
23
23
 
24
+
24
25
  @dataclass
25
26
  class InsertOptions:
26
27
  """Options for insert operations.
@@ -38,6 +39,7 @@ class InsertOptions:
38
39
  validate: bool = True
39
40
  skip_validation_on_retry: bool = False
40
41
 
42
+
41
43
  @dataclass
42
44
  class FailedRecord(Generic[T]):
43
45
  """Represents a failed record during insertion with error details.
@@ -51,6 +53,7 @@ class FailedRecord(Generic[T]):
51
53
  error: str
52
54
  index: Optional[int] = None
53
55
 
56
+
54
57
  @dataclass
55
58
  class ValidationError:
56
59
  """Validation error for a record with detailed error information.
@@ -66,6 +69,7 @@ class ValidationError:
66
69
  index: Optional[int] = None
67
70
  path: Optional[str] = None
68
71
 
72
+
69
73
  @dataclass
70
74
  class ValidationResult(Generic[T]):
71
75
  """Result of data validation with success/failure breakdown.
@@ -79,6 +83,7 @@ class ValidationResult(Generic[T]):
79
83
  invalid: List[ValidationError]
80
84
  total: int
81
85
 
86
+
82
87
  @dataclass
83
88
  class InsertResult(Generic[T]):
84
89
  """Result of an insert operation with detailed success/failure information.
@@ -94,14 +99,18 @@ class InsertResult(Generic[T]):
94
99
  total: int
95
100
  failed_records: Optional[List[FailedRecord[T]]] = None
96
101
 
102
+
97
103
  class OlapConfig(BaseModel):
98
104
  model_config = {"extra": "forbid"} # Reject unknown fields for a clean API
99
-
105
+
100
106
  """Configuration for OLAP tables (e.g., ClickHouse tables).
101
107
 
102
108
  Attributes:
103
109
  order_by_fields: List of column names to use for the ORDER BY clause.
104
110
  Crucial for `ReplacingMergeTree` and performance.
111
+ order_by_expression: An arbitrary ClickHouse expression for ORDER BY. Example:
112
+ `order_by_expression="(id, name)"` is equivalent to order_by_fields=["id", "name"], or
113
+ "tuple()" for no sorting.
105
114
  partition_by: Optional PARTITION BY expression (single ClickHouse SQL expression).
106
115
  engine: The ClickHouse table engine to use. Can be either a ClickHouseEngines enum value
107
116
  (for backward compatibility) or an EngineConfig instance (recommended).
@@ -112,6 +121,7 @@ class OlapConfig(BaseModel):
112
121
  These are alterable settings that can be changed without recreating the table.
113
122
  """
114
123
  order_by_fields: list[str] = []
124
+ order_by_expression: Optional[str] = None
115
125
  partition_by: Optional[str] = None
116
126
  engine: Optional[Union[ClickHouseEngines, EngineConfig]] = None
117
127
  version: Optional[str] = None
@@ -119,6 +129,13 @@ class OlapConfig(BaseModel):
119
129
  life_cycle: Optional[LifeCycle] = None
120
130
  settings: Optional[dict[str, str]] = None
121
131
 
132
+ def model_post_init(self, __context):
133
+ has_fields = bool(self.order_by_fields)
134
+ has_expr = isinstance(self.order_by_expression, str) and len(self.order_by_expression) > 0
135
+ if has_fields and has_expr:
136
+ raise ValueError("Provide either order_by_fields or order_by_expression, not both.")
137
+
138
+
122
139
  class OlapTable(TypedMooseResource, Generic[T]):
123
140
  """Represents an OLAP table (e.g., a ClickHouse table) typed with a Pydantic model.
124
141
 
@@ -155,11 +172,11 @@ class OlapTable(TypedMooseResource, Generic[T]):
155
172
  f"OlapTable with name {name} and version {config.version or 'unversioned'} already exists"
156
173
  )
157
174
  _tables[registry_key] = self
158
-
175
+
159
176
  # Check if using legacy enum-based engine configuration
160
177
  if config.engine and isinstance(config.engine, ClickHouseEngines):
161
178
  logger = Logger(action="OlapTable")
162
-
179
+
163
180
  # Special case for S3Queue - more detailed migration message
164
181
  if config.engine == ClickHouseEngines.S3Queue:
165
182
  logger.highlight(
@@ -179,7 +196,7 @@ class OlapTable(TypedMooseResource, Generic[T]):
179
196
  f" New: from moose_lib.blocks import {engine_name}Engine; engine={engine_name}Engine()\n"
180
197
  "The new API provides better type safety and configuration options."
181
198
  )
182
-
199
+
183
200
  # Also emit a Python warning for development environments
184
201
  warnings.warn(
185
202
  f"Table '{name}' uses deprecated ClickHouseEngines enum. "
@@ -343,9 +360,9 @@ class OlapTable(TypedMooseResource, Generic[T]):
343
360
  )
344
361
 
345
362
  def _validate_insert_parameters(
346
- self,
347
- data: Union[List[T], Iterator[T]],
348
- options: Optional[InsertOptions]
363
+ self,
364
+ data: Union[List[T], Iterator[T]],
365
+ options: Optional[InsertOptions]
349
366
  ) -> Tuple[bool, str, bool]:
350
367
  """Validate input parameters and strategy compatibility.
351
368
 
@@ -372,11 +389,11 @@ class OlapTable(TypedMooseResource, Generic[T]):
372
389
  return is_stream, strategy, should_validate
373
390
 
374
391
  def _perform_pre_insertion_validation(
375
- self,
376
- data: List[T],
377
- should_validate: bool,
378
- strategy: str,
379
- options: Optional[InsertOptions] = None
392
+ self,
393
+ data: List[T],
394
+ should_validate: bool,
395
+ strategy: str,
396
+ options: Optional[InsertOptions] = None
380
397
  ) -> Tuple[List[T], List[ValidationError]]:
381
398
  """Perform pre-insertion validation for array data.
382
399
 
@@ -421,11 +438,11 @@ class OlapTable(TypedMooseResource, Generic[T]):
421
438
  return data, []
422
439
 
423
440
  def _handle_validation_errors(
424
- self,
425
- validation_errors: List[ValidationError],
426
- strategy: str,
427
- data: List[T],
428
- options: Optional[InsertOptions]
441
+ self,
442
+ validation_errors: List[ValidationError],
443
+ strategy: str,
444
+ data: List[T],
445
+ options: Optional[InsertOptions]
429
446
  ) -> None:
430
447
  """Handle validation errors based on the specified strategy.
431
448
 
@@ -448,10 +465,10 @@ class OlapTable(TypedMooseResource, Generic[T]):
448
465
  )
449
466
 
450
467
  def _check_validation_thresholds(
451
- self,
452
- validation_errors: List[ValidationError],
453
- total_records: int,
454
- options: Optional[InsertOptions]
468
+ self,
469
+ validation_errors: List[ValidationError],
470
+ total_records: int,
471
+ options: Optional[InsertOptions]
455
472
  ) -> None:
456
473
  """Check if validation errors exceed configured thresholds.
457
474
 
@@ -464,14 +481,14 @@ class OlapTable(TypedMooseResource, Generic[T]):
464
481
  validation_failed_ratio = validation_failed_count / total_records
465
482
 
466
483
  if (options and options.allow_errors is not None and
467
- validation_failed_count > options.allow_errors):
484
+ validation_failed_count > options.allow_errors):
468
485
  raise ValueError(
469
486
  f"Too many validation failures: {validation_failed_count} > {options.allow_errors}. "
470
487
  f"Errors: {', '.join(e.error for e in validation_errors)}"
471
488
  )
472
489
 
473
490
  if (options and options.allow_errors_ratio is not None and
474
- validation_failed_ratio > options.allow_errors_ratio):
491
+ validation_failed_ratio > options.allow_errors_ratio):
475
492
  raise ValueError(
476
493
  f"Validation failure ratio too high: {validation_failed_ratio:.3f} > "
477
494
  f"{options.allow_errors_ratio}. Errors: {', '.join(e.error for e in validation_errors)}"
@@ -492,13 +509,13 @@ class OlapTable(TypedMooseResource, Generic[T]):
492
509
  return {**settings, "wait_end_of_query": 1}
493
510
 
494
511
  def _prepare_insert_options(
495
- self,
496
- table_name: str,
497
- data: Union[List[T], Iterator[T]],
498
- validated_data: List[T],
499
- is_stream: bool,
500
- strategy: str,
501
- options: Optional[InsertOptions]
512
+ self,
513
+ table_name: str,
514
+ data: Union[List[T], Iterator[T]],
515
+ validated_data: List[T],
516
+ is_stream: bool,
517
+ strategy: str,
518
+ options: Optional[InsertOptions]
502
519
  ) -> tuple[str, bytes, dict]:
503
520
  """Prepare insert options for JSONEachRow raw SQL insert, returning settings dict."""
504
521
  # Base settings for all inserts
@@ -511,7 +528,7 @@ class OlapTable(TypedMooseResource, Generic[T]):
511
528
  }
512
529
  settings = self._with_wait_end_settings(base_settings)
513
530
  if (strategy == "discard" and options and
514
- (options.allow_errors is not None or options.allow_errors_ratio is not None)):
531
+ (options.allow_errors is not None or options.allow_errors_ratio is not None)):
515
532
  if options.allow_errors is not None:
516
533
  settings["input_format_allow_errors_num"] = options.allow_errors
517
534
  if options.allow_errors_ratio is not None:
@@ -536,13 +553,13 @@ class OlapTable(TypedMooseResource, Generic[T]):
536
553
  return quote_identifier(table_name), json_lines, settings
537
554
 
538
555
  def _create_success_result(
539
- self,
540
- data: Union[List[T], Iterator[T]],
541
- validated_data: List[T],
542
- validation_errors: List[ValidationError],
543
- is_stream: bool,
544
- should_validate: bool,
545
- strategy: str
556
+ self,
557
+ data: Union[List[T], Iterator[T]],
558
+ validated_data: List[T],
559
+ validation_errors: List[ValidationError],
560
+ is_stream: bool,
561
+ should_validate: bool,
562
+ strategy: str
546
563
  ) -> InsertResult[T]:
547
564
  """Create appropriate result based on input type.
548
565
 
@@ -585,10 +602,10 @@ class OlapTable(TypedMooseResource, Generic[T]):
585
602
  return result
586
603
 
587
604
  def _retry_individual_records(
588
- self,
589
- client: Client,
590
- records: List[T],
591
- options: InsertOptions
605
+ self,
606
+ client: Client,
607
+ records: List[T],
608
+ options: InsertOptions
592
609
  ) -> InsertResult[T]:
593
610
  successful: List[T] = []
594
611
  failed: List[FailedRecord[T]] = []
@@ -642,13 +659,13 @@ class OlapTable(TypedMooseResource, Generic[T]):
642
659
  )
643
660
 
644
661
  def _insert_array_data(
645
- self,
646
- client: Client,
647
- table_name: str,
648
- data: List[T],
649
- should_validate: bool,
650
- strategy: str,
651
- options: Optional[InsertOptions]
662
+ self,
663
+ client: Client,
664
+ table_name: str,
665
+ data: List[T],
666
+ should_validate: bool,
667
+ strategy: str,
668
+ options: Optional[InsertOptions]
652
669
  ) -> InsertResult[T]:
653
670
  """Insert array data into the table with validation and error handling.
654
671
 
@@ -701,12 +718,12 @@ class OlapTable(TypedMooseResource, Generic[T]):
701
718
  )
702
719
 
703
720
  def _insert_stream(
704
- self,
705
- client: Client,
706
- table_name: str,
707
- data: Iterator[T],
708
- strategy: str,
709
- options: Optional[InsertOptions]
721
+ self,
722
+ client: Client,
723
+ table_name: str,
724
+ data: Iterator[T],
725
+ strategy: str,
726
+ options: Optional[InsertOptions]
710
727
  ) -> InsertResult[T]:
711
728
  """Insert data from an iterator into the table.
712
729
 
@@ -769,9 +786,9 @@ class OlapTable(TypedMooseResource, Generic[T]):
769
786
  raise ValueError(f"Too many errors during stream insert: {e}")
770
787
 
771
788
  def insert(
772
- self,
773
- data: Union[List[T], Iterator[T]],
774
- options: Optional[InsertOptions] = None
789
+ self,
790
+ data: Union[List[T], Iterator[T]],
791
+ options: Optional[InsertOptions] = None
775
792
  ) -> InsertResult[T]:
776
793
  """Insert data into the table with validation and error handling.
777
794
 
@@ -872,7 +889,7 @@ class OlapTable(TypedMooseResource, Generic[T]):
872
889
  if is_array_nested_type(data_type):
873
890
  # For Array(Nested(...)), wrap each item in its own array and recurse
874
891
  if (isinstance(value, list) and
875
- (len(value) == 0 or isinstance(value[0], dict))):
892
+ (len(value) == 0 or isinstance(value[0], dict))):
876
893
  nested_columns = data_type.element_type.columns
877
894
  result[col.name] = [
878
895
  [self._map_to_clickhouse_record(item, nested_columns)]
@@ -884,4 +901,4 @@ class OlapTable(TypedMooseResource, Generic[T]):
884
901
  result[col.name] = self._map_to_clickhouse_record(value, data_type.columns)
885
902
  # All other types: leave as is
886
903
 
887
- return result
904
+ return result
@@ -160,7 +160,7 @@ class TableConfig(BaseModel):
160
160
 
161
161
  name: str
162
162
  columns: List[Column]
163
- order_by: List[str]
163
+ order_by: List[str] | str
164
164
  partition_by: Optional[str]
165
165
  engine_config: Optional[EngineConfigDict] = Field(None, discriminator='engine')
166
166
  version: Optional[str] = None
@@ -367,7 +367,7 @@ def _convert_basic_engine_instance(engine: "EngineConfig") -> Optional[EngineCon
367
367
  MergeTreeEngine, ReplacingMergeTreeEngine,
368
368
  AggregatingMergeTreeEngine, SummingMergeTreeEngine
369
369
  )
370
-
370
+
371
371
  if isinstance(engine, MergeTreeEngine):
372
372
  return MergeTreeConfigDict()
373
373
  elif isinstance(engine, ReplacingMergeTreeEngine):
@@ -395,7 +395,7 @@ def _convert_replicated_engine_instance(engine: "EngineConfig") -> Optional[Engi
395
395
  ReplicatedMergeTreeEngine, ReplicatedReplacingMergeTreeEngine,
396
396
  ReplicatedAggregatingMergeTreeEngine, ReplicatedSummingMergeTreeEngine
397
397
  )
398
-
398
+
399
399
  if isinstance(engine, ReplicatedMergeTreeEngine):
400
400
  return ReplicatedMergeTreeConfigDict(
401
401
  keeper_path=engine.keeper_path,
@@ -432,7 +432,7 @@ def _convert_engine_instance_to_config_dict(engine: "EngineConfig") -> EngineCon
432
432
  EngineConfigDict with engine-specific configuration
433
433
  """
434
434
  from moose_lib.blocks import S3QueueEngine
435
-
435
+
436
436
  # Try S3Queue first
437
437
  if isinstance(engine, S3QueueEngine):
438
438
  return S3QueueConfigDict(
@@ -443,17 +443,17 @@ def _convert_engine_instance_to_config_dict(engine: "EngineConfig") -> EngineCon
443
443
  compression=engine.compression,
444
444
  headers=engine.headers
445
445
  )
446
-
446
+
447
447
  # Try basic engines
448
448
  basic_config = _convert_basic_engine_instance(engine)
449
449
  if basic_config:
450
450
  return basic_config
451
-
451
+
452
452
  # Try replicated engines
453
453
  replicated_config = _convert_replicated_engine_instance(engine)
454
454
  if replicated_config:
455
455
  return replicated_config
456
-
456
+
457
457
  # Fallback for any other EngineConfig subclass
458
458
  return BaseEngineConfigDict(engine=engine.__class__.__name__.replace("Engine", ""))
459
459
 
@@ -558,10 +558,18 @@ def to_infra_map() -> dict:
558
558
  f"{table.name}_{table.config.version}" if table.config.version else table.name
559
559
  )
560
560
 
561
+ # Determine ORDER BY: list of fields or single expression
562
+ has_fields = bool(table.config.order_by_fields)
563
+ has_expr = table.config.order_by_expression is not None
564
+ if has_fields and has_expr:
565
+ raise ValueError(f"Table {table.name}: Provide either order_by_fields or order_by_expression, not both.")
566
+
567
+ order_by_value = table.config.order_by_expression if has_expr else table.config.order_by_fields
568
+
561
569
  tables[id_key] = TableConfig(
562
570
  name=table.name,
563
571
  columns=table._column_list,
564
- order_by=table.config.order_by_fields,
572
+ order_by=order_by_value,
565
573
  partition_by=table.config.partition_by,
566
574
  engine_config=engine_config,
567
575
  version=table.config.version,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moose_lib
3
- Version: 0.6.119
3
+ Version: 0.6.121
4
4
  Home-page: https://www.fiveonefour.com/moose
5
5
  Author: Fiveonefour Labs Inc.
6
6
  Author-email: support@fiveonefour.com
File without changes
File without changes
File without changes