moose-lib 0.6.57__py3-none-any.whl → 0.6.59__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 moose-lib might be problematic. Click here for more details.

@@ -13,5 +13,7 @@ _ingest_apis: Dict[str, Any] = {}
13
13
  _apis: Dict[str, Any] = {}
14
14
  # Alias map for O(1) fallback of sole versioned APIs: base name -> handler
15
15
  _api_name_aliases: Dict[str, Any] = {}
16
+ # Map from custom paths to API instances for path-based lookup
17
+ _api_path_map: Dict[str, Any] = {}
16
18
  _sql_resources: Dict[str, Any] = {}
17
- _workflows: Dict[str, Any] = {}
19
+ _workflows: Dict[str, Any] = {}
@@ -12,7 +12,7 @@ from pydantic import BaseModel
12
12
  from pydantic.json_schema import JsonSchemaValue
13
13
 
14
14
  from .types import BaseTypedResource, T, U
15
- from ._registry import _apis, _api_name_aliases
15
+ from ._registry import _apis, _api_name_aliases, _api_path_map
16
16
 
17
17
  # Global base URL configuration
18
18
  _global_base_url: Optional[str] = None
@@ -48,9 +48,11 @@ class ApiConfig(BaseModel):
48
48
 
49
49
  Attributes:
50
50
  version: Optional version string.
51
+ path: Optional custom path for the API endpoint. If not specified, defaults to the API name.
51
52
  metadata: Optional metadata for the API.
52
53
  """
53
54
  version: Optional[str] = None
55
+ path: Optional[str] = None
54
56
  metadata: Optional[dict] = None
55
57
 
56
58
 
@@ -113,12 +115,68 @@ class Api(BaseTypedResource, Generic[U]):
113
115
  else:
114
116
  # Neither provided, use default config
115
117
  self.config = ApiConfig()
116
-
118
+
117
119
  self.query_function = query_function
118
120
  self.metadata = getattr(self.config, 'metadata', {}) or {}
119
121
  key = _generate_api_key(name, self.config.version)
122
+
123
+ # Check for duplicate registration
124
+ if key in _apis:
125
+ raise ValueError(
126
+ f"Consumption API with name {name} and version {self.config.version} already exists"
127
+ )
120
128
  _apis[key] = self
121
129
 
130
+ # Register by custom path if provided
131
+ if self.config.path:
132
+ # For unversioned APIs or when path should be used as-is
133
+ if self.config.version is None:
134
+ # Check for collision
135
+ if self.config.path in _api_path_map:
136
+ existing = _api_path_map[self.config.path]
137
+ raise ValueError(
138
+ f'Cannot register API "{name}" with custom path "{self.config.path}" - '
139
+ f'this path is already used by API "{existing.name}"'
140
+ )
141
+ # Only register unversioned path for unversioned APIs
142
+ _api_path_map[self.config.path] = self
143
+ else:
144
+ # For versioned APIs, check if version is already in the path
145
+ path_ends_with_version = (
146
+ self.config.path.endswith(f"/{self.config.version}") or
147
+ self.config.path == self.config.version or
148
+ (self.config.path.endswith(self.config.version) and
149
+ len(self.config.path) > len(self.config.version) and
150
+ self.config.path[-(len(self.config.version) + 1)] == '/')
151
+ )
152
+
153
+ if path_ends_with_version:
154
+ # Path already contains version, check for collision
155
+ if self.config.path in _api_path_map:
156
+ existing = _api_path_map[self.config.path]
157
+ raise ValueError(
158
+ f'Cannot register API "{name}" with path "{self.config.path}" - '
159
+ f'this path is already used by API "{existing.name}"'
160
+ )
161
+ _api_path_map[self.config.path] = self
162
+ else:
163
+ # Path doesn't contain version, register with version appended
164
+ path_with_version = f"{self.config.path.rstrip('/')}/{self.config.version}"
165
+
166
+ # Check for collision on versioned path
167
+ if path_with_version in _api_path_map:
168
+ existing = _api_path_map[path_with_version]
169
+ raise ValueError(
170
+ f'Cannot register API "{name}" with path "{path_with_version}" - '
171
+ f'this path is already used by API "{existing.name}"'
172
+ )
173
+ _api_path_map[path_with_version] = self
174
+
175
+ # Also register the unversioned path if not already claimed
176
+ # (This is intentionally more permissive - first API gets the unversioned path)
177
+ if self.config.path not in _api_path_map:
178
+ _api_path_map[self.config.path] = self
179
+
122
180
  # Maintain alias for base name:
123
181
  # - If explicit unversioned registered, alias -> that
124
182
  # - Else, if exactly one versioned exists, alias -> that
@@ -205,8 +263,27 @@ class Api(BaseTypedResource, Generic[U]):
205
263
  "MOOSE_BASE_URL environment variable, or pass base_url parameter."
206
264
  )
207
265
 
208
- # Construct the API endpoint URL
209
- url = f"{effective_base_url.rstrip('/')}/api/{self.name}"
266
+ # Construct the API endpoint URL using custom path or default to name
267
+ if self.config.path:
268
+ # Check if the custom path already contains the version
269
+ if self.config.version:
270
+ path_ends_with_version = (
271
+ self.config.path.endswith(f"/{self.config.version}") or
272
+ self.config.path == self.config.version or
273
+ (self.config.path.endswith(self.config.version) and
274
+ len(self.config.path) > len(self.config.version) and
275
+ self.config.path[-(len(self.config.version) + 1)] == '/')
276
+ )
277
+ if path_ends_with_version:
278
+ path = self.config.path
279
+ else:
280
+ path = f"{self.config.path.rstrip('/')}/{self.config.version}"
281
+ else:
282
+ path = self.config.path
283
+ else:
284
+ # Default to name with optional version
285
+ path = self.name if not self.config.version else f"{self.name}/{self.config.version}"
286
+ url = f"{effective_base_url.rstrip('/')}/api/{path}"
210
287
 
211
288
  # Convert Pydantic model to dictionary
212
289
  params_dict = params.model_dump()
@@ -17,9 +17,11 @@ class IngestConfig(BaseModel):
17
17
 
18
18
  Attributes:
19
19
  version: Optional version string.
20
+ path: Optional custom path for the ingestion endpoint.
20
21
  metadata: Optional metadata for the ingestion point.
21
22
  """
22
23
  version: Optional[str] = None
24
+ path: Optional[str] = None
23
25
  metadata: Optional[dict] = None
24
26
 
25
27
  @dataclasses.dataclass
@@ -28,12 +30,15 @@ class IngestConfigWithDestination[T: BaseModel]:
28
30
 
29
31
  Attributes:
30
32
  destination: The `Stream` where ingested data will be sent.
33
+ dead_letter_queue: Optional dead letter queue for failed messages.
31
34
  version: Optional version string.
35
+ path: Optional custom path for the ingestion endpoint.
32
36
  metadata: Optional metadata for the ingestion configuration.
33
37
  """
34
38
  destination: Stream[T]
35
39
  dead_letter_queue: Optional[DeadLetterQueue[T]] = None
36
40
  version: Optional[str] = None
41
+ path: Optional[str] = None
37
42
  metadata: Optional[dict] = None
38
43
 
39
44
  class IngestApi(TypedMooseResource, Generic[T]):
@@ -24,7 +24,9 @@ class IngestPipelineConfig(BaseModel):
24
24
  table: Configuration for the OLAP table component.
25
25
  stream: Configuration for the stream component.
26
26
  ingest: Configuration for the ingest API component.
27
+ dead_letter_queue: Configuration for the dead letter queue.
27
28
  version: Optional version string applied to all created components.
29
+ path: Optional custom path for the ingestion API endpoint.
28
30
  metadata: Optional metadata for the ingestion pipeline.
29
31
  life_cycle: Determines how changes in code will propagate to the resources.
30
32
  """
@@ -33,6 +35,7 @@ class IngestPipelineConfig(BaseModel):
33
35
  ingest: bool | IngestConfig = True
34
36
  dead_letter_queue: bool | StreamConfig = True
35
37
  version: Optional[str] = None
38
+ path: Optional[str] = None
36
39
  metadata: Optional[dict] = None
37
40
  life_cycle: Optional[LifeCycle] = None
38
41
 
@@ -154,6 +157,8 @@ class IngestPipeline(TypedMooseResource, Generic[T]):
154
157
  ingest_config_dict["destination"] = self.stream
155
158
  if config.version:
156
159
  ingest_config_dict["version"] = config.version
160
+ if config.path:
161
+ ingest_config_dict["path"] = config.path
157
162
  if self.dead_letter_queue:
158
163
  ingest_config_dict["dead_letter_queue"] = self.dead_letter_queue
159
164
  ingest_config_dict["metadata"] = ingest_metadata
@@ -19,6 +19,7 @@ from ._registry import (
19
19
  _sql_resources,
20
20
  _workflows,
21
21
  _api_name_aliases,
22
+ _api_path_map,
22
23
  )
23
24
 
24
25
  def get_tables() -> Dict[str, OlapTable]:
@@ -50,11 +51,25 @@ def get_apis() -> Dict[str, Api]:
50
51
  return _apis
51
52
 
52
53
  def get_api(name: str) -> Optional[Api]:
53
- """Get a registered API by name.
54
+ """Get a registered API by name or path.
54
55
 
55
- Supports unversioned lookup by name via alias map when only a single versioned API exists.
56
+ Supports:
57
+ - Direct lookup by name:version
58
+ - Unversioned lookup by name via alias map when only a single versioned API exists
59
+ - Lookup by custom path (if configured)
56
60
  """
57
- return _apis.get(name) or _api_name_aliases.get(name)
61
+ # Try direct lookup first
62
+ api = _apis.get(name)
63
+ if api:
64
+ return api
65
+
66
+ # Try alias lookup
67
+ api = _api_name_aliases.get(name)
68
+ if api:
69
+ return api
70
+
71
+ # Try path-based lookup
72
+ return _api_path_map.get(name)
58
73
 
59
74
  def get_sql_resources() -> Dict[str, SqlResource]:
60
75
  """Get all registered SQL resources."""
moose_lib/internal.py CHANGED
@@ -172,7 +172,9 @@ class IngestApiConfig(BaseModel):
172
172
  name: Name of the Ingest API.
173
173
  columns: List of columns expected in the input data.
174
174
  write_to: The target stream where the ingested data is written.
175
+ dead_letter_queue: Optional dead letter queue name.
175
176
  version: Optional version string of the API configuration.
177
+ path: Optional custom path for the ingestion endpoint.
176
178
  metadata: Optional metadata for the API.
177
179
  """
178
180
  model_config = model_config
@@ -182,6 +184,7 @@ class IngestApiConfig(BaseModel):
182
184
  write_to: Target
183
185
  dead_letter_queue: Optional[str] = None
184
186
  version: Optional[str] = None
187
+ path: Optional[str] = None
185
188
  metadata: Optional[dict] = None
186
189
  json_schema: dict[str, Any] = Field(serialization_alias="schema")
187
190
 
@@ -194,6 +197,7 @@ class InternalApiConfig(BaseModel):
194
197
  query_params: List of columns representing the expected query parameters.
195
198
  response_schema: JSON schema definition of the API's response body.
196
199
  version: Optional version string of the API configuration.
200
+ path: Optional custom path for the API endpoint.
197
201
  metadata: Optional metadata for the API.
