lumera 0.9.4__tar.gz → 0.9.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumera
3
- Version: 0.9.4
3
+ Version: 0.9.6
4
4
  Summary: SDK for building on Lumera platform
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: requests
@@ -5,7 +5,7 @@ This SDK provides helpers for automations running within the Lumera environment
5
5
  to interact with the Lumera API and define dynamic user interfaces.
6
6
  """
7
7
 
8
- from importlib.metadata import version, PackageNotFoundError
8
+ from importlib.metadata import PackageNotFoundError, version
9
9
 
10
10
  try:
11
11
  __version__ = version("lumera")
@@ -31,6 +31,9 @@ from .exceptions import (
31
31
  ValidationError,
32
32
  )
33
33
 
34
+ # Import file types for Pydantic automation inputs
35
+ from .files import LumeraFile, LumeraFiles
36
+
34
37
  # Import key SDK helpers to expose them at the package root.
35
38
  from .sdk import (
36
39
  CollectionField,
@@ -87,6 +90,8 @@ __all__ = [
87
90
  # Type definitions
88
91
  "CollectionField",
89
92
  "HookReplayResult",
93
+ "LumeraFile",
94
+ "LumeraFiles",
90
95
  # Exceptions
91
96
  "LumeraAPIError",
92
97
  "RecordNotUniqueError",
@@ -0,0 +1,97 @@
1
+ """
2
+ File input types for Lumera automations.
3
+
4
+ These types are used with Pydantic models to define file inputs for automations.
5
+ They generate the correct JSON schema format that the frontend recognizes for
6
+ file picker UI.
7
+
8
+ Example:
9
+ from pydantic import BaseModel, Field
10
+ from lumera import LumeraFile, LumeraFiles
11
+
12
+ class ProcessInputs(BaseModel):
13
+ report: LumeraFile = Field(..., description="Excel report to process")
14
+ attachments: LumeraFiles = Field(default=[], description="Additional files")
15
+
16
+ def main(inputs: ProcessInputs):
17
+ # inputs.report is a string path like "/tmp/lumera-files/report.xlsx"
18
+ with open(inputs.report) as f:
19
+ ...
20
+ """
21
+
22
+ from typing import Annotated, Any
23
+
24
+ # Check if Pydantic is available
25
+ try:
26
+ from pydantic import GetJsonSchemaHandler
27
+ from pydantic.json_schema import JsonSchemaValue
28
+ from pydantic_core import CoreSchema
29
+
30
+ _HAS_PYDANTIC = True
31
+ except ImportError:
32
+ _HAS_PYDANTIC = False
33
+
34
+
35
+ if _HAS_PYDANTIC:
36
+
37
+ class _LumeraFileSchema:
38
+ """
39
+ Pydantic JSON schema handler for single file inputs.
40
+
41
+ Generates: {"type": "string", "format": "file"}
42
+
43
+ The frontend recognizes this schema and renders a file picker.
44
+ At runtime, the value is a string path to the downloaded file.
45
+ """
46
+
47
+ @classmethod
48
+ def __get_pydantic_json_schema__(
49
+ cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
50
+ ) -> JsonSchemaValue:
51
+ return {"type": "string", "format": "file"}
52
+
53
+ class _LumeraFilesSchema:
54
+ """
55
+ Pydantic JSON schema handler for multiple file inputs.
56
+
57
+ Generates: {"type": "array", "items": {"type": "string", "format": "file"}}
58
+
59
+ The frontend recognizes this schema and renders a multi-file picker.
60
+ At runtime, the value is a list of string paths to downloaded files.
61
+ """
62
+
63
+ @classmethod
64
+ def __get_pydantic_json_schema__(
65
+ cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler
66
+ ) -> JsonSchemaValue:
67
+ return {"type": "array", "items": {"type": "string", "format": "file"}}
68
+
69
+ # Public types using Annotated to attach schema handlers
70
+ LumeraFile: Any = Annotated[str, _LumeraFileSchema()]
71
+ """
72
+ Type for single file input in Pydantic models.
73
+
74
+ At runtime, this is a string containing the local file path.
75
+ In JSON schema, generates {"type": "string", "format": "file"}.
76
+
77
+ Example:
78
+ class Inputs(BaseModel):
79
+ document: LumeraFile = Field(..., description="PDF to process")
80
+ """
81
+
82
+ LumeraFiles: Any = Annotated[list[str], _LumeraFilesSchema()]
83
+ """
84
+ Type for multiple file inputs in Pydantic models.
85
+
86
+ At runtime, this is a list of strings containing local file paths.
87
+ In JSON schema, generates {"type": "array", "items": {"type": "string", "format": "file"}}.
88
+
89
+ Example:
90
+ class Inputs(BaseModel):
91
+ documents: LumeraFiles = Field(default=[], description="PDFs to merge")
92
+ """
93
+
94
+ else:
95
+ # Fallback when Pydantic is not installed - types are just aliases
96
+ LumeraFile: Any = str
97
+ LumeraFiles: Any = list
@@ -510,7 +510,8 @@ def iter_all(
510
510
  yield from items
511
511
 
512
512
  # Check if there are more pages
513
- total_pages = result.get("totalPages", 0)
513
+ # Use `or 0` instead of default to handle None values
514
+ total_pages = result.get("totalPages") or 0
514
515
  if page >= total_pages:
515
516
  break
516
517
 
@@ -563,6 +564,7 @@ def ensure_collection(
563
564
  name: str,
564
565
  schema: Sequence[dict[str, Any]] | None = None,
565
566
  *,
567
+ id: str | None = None,
566
568
  indexes: Sequence[str] | None = None,
567
569
  ) -> dict[str, Any]:
568
570
  """Ensure a collection exists with the given schema (idempotent).
