moose-lib 0.6.148.dev3442438466__py3-none-any.whl → 0.6.283__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.
Files changed (59) hide show
  1. moose_lib/__init__.py +34 -3
  2. moose_lib/blocks.py +416 -52
  3. moose_lib/clients/redis_client.py +26 -14
  4. moose_lib/commons.py +37 -30
  5. moose_lib/config/config_file.py +5 -1
  6. moose_lib/config/runtime.py +73 -34
  7. moose_lib/data_models.py +331 -61
  8. moose_lib/dmv2/__init__.py +69 -73
  9. moose_lib/dmv2/_registry.py +2 -1
  10. moose_lib/dmv2/_source_capture.py +37 -0
  11. moose_lib/dmv2/consumption.py +55 -32
  12. moose_lib/dmv2/ingest_api.py +9 -2
  13. moose_lib/dmv2/ingest_pipeline.py +35 -16
  14. moose_lib/dmv2/life_cycle.py +3 -1
  15. moose_lib/dmv2/materialized_view.py +24 -14
  16. moose_lib/dmv2/moose_model.py +165 -0
  17. moose_lib/dmv2/olap_table.py +299 -151
  18. moose_lib/dmv2/registry.py +18 -3
  19. moose_lib/dmv2/sql_resource.py +16 -8
  20. moose_lib/dmv2/stream.py +75 -23
  21. moose_lib/dmv2/types.py +14 -8
  22. moose_lib/dmv2/view.py +13 -6
  23. moose_lib/dmv2/web_app.py +11 -6
  24. moose_lib/dmv2/web_app_helpers.py +5 -1
  25. moose_lib/dmv2/workflow.py +37 -9
  26. moose_lib/internal.py +340 -56
  27. moose_lib/main.py +87 -56
  28. moose_lib/query_builder.py +18 -5
  29. moose_lib/query_param.py +54 -20
  30. moose_lib/secrets.py +122 -0
  31. moose_lib/streaming/streaming_function_runner.py +233 -117
  32. moose_lib/utilities/sql.py +0 -1
  33. {moose_lib-0.6.148.dev3442438466.dist-info → moose_lib-0.6.283.dist-info}/METADATA +18 -1
  34. moose_lib-0.6.283.dist-info/RECORD +63 -0
  35. tests/__init__.py +1 -1
  36. tests/conftest.py +6 -5
  37. tests/test_backward_compatibility.py +85 -0
  38. tests/test_cluster_validation.py +85 -0
  39. tests/test_codec.py +75 -0
  40. tests/test_column_formatting.py +80 -0
  41. tests/test_fixedstring.py +43 -0
  42. tests/test_iceberg_config.py +105 -0
  43. tests/test_int_types.py +211 -0
  44. tests/test_kafka_config.py +141 -0
  45. tests/test_materialized.py +74 -0
  46. tests/test_metadata.py +37 -0
  47. tests/test_moose.py +21 -30
  48. tests/test_moose_model.py +153 -0
  49. tests/test_olap_table_moosemodel.py +89 -0
  50. tests/test_olap_table_versioning.py +52 -58
  51. tests/test_query_builder.py +97 -9
  52. tests/test_redis_client.py +10 -3
  53. tests/test_s3queue_config.py +211 -110
  54. tests/test_secrets.py +239 -0
  55. tests/test_simple_aggregate.py +42 -40
  56. tests/test_web_app.py +11 -5
  57. moose_lib-0.6.148.dev3442438466.dist-info/RECORD +0 -47
  58. {moose_lib-0.6.148.dev3442438466.dist-info → moose_lib-0.6.283.dist-info}/WHEEL +0 -0
  59. {moose_lib-0.6.148.dev3442438466.dist-info → moose_lib-0.6.283.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ Global registries for Moose Data Model v2 (dmv2) resources.
4
4
  This module provides functions to access the registered resources.
5
5
  The actual registry dictionaries are maintained in _registry.py to avoid circular dependencies.
6
6
  """
7
+
7
8
  from typing import Optional, Dict
8
9
  from .olap_table import OlapTable
9
10
  from .stream import Stream
@@ -24,34 +25,42 @@ from ._registry import (
24
25
  _web_apps,
25
26
  )
26
27
 
28
+
27
29
  def get_tables() -> Dict[str, OlapTable]:
28
30
  """Get all registered OLAP tables."""
29
31
  return _tables
30
32
 
33
+
31
34
  def get_table(name: str) -> Optional[OlapTable]:
32
35
  """Get a registered OLAP table by name."""
33
36
  return _tables.get(name)
34
37
 
38
+
35
39
  def get_streams() -> Dict[str, Stream]:
36
40
  """Get all registered streams."""
37
41
  return _streams
38
42
 
43
+
39
44
  def get_stream(name: str) -> Optional[Stream]:
40
45
  """Get a registered stream by name."""
41
46
  return _streams.get(name)
42
47
 
48
+
43
49
  def get_ingest_apis() -> Dict[str, IngestApi]:
44
50
  """Get all registered ingestion APIs."""
45
51
  return _ingest_apis
46
52
 
53
+
47
54
  def get_ingest_api(name: str) -> Optional[IngestApi]:
48
55
  """Get a registered ingestion API by name."""
49
56
  return _ingest_apis.get(name)
50
57
 
58
+
51
59
  def get_apis() -> Dict[str, Api]:
52
60
  """Get all registered APIs."""
53
61
  return _apis
54
62
 
63
+
55
64
  def get_api(name: str) -> Optional[Api]:
56
65
  """Get a registered API by name or path.
57
66
 
@@ -64,35 +73,41 @@ def get_api(name: str) -> Optional[Api]:
64
73
  api = _apis.get(name)
65
74
  if api:
66
75
  return api
67
-
76
+
68
77
  # Try alias lookup
69
78
  api = _api_name_aliases.get(name)
70
79
  if api:
71
80
  return api
72
-
81
+
73
82
  # Try path-based lookup
74
83
  return _api_path_map.get(name)
75
84
 
85
+
76
86
  def get_sql_resources() -> Dict[str, SqlResource]:
77
87
  """Get all registered SQL resources."""
78
88
  return _sql_resources
79
89
 
90
+
80
91
  def get_sql_resource(name: str) -> Optional[SqlResource]:
81
92
  """Get a registered SQL resource by name."""
82
93
  return _sql_resources.get(name)
83
94
 
95
+
84
96
  def get_workflows() -> Dict[str, Workflow]:
85
97
  """Get all registered workflows."""
86
98
  return _workflows
87
99
 
100
+
88
101
  def get_workflow(name: str) -> Optional[Workflow]:
89
102
  """Get a registered workflow by name."""
90
103
  return _workflows.get(name)
91
104
 
105
+
92
106
  def get_web_apps() -> Dict[str, WebApp]:
93
107
  """Get all registered WebApps."""
94
108
  return _web_apps
95
109
 
110
+
96
111
  def get_web_app(name: str) -> Optional[WebApp]:
97
112
  """Get a registered WebApp by name."""
98
113
  return _web_apps.get(name)
@@ -103,4 +118,4 @@ get_consumption_apis = get_apis
103
118
  """@deprecated: Use get_apis instead of get_consumption_apis"""
104
119
 
105
120
  get_consumption_api = get_api
106
- """@deprecated: Use get_api instead of get_consumption_api"""
121
+ """@deprecated: Use get_api instead of get_consumption_api"""
@@ -4,11 +4,14 @@ Base SQL resource definitions for Moose Data Model v2 (dmv2).
4
4
  This module provides the base class for SQL resources like Views and Materialized Views,
5
5
  handling common functionality like setup/teardown SQL commands and dependency tracking.
6
6
  """
7
+
7
8
  from typing import Any, Optional, Union, List
8
9
  from pydantic import BaseModel
9
10
 
10
11
  from .olap_table import OlapTable
11
12
  from ._registry import _sql_resources
13
+ from ._source_capture import get_source_file_from_stack
14
+
12
15
 
13
16
  class SqlResource:
14
17
  """Base class for SQL resources like Views and Materialized Views.
@@ -23,22 +26,25 @@ class SqlResource:
23
26
  pulls_data_from (list[SqlObject]): List of tables/views this resource reads from.
24
27
  pushes_data_to (list[SqlObject]): List of tables/views this resource writes to.
25
28
  kind: The kind of the SQL resource (e.g., "SqlResource").
29
+ source_file: Optional path to the source file where this resource was defined.
26
30
  """
31
+
27
32
  setup: list[str]
28
33
  teardown: list[str]
29
34
  name: str
30
35
  kind: str = "SqlResource"
31
36
  pulls_data_from: list[Union[OlapTable, "SqlResource"]]
32
37
  pushes_data_to: list[Union[OlapTable, "SqlResource"]]
38
+ source_file: Optional[str]
33
39
 
34
40
  def __init__(
35
- self,
36
- name: str,
37
- setup: list[str],
38
- teardown: list[str],
39
- pulls_data_from: Optional[list[Union[OlapTable, "SqlResource"]]] = None,
40
- pushes_data_to: Optional[list[Union[OlapTable, "SqlResource"]]] = None,
41
- metadata: dict = None
41
+ self,
42
+ name: str,
43
+ setup: list[str],
44
+ teardown: list[str],
45
+ pulls_data_from: Optional[list[Union[OlapTable, "SqlResource"]]] = None,
46
+ pushes_data_to: Optional[list[Union[OlapTable, "SqlResource"]]] = None,
47
+ metadata: dict = None,
42
48
  ):
43
49
  self.name = name
44
50
  self.setup = setup
@@ -46,4 +52,6 @@ class SqlResource:
46
52
  self.pulls_data_from = pulls_data_from or []
47
53
  self.pushes_data_to = pushes_data_to or []
48
54
  self.metadata = metadata
49
- _sql_resources[name] = self
55
+ # Capture source file from call stack
56
+ self.source_file = get_source_file_from_stack()
57
+ _sql_resources[name] = self
moose_lib/dmv2/stream.py CHANGED
@@ -4,6 +4,7 @@ Stream definitions for Moose Data Model v2 (dmv2).
4
4
  This module provides classes for defining and configuring data streams,
5
5
  including stream transformations, consumers, and dead letter queues.
6
6
  """
7
+
7
8
  import dataclasses
8
9
  import datetime
9
10
  import json
@@ -50,6 +51,7 @@ class StreamConfig(BaseModel):
50
51
  life_cycle: Determines how changes in code will propagate to the resources.
51
52
  default_dead_letter_queue: default dead letter queue used by transforms/consumers
52
53
  """
54
+
53
55
  parallelism: int = 1
54
56
  retention_period: int = 60 * 60 * 24 * 7 # 7 days
55
57
  destination: Optional[OlapTable] = None
@@ -69,6 +71,7 @@ class TransformConfig(BaseModel):
69
71
  version: Optional version string to identify a specific transformation.
70
72
  Allows multiple transformations to the same destination if versions differ.
71
73
  """
74
+
72
75
  version: Optional[str] = None
73
76
  dead_letter_queue: "Optional[DeadLetterQueue]" = None
74
77
  model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -82,6 +85,7 @@ class ConsumerConfig(BaseModel):
82
85
  version: Optional version string to identify a specific consumer.
83
86
  Allows multiple consumers if versions differ.
84
87
  """
88
+
85
89
  version: Optional[str] = None
86
90
  dead_letter_queue: "Optional[DeadLetterQueue]" = None
87
91
  model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -90,6 +94,7 @@ class ConsumerConfig(BaseModel):
90
94
  @dataclasses.dataclass
91
95
  class _RoutedMessage:
92
96
  """Internal class representing a message routed to a specific stream."""
97
+
93
98
  destination: "Stream[Any]"
94
99
  values: ZeroOrMany[Any]
95
100
 
@@ -97,6 +102,7 @@ class _RoutedMessage:
97
102
  @dataclasses.dataclass
98
103
  class ConsumerEntry(Generic[T]):
99
104
  """Internal class representing a consumer with its configuration."""
105
+
100
106
  consumer: Callable[[T], None]
101
107
  config: ConsumerConfig
102
108
 
@@ -104,6 +110,7 @@ class ConsumerEntry(Generic[T]):
104
110
  @dataclasses.dataclass
105
111
  class TransformEntry(Generic[T]):
106
112
  """Internal class representing a transformation with its configuration."""
113
+
107
114
  destination: "Stream[Any]"
108
115
  transformation: Callable[[T], ZeroOrMany[Any]]
109
116
  config: TransformConfig
@@ -128,6 +135,7 @@ class Stream(TypedMooseResource, Generic[T]):
128
135
  name (str): The name of the stream.
129
136
  model_type (type[T]): The Pydantic model associated with this stream.
130
137
  """
138
+
131
139
  config: StreamConfig
132
140
  transformations: dict[str, list[TransformEntry[T]]]
133
141
  consumers: list[ConsumerEntry[T]]
@@ -146,8 +154,12 @@ class Stream(TypedMooseResource, Generic[T]):
146
154
  self.default_dead_letter_queue = self.config.default_dead_letter_queue
147
155
  _streams[name] = self
148
156
 
149
- def add_transform(self, destination: "Stream[U]", transformation: Callable[[T], ZeroOrMany[U]],
150
- config: TransformConfig = None):
157
+ def add_transform(
158
+ self,
159
+ destination: "Stream[U]",
160
+ transformation: Callable[[T], ZeroOrMany[U]],
161
+ config: TransformConfig = None,
162
+ ):
151
163
  """Adds a transformation step from this stream to a destination stream.
152
164
 
153
165
  The transformation function receives a record of type `T` and should return
@@ -160,23 +172,37 @@ class Stream(TypedMooseResource, Generic[T]):
160
172
  """
161
173
  config = config or TransformConfig()
162
174
  if (
163
- self.default_dead_letter_queue is not None
164
- and config.dead_letter_queue is None
175
+ self.default_dead_letter_queue is not None
176
+ and config.dead_letter_queue is None
165
177
  ):
166
178
  config = config.model_copy()
167
179
  config.dead_letter_queue = self.default_dead_letter_queue
168
180
  if destination.name in self.transformations:
169
181
  existing_transforms = self.transformations[destination.name]
170
182
  # Check if a transform with this version already exists
171
- has_version = any(t.config.version == config.version for t in existing_transforms)
183
+ has_version = any(
184
+ t.config.version == config.version for t in existing_transforms
185
+ )
172
186
  if not has_version:
173
187
  existing_transforms.append(
174
- TransformEntry(destination=destination, transformation=transformation, config=config))
188
+ TransformEntry(
189
+ destination=destination,
190
+ transformation=transformation,
191
+ config=config,
192
+ )
193
+ )
175
194
  else:
176
195
  self.transformations[destination.name] = [
177
- TransformEntry(destination=destination, transformation=transformation, config=config)]
196
+ TransformEntry(
197
+ destination=destination,
198
+ transformation=transformation,
199
+ config=config,
200
+ )
201
+ ]
178
202
 
179
- def add_consumer(self, consumer: Callable[[T], None], config: ConsumerConfig = None):
203
+ def add_consumer(
204
+ self, consumer: Callable[[T], None], config: ConsumerConfig = None
205
+ ):
180
206
  """Adds a consumer function to be executed for each record in the stream.
181
207
 
182
208
  Consumers are typically used for side effects like logging or triggering external actions.
@@ -187,8 +213,8 @@ class Stream(TypedMooseResource, Generic[T]):
187
213
  """
188
214
  config = config or ConsumerConfig()
189
215
  if (
190
- self.default_dead_letter_queue is not None
191
- and config.dead_letter_queue is None
216
+ self.default_dead_letter_queue is not None
217
+ and config.dead_letter_queue is None
192
218
  ):
193
219
  config = config.model_copy()
194
220
  config.dead_letter_queue = self.default_dead_letter_queue
@@ -243,12 +269,15 @@ class Stream(TypedMooseResource, Generic[T]):
243
269
 
244
270
  def _build_full_topic_name(self, namespace: Optional[str]) -> str:
245
271
  """Build full topic name with optional namespace and version suffix."""
246
- version_suffix = f"_{self.config.version.replace('.', '_')}" if self.config.version else ""
272
+ version_suffix = (
273
+ f"_{self.config.version.replace('.', '_')}" if self.config.version else ""
274
+ )
247
275
  base = f"{self.name}{version_suffix}"
248
276
  return f"{namespace}.{base}" if namespace else base
249
277
 
250
278
  def _create_kafka_config_hash(self, cfg: RuntimeKafkaConfig) -> str:
251
279
  import hashlib
280
+
252
281
  config_string = ":".join(
253
282
  str(x)
254
283
  for x in (
@@ -273,11 +302,17 @@ class Stream(TypedMooseResource, Generic[T]):
273
302
  cfg: RuntimeKafkaConfig = config_registry.get_kafka_config()
274
303
  current_hash = self._create_kafka_config_hash(cfg)
275
304
 
276
- if self._memoized_producer is not None and self._kafka_config_hash == current_hash:
305
+ if (
306
+ self._memoized_producer is not None
307
+ and self._kafka_config_hash == current_hash
308
+ ):
277
309
  return self._memoized_producer, cfg
278
310
 
279
311
  # Close previous producer if config changed
280
- if self._memoized_producer is not None and self._kafka_config_hash != current_hash:
312
+ if (
313
+ self._memoized_producer is not None
314
+ and self._kafka_config_hash != current_hash
315
+ ):
281
316
  try:
282
317
  self._memoized_producer.flush()
283
318
  self._memoized_producer.close()
@@ -296,7 +331,7 @@ class Stream(TypedMooseResource, Generic[T]):
296
331
  sasl_password=cfg.sasl_password,
297
332
  sasl_mechanism=cfg.sasl_mechanism,
298
333
  security_protocol=cfg.security_protocol,
299
- value_serializer=lambda v: v.model_dump_json().encode('utf-8'),
334
+ value_serializer=lambda v: v.model_dump_json().encode("utf-8"),
300
335
  acks="all",
301
336
  )
302
337
 
@@ -371,7 +406,9 @@ class Stream(TypedMooseResource, Generic[T]):
371
406
  elif isinstance(sr.reference, SubjectLatest):
372
407
  schema = client.get_latest_version(sr.reference.name).schema
373
408
  else:
374
- schema = client.get_version(sr.reference.subject, sr.reference.version).schema
409
+ schema = client.get_version(
410
+ sr.reference.subject, sr.reference.version
411
+ ).schema
375
412
 
376
413
  serializer = JSONSerializer(schema, client)
377
414
 
@@ -395,9 +432,12 @@ class DeadLetterModel(BaseModel, Generic[T]):
395
432
  failed_at: Timestamp when the error occurred.
396
433
  source: Source of the error ("api", "transform", or "table").
397
434
  """
398
- model_config = ConfigDict(alias_generator=AliasGenerator(
399
- serialization_alias=to_camel,
400
- ))
435
+
436
+ model_config = ConfigDict(
437
+ alias_generator=AliasGenerator(
438
+ serialization_alias=to_camel,
439
+ )
440
+ )
401
441
  original_record: Any
402
442
  error_message: str
403
443
  error_type: str
@@ -429,10 +469,16 @@ class DeadLetterQueue(Stream, Generic[T]):
429
469
  """
430
470
  self._model_type = self._get_type(kwargs)
431
471
  kwargs["t"] = DeadLetterModel[self._model_type]
432
- super().__init__(name, config if config is not None else StreamConfig(), **kwargs)
472
+ super().__init__(
473
+ name, config if config is not None else StreamConfig(), **kwargs
474
+ )
433
475
 
434
- def add_transform(self, destination: Stream[U], transformation: Callable[[DeadLetterModel[T]], ZeroOrMany[U]],
435
- config: TransformConfig = None):
476
+ def add_transform(
477
+ self,
478
+ destination: Stream[U],
479
+ transformation: Callable[[DeadLetterModel[T]], ZeroOrMany[U]],
480
+ config: TransformConfig = None,
481
+ ):
436
482
  def wrapped_transform(record: DeadLetterModel[T]):
437
483
  record._t = self._model_type
438
484
  return transformation(record)
@@ -440,7 +486,11 @@ class DeadLetterQueue(Stream, Generic[T]):
440
486
  config = config or TransformConfig()
441
487
  super().add_transform(destination, wrapped_transform, config)
442
488
 
443
- def add_consumer(self, consumer: Callable[[DeadLetterModel[T]], None], config: ConsumerConfig = None):
489
+ def add_consumer(
490
+ self,
491
+ consumer: Callable[[DeadLetterModel[T]], None],
492
+ config: ConsumerConfig = None,
493
+ ):
444
494
  def wrapped_consumer(record: DeadLetterModel[T]):
445
495
  record._t = self._model_type
446
496
  return consumer(record)
@@ -448,7 +498,9 @@ class DeadLetterQueue(Stream, Generic[T]):
448
498
  config = config or ConsumerConfig()
449
499
  super().add_consumer(wrapped_consumer, config)
450
500
 
451
- def set_multi_transform(self, transformation: Callable[[DeadLetterModel[T]], list[_RoutedMessage]]):
501
+ def set_multi_transform(
502
+ self, transformation: Callable[[DeadLetterModel[T]], list[_RoutedMessage]]
503
+ ):
452
504
  def wrapped_transform(record: DeadLetterModel[T]):
453
505
  record._t = self._model_type
454
506
  return transformation(record)
moose_lib/dmv2/types.py CHANGED
@@ -5,6 +5,7 @@ This module provides the core type definitions and base classes used across
5
5
  the dmv2 package, including generic type parameters, type aliases, and base
6
6
  resource classes.
7
7
  """
8
+
8
9
  from typing import Any, Generic, TypeVar, Union
9
10
 
10
11
  import typing_extensions
@@ -12,10 +13,10 @@ from pydantic import BaseModel
12
13
  from pydantic.fields import FieldInfo
13
14
  from ..data_models import Column
14
15
 
15
- T = TypeVar('T', bound=BaseModel)
16
- U = TypeVar('U', bound=BaseModel)
17
- T_none = TypeVar('T_none', bound=Union[BaseModel, None])
18
- U_none = TypeVar('U_none', bound=Union[BaseModel, None])
16
+ T = TypeVar("T", bound=BaseModel)
17
+ U = TypeVar("U", bound=BaseModel)
18
+ T_none = TypeVar("T_none", bound=Union[BaseModel, None])
19
+ U_none = TypeVar("U_none", bound=Union[BaseModel, None])
19
20
  type ZeroOrMany[T] = Union[T, list[T], None]
20
21
 
21
22
 
@@ -33,6 +34,7 @@ class Cols:
33
34
  >>> print(table.cols.user_id) # Output: a column object
34
35
  >>> print(table.cols.non_existent) # Raises AttributeError
35
36
  """
37
+
36
38
  _columns: dict[str, Column]
37
39
 
38
40
  def __init__(self, columns: list[Column]):
@@ -52,7 +54,7 @@ class Cols:
52
54
  return self.__getattr__(item)
53
55
 
54
56
 
55
- @typing_extensions.deprecated('use cols in OlapTable instead')
57
+ @typing_extensions.deprecated("use cols in OlapTable instead")
56
58
  class Columns(Generic[T]):
57
59
  """Provides runtime checked column name access for Moose resources.
58
60
 
@@ -70,6 +72,7 @@ class Columns(Generic[T]):
70
72
  Args:
71
73
  model: The Pydantic model type whose fields represent the columns.
72
74
  """
75
+
73
76
  _fields: dict[str, FieldInfo]
74
77
 
75
78
  def __init__(self, model: type[T]):
@@ -90,14 +93,17 @@ class BaseTypedResource(Generic[T]):
90
93
  Attributes:
91
94
  name (str): The name of the Moose resource.
92
95
  """
96
+
93
97
  _t: type[T]
94
98
  name: str
95
99
 
96
100
  @classmethod
97
101
  def _get_type(cls, keyword_args: dict):
98
- t = keyword_args.get('t')
102
+ t = keyword_args.get("t")
99
103
  if t is None:
100
- raise ValueError(f"Use `{cls.__name__}[T](name='...')` to supply the Pydantic model type`")
104
+ raise ValueError(
105
+ f"Use `{cls.__name__}[T](name='...')` to supply the Pydantic model type`"
106
+ )
101
107
  if not isinstance(t, type) or not issubclass(t, BaseModel):
102
108
  raise ValueError(f"{t} is not a Pydantic model")
103
109
  return t
@@ -130,7 +136,7 @@ class TypedMooseResource(BaseTypedResource, Generic[T]):
130
136
  """
131
137
 
132
138
  @property
133
- @typing_extensions.deprecated('use cols in OlapTable instead', category=None)
139
+ @typing_extensions.deprecated("use cols in OlapTable instead", category=None)
134
140
  def columns(self):
135
141
  return Columns[T](self._t)
136
142
 
moose_lib/dmv2/view.py CHANGED
@@ -4,12 +4,14 @@ View definitions for Moose Data Model v2 (dmv2).
4
4
  This module provides classes for defining standard SQL Views,
5
5
  including their SQL statements and dependencies.
6
6
  """
7
+
7
8
  from typing import Union, List, Optional
8
9
  from pydantic import BaseModel
9
10
 
10
11
  from .sql_resource import SqlResource
11
12
  from .olap_table import OlapTable
12
13
 
14
+
13
15
  class View(SqlResource):
14
16
  """Represents a standard SQL database View.
15
17
 
@@ -27,10 +29,15 @@ class View(SqlResource):
27
29
  pulls_data_from (list[SqlObject]): Source tables/views.
28
30
  """
29
31
 
30
- def __init__(self, name: str, select_statement: str, base_tables: list[Union[OlapTable, SqlResource]],
31
- metadata: dict = None):
32
- setup = [
33
- f"CREATE VIEW IF NOT EXISTS {name} AS {select_statement}".strip()
34
- ]
32
+ def __init__(
33
+ self,
34
+ name: str,
35
+ select_statement: str,
36
+ base_tables: list[Union[OlapTable, SqlResource]],
37
+ metadata: dict = None,
38
+ ):
39
+ setup = [f"CREATE VIEW IF NOT EXISTS {name} AS {select_statement}".strip()]
35
40
  teardown = [f"DROP VIEW IF EXISTS {name}"]
36
- super().__init__(name, setup, teardown, pulls_data_from=base_tables, metadata=metadata)
41
+ super().__init__(
42
+ name, setup, teardown, pulls_data_from=base_tables, metadata=metadata
43
+ )
moose_lib/dmv2/web_app.py CHANGED
@@ -5,6 +5,7 @@ This module allows developers to register FastAPI applications as WebApp resourc
5
5
  that are managed by the Moose infrastructure, similar to other resources like
6
6
  OlapTables, Streams, and APIs.
7
7
  """
8
+
8
9
  from typing import Optional, Dict, Any
9
10
  from dataclasses import dataclass
10
11
 
@@ -29,6 +30,7 @@ class WebAppMetadata:
29
30
  Attributes:
30
31
  description: Optional description of the WebApp's purpose.
31
32
  """
33
+
32
34
  description: Optional[str] = None
33
35
 
34
36
 
@@ -45,6 +47,7 @@ class WebAppConfig:
45
47
  inject_moose_utils: Whether to inject MooseClient utilities into requests.
46
48
  Defaults to True.
47
49
  """
50
+
48
51
  mount_path: str
49
52
  metadata: Optional[WebAppMetadata] = None
50
53
  inject_moose_utils: bool = True
@@ -111,7 +114,9 @@ class WebApp:
111
114
  _web_apps[name] = self
112
115
 
113
116
  @staticmethod
114
- def _validate(name: str, config: WebAppConfig, existing_web_apps: Dict[str, 'WebApp']) -> None:
117
+ def _validate(
118
+ name: str, config: WebAppConfig, existing_web_apps: Dict[str, "WebApp"]
119
+ ) -> None:
115
120
  """Validate WebApp configuration.
116
121
 
117
122
  Args:
@@ -129,7 +134,7 @@ class WebApp:
129
134
  # Validate mountPath - it is required
130
135
  if not config.mount_path:
131
136
  raise ValueError(
132
- f"mountPath is required. Please specify a mount path for your WebApp (e.g., \"/myapi\")."
137
+ f'mountPath is required. Please specify a mount path for your WebApp (e.g., "/myapi").'
133
138
  )
134
139
 
135
140
  mount_path = config.mount_path
@@ -137,7 +142,7 @@ class WebApp:
137
142
  # Check for root path - not allowed as it would overlap reserved paths
138
143
  if mount_path == "/":
139
144
  raise ValueError(
140
- f"mountPath cannot be \"/\" as it would allow routes to overlap with reserved paths: "
145
+ f'mountPath cannot be "/" as it would allow routes to overlap with reserved paths: '
141
146
  f"{', '.join(RESERVED_MOUNT_PATHS)}"
142
147
  )
143
148
 
@@ -154,7 +159,7 @@ class WebApp:
154
159
  raise ValueError(
155
160
  f"mountPath cannot begin with a reserved path: "
156
161
  f"{', '.join(RESERVED_MOUNT_PATHS)}. "
157
- f"Got: \"{mount_path}\""
162
+ f'Got: "{mount_path}"'
158
163
  )
159
164
 
160
165
  # Check for duplicate mount path
@@ -162,8 +167,8 @@ class WebApp:
162
167
  existing_mount = existing_app.config.mount_path
163
168
  if existing_mount == mount_path:
164
169
  raise ValueError(
165
- f"WebApp with mountPath \"{mount_path}\" already exists "
166
- f"(used by WebApp \"{existing_name}\")"
170
+ f'WebApp with mountPath "{mount_path}" already exists '
171
+ f'(used by WebApp "{existing_name}")'
167
172
  )
168
173
 
169
174
  def __repr__(self) -> str:
@@ -4,6 +4,7 @@ Helper utilities for WebApp integration with FastAPI.
4
4
  This module provides utilities to access Moose services (ClickHouse, Temporal)
5
5
  from within FastAPI request handlers.
6
6
  """
7
+
7
8
  from typing import Optional, Any, Dict
8
9
  from dataclasses import dataclass
9
10
 
@@ -17,6 +18,7 @@ class ApiUtil:
17
18
  sql: SQL template function for building safe queries.
18
19
  jwt: JWT payload if authentication is enabled, None otherwise.
19
20
  """
21
+
20
22
  client: Any # MooseClient, typed as Any to avoid circular import
21
23
  sql: Any # sql function from moose_lib.main
22
24
  jwt: Optional[Dict[str, Any]] = None
@@ -55,7 +57,7 @@ def get_moose_utils(request: Any) -> Optional[ApiUtil]:
55
57
  ```
56
58
  """
57
59
  # FastAPI uses request.state for storing custom data
58
- if hasattr(request, 'state') and hasattr(request.state, 'moose'):
60
+ if hasattr(request, "state") and hasattr(request.state, "moose"):
59
61
  return request.state.moose
60
62
  return None
61
63
 
@@ -83,10 +85,12 @@ def get_moose_dependency():
83
85
  return result
84
86
  ```
85
87
  """
88
+
86
89
  def moose_dependency(request: Any) -> ApiUtil:
87
90
  moose = get_moose_utils(request)
88
91
  if moose is None:
89
92
  # This should rarely happen if inject_moose_utils=True
90
93
  raise RuntimeError("Moose utilities not available in request")
91
94
  return moose
95
+
92
96
  return moose_dependency