198
202
  """
199
203
  model_config = model_config
@@ -202,6 +206,7 @@ class InternalApiConfig(BaseModel):
202
206
  query_params: List[Column]
203
207
  response_schema: JsonSchemaValue
204
208
  version: Optional[str] = None
209
+ path: Optional[str] = None
205
210
  metadata: Optional[dict] = None
206
211
 
207
212
 
@@ -479,6 +484,7 @@ def to_infra_map() -> dict:
479
484
  name=name,
480
485
  columns=_to_columns(api._t),
481
486
  version=api.config.version,
487
+ path=api.config.path,
482
488
  write_to=Target(
483
489
  kind="stream",
484
490
  name=api.config.destination.name
@@ -496,6 +502,7 @@ def to_infra_map() -> dict:
496
502
  query_params=_to_columns(api.model_type),
497
503
  response_schema=api.get_response_schema(),
498
504
  version=api.config.version,
505
+ path=api.config.path,
499
506
  metadata=getattr(api, "metadata", None),
500
507
  )
501
508
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moose_lib
3
- Version: 0.6.57
3
+ Version: 0.6.59
4
4
  Home-page: https://www.fiveonefour.com/moose
5
5
  Author: Fiveonefour Labs Inc.
6
6
  Author-email: support@fiveonefour.com
@@ -2,8 +2,8 @@ moose_lib/__init__.py,sha256=LiUdVqmtASPqbMF53dXVZzCdU1jtFVx62_tiSbW65a0,162
2
2
  moose_lib/blocks.py,sha256=gC8dlV3HI4R8lO2wAxgLzn8WjKEMBjLiKyNAy4bssGE,11379
3
3
  moose_lib/commons.py,sha256=FUpRv8D3-LeGcjhcqtDyiimz5izwpCq53h50ydxC_uA,3711
4
4
  moose_lib/data_models.py,sha256=HT8ROf7HLS_BBlWvF-zGR7P-4gYURdMgfMlXa0O4i4s,10744
5
- moose_lib/dmv2-serializer.py,sha256=CL_Pvvg8tJOT8Qk6hywDNzY8MYGhMVdTOw8arZi3jng,49
6
- moose_lib/internal.py,sha256=MPSdZkJBi0tNyKTAkcT5pexraUo6roeNbg1L6gCsX68,20832
5
+ moose_lib/dmv2_serializer.py,sha256=CL_Pvvg8tJOT8Qk6hywDNzY8MYGhMVdTOw8arZi3jng,49
6
+ moose_lib/internal.py,sha256=InJ3Mw5E3kZU8LSZLw7NTqQtsCLeNeOHpzc9ueO__FM,21142
7
7
  moose_lib/main.py,sha256=dLcRE4jshP6ViDVLj--Y83QRsEp0dpwh7WilSEZ4ICk,18541
8
8
  moose_lib/query_param.py,sha256=kxcR09BMIsEg4o2qetjKrVu1YFRaLfMEzwzyGsKUpvA,6474
9
9
  moose_lib/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -12,14 +12,14 @@ moose_lib/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
12
12
  moose_lib/config/config_file.py,sha256=NyjY6YFraBel7vBk18lLkpGaPR1viKMAEv4ZldyfLIA,2585
13
13
  moose_lib/config/runtime.py,sha256=h4SXRn4Mlbeh02lKN7HX2Mdp3QzySnZOeSvm8G-B3ko,3857
14
14
  moose_lib/dmv2/__init__.py,sha256=3DVAtNMZUoP94CMJBFhuXfYEQXDbQUNKSgg9XnKqae0,2768
15
- moose_lib/dmv2/_registry.py,sha256=RweMQWHSvhxgSceZ5mfd0NGnJ-49VZORbaFmw-O0bFY,611
16
- moose_lib/dmv2/consumption.py,sha256=f43XPgJn1JLPPEpYbJ1FQ8Efi92YJH7DWa1wb5TY38g,8747
17
- moose_lib/dmv2/ingest_api.py,sha256=Snek9NGwaJl_BuImSWGtQq91m9D3AJ4qBoGiKZ-9yTQ,2323
18
- moose_lib/dmv2/ingest_pipeline.py,sha256=2tDM4gCjQMdDRDjvymI5Z1DlkXULx9nY4nG9M3YXpjk,7033
15
+ moose_lib/dmv2/_registry.py,sha256=gsLuWOvekgQRVvjVjPRHW2hN48LWyOWpLSpS2I-CWHc,708
16
+ moose_lib/dmv2/consumption.py,sha256=sWfGwgCBQIIrhFEbx1CULfoN3rFFoy8uCBUefu4ejiw,12928
17
+ moose_lib/dmv2/ingest_api.py,sha256=XhvHHgGPXp-BuRpAALth-FRhanwy-zJQ_83Cg_RLolM,2586
18
+ moose_lib/dmv2/ingest_pipeline.py,sha256=dECi4ZM5G6me2rJM_2xzATmTFsJGxhI-cyOs5CnbTFs,7284
19
19
  moose_lib/dmv2/life_cycle.py,sha256=wl0k6yzwU1MJ_fO_UkN29buoY5G6ChYZvfwigP9fVfM,1254
20
20
  moose_lib/dmv2/materialized_view.py,sha256=3JbNLC26p7xOQK0DpgkreWKx7k5V3x3bz9a7kjDqKuU,4876
21
21
  moose_lib/dmv2/olap_table.py,sha256=fqGEphHubfR3oxVb6cXwmHVgx7gvb5QO7GCgDNnwAJI,33938
22
- moose_lib/dmv2/registry.py,sha256=janjYszIXDnMfuwkNhoiBmMLar3umQ3Npw9Ms-u6p-8,2373
22
+ moose_lib/dmv2/registry.py,sha256=SZOXhSC3kizhPHKQN6ZWzoMZHl90AUG3H_WKVev5zIU,2680
23
23
  moose_lib/dmv2/sql_resource.py,sha256=kUZoGqxhZMHMthtBZGYJBxTFjXkspXiWLXhJRYXgGUM,1864
24
24
  moose_lib/dmv2/stream.py,sha256=jiUWBsjFalLLP63mikOxyHRdieiDAlzf9lXfLye-Wjc,10761
25
25
  moose_lib/dmv2/types.py,sha256=5FsB0HLHFkYB-8cjJ0rtRUjqahVA-ToLr2JXT1lFiss,3276
@@ -34,7 +34,7 @@ tests/conftest.py,sha256=ZVJNbnr4DwbcqkTmePW6U01zAzE6QD0kNAEZjPG1f4s,169
34
34
  tests/test_moose.py,sha256=mBsx_OYWmL8ppDzL_7Bd7xR6qf_i3-pCIO3wm2iQNaA,2136
35
35
  tests/test_redis_client.py,sha256=d9_MLYsJ4ecVil_jPB2gW3Q5aWnavxmmjZg2uYI3LVo,3256
36
36
  tests/test_s3queue_config.py,sha256=F05cnD61S2wBKPabcpEJxf55-DJGF4nLqwBb6aFbprc,9741
37
- moose_lib-0.6.57.dist-info/METADATA,sha256=K56SiYlkK3_a-aPk3R1mNYJFoBr10nmX1UIzgnw32Ts,730
38
- moose_lib-0.6.57.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- moose_lib-0.6.57.dist-info/top_level.txt,sha256=XEns2-4aCmGp2XjJAeEH9TAUcGONLnSLy6ycT9FSJh8,16
40
- moose_lib-0.6.57.dist-info/RECORD,,
37
+ moose_lib-0.6.59.dist-info/METADATA,sha256=Gy84lVrqZSIqkuX0kowOTB3vjDD4xGdbQNsSaHWydA0,730
38
+ moose_lib-0.6.59.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ moose_lib-0.6.59.dist-info/top_level.txt,sha256=XEns2-4aCmGp2XjJAeEH9TAUcGONLnSLy6ycT9FSJh8,16
40
+ moose_lib-0.6.59.dist-info/RECORD,,
File without changes