@@ -579,7 +581,7 @@ def ensure_collection(
579
581
  If you omit schema or indexes, existing values are preserved.
580
582
 
581
583
  Args:
582
- name: Collection name (must not start with '_')
584
+ name: Collection name (can be renamed later in UI)
583
585
  schema: List of field definitions. If provided, replaces all user fields.
584
586
  Each field is a dict with:
585
587
  - name: Field name (required)
@@ -587,33 +589,40 @@ def ensure_collection(
587
589
  relation, select, editor, lumera_file
588
590
  - required: Whether field is required (default False)
589
591
  - options: Type-specific options (e.g., collectionId for relations)
592
+ id: Optional stable identifier for the collection. If provided on creation,
593
+ this ID will be used instead of an auto-generated one. The ID remains
594
+ stable even if the collection is renamed, making it ideal for use in
595
+ automations and hooks. Must be alphanumeric with underscores only.
596
+ Cannot be changed after creation.
590
597
  indexes: Optional list of user index DDL statements. If provided,
591
598
  replaces all user indexes.
592
599
 
593
600
  Returns:
594
601
  Collection object with:
602
+ - id: The collection's stable identifier
603
+ - name: The collection's display name
595
604
  - schema: User-defined fields only (what you can modify)
596
605
  - indexes: User-defined indexes only (what you can modify)
597
606
  - systemInfo: Read-only system fields and indexes (automatically managed)
598
607
 
599
608
  Example:
600
- >>> # Create or update a collection
601
- >>> col = pb.ensure_collection("deposits", [
602
- ... {"name": "amount", "type": "number", "required": True},
603
- ... {"name": "status", "type": "text"},
604
- ... ])
609
+ >>> # Create with stable ID for use in automations
610
+ >>> col = pb.ensure_collection(
611
+ ... "Customer Orders Q1",
612
+ ... schema=[
613
+ ... {"name": "amount", "type": "number", "required": True},
614
+ ... {"name": "status", "type": "text"},
615
+ ... ],
616
+ ... id="orders", # stable reference
617
+ ... )
605
618
  >>>
606
- >>> # Add a field using copy-modify-send pattern
607
- >>> col = pb.get_collection("deposits")
608
- >>> col["schema"].append({"name": "notes", "type": "text"})
609
- >>> pb.ensure_collection("deposits", col["schema"])
619
+ >>> # Later, collection can be renamed but ID stays "orders"
620
+ >>> # Automations using pb.search("orders", ...) still work!
610
621
  >>>
611
- >>> # Add an index
612
- >>> pb.ensure_collection("deposits", indexes=[
613
- ... "CREATE INDEX idx_status ON deposits (status)"
614
- ... ])
622
+ >>> # Create without custom ID (auto-generated)
623
+ >>> col = pb.ensure_collection("deposits", [...])
615
624
  """
616
- return _ensure_collection(name, schema=schema, indexes=indexes)
625
+ return _ensure_collection(name, schema=schema, id=id, indexes=indexes)
617
626
 
618
627
 
619
628
  def delete_collection(name: str) -> None:
@@ -185,6 +185,7 @@ def ensure_collection(
185
185
  *,
186
186
  collection_type: str = "base",
187
187
  schema: Iterable[CollectionField] | object = _UNSET,
188
+ id: str | None = None,
188
189
  indexes: Iterable[str] | object = _UNSET,
189
190
  ) -> dict[str, Any]:
190
191
  """Ensure a collection exists with the given schema and indexes.
@@ -200,34 +201,37 @@ def ensure_collection(
200
201
  (external_id unique index, updated index) are automatically managed.
201
202
 
202
203
  Args:
203
- name: Collection name. Required.
204
+ name: Collection name (display name, can be renamed later).
204
205
  collection_type: Collection type, defaults to "base".
205
206
  schema: List of field definitions. If provided, replaces all user fields.
206
207
  If omitted, existing fields are preserved.
208
+ id: Optional stable identifier. If provided on creation, this ID will be
209
+ used instead of an auto-generated one. The ID remains stable even if
210
+ the collection is renamed. Must be alphanumeric with underscores only.
211
+ Cannot be changed after creation.
207
212
  indexes: List of index DDL statements. If provided, replaces all user indexes.
208
213
  If omitted, existing indexes are preserved.
209
214
 
210
215
  Returns:
211
216
  The collection data including:
217
+ - id: The collection's stable identifier
218
+ - name: The collection's display name
212
219
  - schema: User-defined fields only
213
220
  - indexes: User-defined indexes only
214
221
  - systemInfo: Object with system-managed fields and indexes (read-only)
215
222
 
216
223
  Example:
217
- # Create or update a collection
224
+ # Create with stable ID for automations
218
225
  coll = ensure_collection(
219
- "customers",
226
+ "Customer Orders Q1",
220
227
  schema=[
221
- {"name": "title", "type": "text", "required": True},
222
- {"name": "email", "type": "text"},
228
+ {"name": "amount", "type": "number", "required": True},
223
229
  ],
224
- indexes=["CREATE INDEX idx_email ON customers (email)"]
230
+ id="orders", # stable reference
225
231
  )
226
232
 
227
- # Add a field (copy-modify-send pattern)
228
- coll = get_collection("customers")
229
- coll["schema"].append({"name": "phone", "type": "text"})
230
- ensure_collection("customers", schema=coll["schema"])
233
+ # Later, rename collection but ID stays "orders"
234
+ # Automations using search("orders", ...) still work!
231
235
  """
232
236
  if not name or not name.strip():
233
237
  raise ValueError("name is required")
@@ -238,6 +242,9 @@ def ensure_collection(
238
242
  if collection_type:
239
243
  payload["type"] = collection_type
240
244
 
245
+ if id is not None and id.strip():
246
+ payload["id"] = id.strip()
247
+
241
248
  if schema is not _UNSET:
242
249
  if schema is None:
243
250
  raise ValueError("schema cannot be None; provide an iterable of fields or omit")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumera
3
- Version: 0.9.4
3
+ Version: 0.9.6
4
4
  Summary: SDK for building on Lumera platform
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: requests
@@ -3,6 +3,7 @@ lumera/__init__.py
3
3
  lumera/_utils.py
4
4
  lumera/automations.py
5
5
  lumera/exceptions.py
6
+ lumera/files.py
6
7
  lumera/google.py
7
8
  lumera/llm.py
8
9
  lumera/locks.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lumera"
3
- version = "0.9.4"
3
+ version = "0.9.6"
4
4
  description = "SDK for building on Lumera platform"
5
5
  requires-python = ">=3.11"
6
6
  dependencies = [
@@ -196,7 +196,7 @@ def test_create_collection_uses_ensure(monkeypatch: pytest.MonkeyPatch) -> None:
196
196
 
197
197
  captured: dict[str, object] = {}
198
198
 
199
- def fake_request(method: str, url: str, **kwargs: object) -> DummyResponse:
199
+ def fake_request(method: str, url: str, **_kwargs: object) -> DummyResponse:
200
200
  captured["method"] = method
201
201
  captured["url"] = url
202
202
  return DummyResponse(status_code=200, json_data={"id": "new"})
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes