planar 0.9.3__py3-none-any.whl → 0.11.0__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.
- planar/ai/agent.py +2 -1
- planar/ai/agent_base.py +24 -5
- planar/ai/state.py +17 -0
- planar/app.py +18 -1
- planar/data/connection.py +108 -0
- planar/data/dataset.py +11 -104
- planar/data/utils.py +89 -0
- planar/db/alembic/env.py +25 -1
- planar/files/storage/azure_blob.py +1 -1
- planar/registry_items.py +2 -0
- planar/routers/dataset_router.py +213 -0
- planar/routers/info.py +79 -36
- planar/routers/models.py +1 -0
- planar/routers/workflow.py +2 -0
- planar/scaffold_templates/pyproject.toml.j2 +1 -1
- planar/security/authorization.py +31 -3
- planar/security/default_policies.cedar +25 -0
- planar/testing/fixtures.py +34 -1
- planar/testing/planar_test_client.py +1 -1
- planar/workflows/decorators.py +2 -1
- planar/workflows/wrappers.py +1 -0
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/METADATA +9 -1
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/RECORD +25 -72
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/WHEEL +1 -1
- planar/ai/test_agent_serialization.py +0 -229
- planar/ai/test_agent_tool_step_display.py +0 -78
- planar/data/test_dataset.py +0 -354
- planar/files/storage/test_azure_blob.py +0 -435
- planar/files/storage/test_local_directory.py +0 -162
- planar/files/storage/test_s3.py +0 -299
- planar/files/test_files.py +0 -282
- planar/human/test_human.py +0 -385
- planar/logging/test_formatter.py +0 -327
- planar/modeling/mixins/test_auditable.py +0 -97
- planar/modeling/mixins/test_timestamp.py +0 -134
- planar/modeling/mixins/test_uuid_primary_key.py +0 -52
- planar/routers/test_agents_router.py +0 -174
- planar/routers/test_files_router.py +0 -49
- planar/routers/test_object_config_router.py +0 -367
- planar/routers/test_routes_security.py +0 -168
- planar/routers/test_rule_router.py +0 -470
- planar/routers/test_workflow_router.py +0 -539
- planar/rules/test_data/account_dormancy_management.json +0 -223
- planar/rules/test_data/airline_loyalty_points_calculator.json +0 -262
- planar/rules/test_data/applicant_risk_assessment.json +0 -435
- planar/rules/test_data/booking_fraud_detection.json +0 -407
- planar/rules/test_data/cellular_data_rollover_system.json +0 -258
- planar/rules/test_data/clinical_trial_eligibility_screener.json +0 -437
- planar/rules/test_data/customer_lifetime_value.json +0 -143
- planar/rules/test_data/import_duties_calculator.json +0 -289
- planar/rules/test_data/insurance_prior_authorization.json +0 -443
- planar/rules/test_data/online_check_in_eligibility_system.json +0 -254
- planar/rules/test_data/order_consolidation_system.json +0 -375
- planar/rules/test_data/portfolio_risk_monitor.json +0 -471
- planar/rules/test_data/supply_chain_risk.json +0 -253
- planar/rules/test_data/warehouse_cross_docking.json +0 -237
- planar/rules/test_rules.py +0 -1494
- planar/security/tests/test_auth_middleware.py +0 -162
- planar/security/tests/test_authorization_context.py +0 -78
- planar/security/tests/test_cedar_basics.py +0 -41
- planar/security/tests/test_cedar_policies.py +0 -158
- planar/security/tests/test_jwt_principal_context.py +0 -179
- planar/test_app.py +0 -142
- planar/test_cli.py +0 -394
- planar/test_config.py +0 -515
- planar/test_object_config.py +0 -527
- planar/test_object_registry.py +0 -14
- planar/test_sqlalchemy.py +0 -193
- planar/test_utils.py +0 -105
- planar/testing/test_memory_storage.py +0 -143
- planar/workflows/test_concurrency_detection.py +0 -120
- planar/workflows/test_lock_timeout.py +0 -140
- planar/workflows/test_serialization.py +0 -1203
- planar/workflows/test_suspend_deserialization.py +0 -231
- planar/workflows/test_workflow.py +0 -2005
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/entry_points.txt +0 -0
planar/data/test_dataset.py
DELETED
@@ -1,354 +0,0 @@
|
|
1
|
-
"""Tests for PlanarDataset."""
|
2
|
-
|
3
|
-
import polars as pl
|
4
|
-
import pyarrow as pa
|
5
|
-
import pytest
|
6
|
-
from ibis import literal
|
7
|
-
|
8
|
-
from planar import PlanarApp
|
9
|
-
from planar.data import PlanarDataset
|
10
|
-
from planar.data.config import DataConfig, SQLiteCatalogConfig
|
11
|
-
from planar.data.exceptions import (
|
12
|
-
DataError,
|
13
|
-
DatasetAlreadyExistsError,
|
14
|
-
DatasetNotFoundError,
|
15
|
-
)
|
16
|
-
from planar.files.storage.config import LocalDirectoryConfig
|
17
|
-
from planar.workflows import step
|
18
|
-
|
19
|
-
|
20
|
-
@pytest.fixture
|
21
|
-
def data_config(tmp_path):
|
22
|
-
"""Create a test data configuration."""
|
23
|
-
data_dir = tmp_path / "data"
|
24
|
-
data_dir.mkdir(exist_ok=True)
|
25
|
-
|
26
|
-
catalog_path = data_dir / "test.sqlite"
|
27
|
-
storage_path = data_dir / "ducklake_files"
|
28
|
-
storage_path.mkdir(exist_ok=True)
|
29
|
-
|
30
|
-
return DataConfig(
|
31
|
-
catalog=SQLiteCatalogConfig(type="sqlite", path=str(catalog_path)),
|
32
|
-
storage=LocalDirectoryConfig(backend="localdir", directory=str(storage_path)),
|
33
|
-
)
|
34
|
-
|
35
|
-
|
36
|
-
@pytest.fixture(name="app")
|
37
|
-
def app_fixture(data_config):
|
38
|
-
"""Create a PlanarApp with data configuration."""
|
39
|
-
app = PlanarApp()
|
40
|
-
# Add data config to the app's config
|
41
|
-
app.config.data = data_config
|
42
|
-
return app
|
43
|
-
|
44
|
-
|
45
|
-
@pytest.mark.asyncio
|
46
|
-
async def test_dataset_create(client):
|
47
|
-
"""Test creating a dataset reference."""
|
48
|
-
dataset = await PlanarDataset.create("test_table")
|
49
|
-
assert dataset.name == "test_table"
|
50
|
-
|
51
|
-
# Dataset reference exists but table isn't created until first write
|
52
|
-
assert not await dataset.exists()
|
53
|
-
|
54
|
-
# Write some data to actually create the table
|
55
|
-
df = pl.DataFrame({"id": [1], "name": ["test"]})
|
56
|
-
await dataset.write(df, mode="overwrite")
|
57
|
-
|
58
|
-
# Now it should exist
|
59
|
-
assert await dataset.exists()
|
60
|
-
|
61
|
-
# Cleanup
|
62
|
-
await dataset.delete()
|
63
|
-
|
64
|
-
|
65
|
-
@pytest.mark.asyncio
|
66
|
-
async def test_dataset_create_if_not_exists(client):
|
67
|
-
"""Test creating a dataset with if_not_exists behavior."""
|
68
|
-
# Create dataset and write data to make it exist
|
69
|
-
dataset1 = await PlanarDataset.create("test_table")
|
70
|
-
df = pl.DataFrame({"id": [1], "name": ["test"]})
|
71
|
-
await dataset1.write(df, mode="overwrite")
|
72
|
-
|
73
|
-
# Create again with if_not_exists=True (default) - should not raise
|
74
|
-
dataset2 = await PlanarDataset.create("test_table", if_not_exists=True)
|
75
|
-
assert dataset2.name == dataset1.name
|
76
|
-
|
77
|
-
# Create again with if_not_exists=False - should raise
|
78
|
-
with pytest.raises(DatasetAlreadyExistsError):
|
79
|
-
await PlanarDataset.create("test_table", if_not_exists=False)
|
80
|
-
|
81
|
-
# Cleanup
|
82
|
-
await dataset1.delete()
|
83
|
-
|
84
|
-
|
85
|
-
@pytest.mark.asyncio
|
86
|
-
async def test_dataset_write_and_read_polars(client):
|
87
|
-
"""Test writing and reading data with Polars."""
|
88
|
-
dataset = await PlanarDataset.create("test_polars")
|
89
|
-
|
90
|
-
# Create test data
|
91
|
-
df = pl.DataFrame(
|
92
|
-
{
|
93
|
-
"id": [1, 2, 3],
|
94
|
-
"name": ["Alice", "Bob", "Charlie"],
|
95
|
-
"amount": [100.5, 200.0, 150.75],
|
96
|
-
}
|
97
|
-
)
|
98
|
-
|
99
|
-
# Write data
|
100
|
-
await dataset.write(df, mode="overwrite")
|
101
|
-
|
102
|
-
# Read data back
|
103
|
-
result = await dataset.to_polars()
|
104
|
-
|
105
|
-
# Verify
|
106
|
-
assert result.shape == df.shape
|
107
|
-
assert set(result.columns) == set(df.columns)
|
108
|
-
assert result["id"].to_list() == [1, 2, 3]
|
109
|
-
assert result["name"].to_list() == ["Alice", "Bob", "Charlie"]
|
110
|
-
|
111
|
-
# Cleanup
|
112
|
-
await dataset.delete()
|
113
|
-
|
114
|
-
|
115
|
-
@pytest.mark.asyncio
|
116
|
-
async def test_dataset_write_and_read_pyarrow(client):
|
117
|
-
"""Test writing and reading data with PyArrow."""
|
118
|
-
dataset = await PlanarDataset.create("test_pyarrow")
|
119
|
-
|
120
|
-
# Create test data
|
121
|
-
table = pa.table(
|
122
|
-
{
|
123
|
-
"id": [1, 2, 3],
|
124
|
-
"name": ["Alice", "Bob", "Charlie"],
|
125
|
-
"amount": [100.5, 200.0, 150.75],
|
126
|
-
}
|
127
|
-
)
|
128
|
-
|
129
|
-
# Write data
|
130
|
-
await dataset.write(table, mode="overwrite")
|
131
|
-
|
132
|
-
# Read data back
|
133
|
-
result = await dataset.to_pyarrow()
|
134
|
-
|
135
|
-
# Verify
|
136
|
-
assert result.num_rows == table.num_rows
|
137
|
-
assert result.column_names == table.column_names
|
138
|
-
|
139
|
-
# Cleanup
|
140
|
-
await dataset.delete()
|
141
|
-
|
142
|
-
|
143
|
-
@pytest.mark.asyncio
|
144
|
-
async def test_dataset_append_mode(client):
|
145
|
-
"""Test appending data to a dataset."""
|
146
|
-
dataset = await PlanarDataset.create("test_append")
|
147
|
-
|
148
|
-
# Write initial data
|
149
|
-
df1 = pl.DataFrame({"id": [1, 2], "value": ["a", "b"]})
|
150
|
-
await dataset.write(df1, mode="overwrite")
|
151
|
-
|
152
|
-
# Append more data
|
153
|
-
df2 = pl.DataFrame({"id": [3, 4], "value": ["c", "d"]})
|
154
|
-
await dataset.write(df2, mode="append")
|
155
|
-
|
156
|
-
result = await dataset.to_polars()
|
157
|
-
|
158
|
-
# Verify
|
159
|
-
assert len(result) == 4
|
160
|
-
assert set(result["id"].to_list()) == {1, 2, 3, 4}
|
161
|
-
assert set(result["value"].to_list()) == {"a", "b", "c", "d"}
|
162
|
-
|
163
|
-
# Cleanup
|
164
|
-
await dataset.delete()
|
165
|
-
|
166
|
-
|
167
|
-
@pytest.mark.asyncio
|
168
|
-
async def test_dataset_overwrite_replaces_existing(client):
|
169
|
-
"""Overwrite should replace existing rows completely."""
|
170
|
-
dataset = await PlanarDataset.create("test_overwrite")
|
171
|
-
|
172
|
-
df1 = pl.DataFrame({"id": [1, 2], "value": ["a", "b"]})
|
173
|
-
await dataset.write(df1, mode="overwrite")
|
174
|
-
result1 = await dataset.to_polars()
|
175
|
-
assert result1.shape == (2, 2)
|
176
|
-
|
177
|
-
df2 = pl.DataFrame({"id": [3], "value": ["c"]})
|
178
|
-
await dataset.write(df2, mode="overwrite")
|
179
|
-
result2 = await dataset.to_polars()
|
180
|
-
assert result2.shape == (1, 2)
|
181
|
-
assert result2["id"].to_list() == [3]
|
182
|
-
assert result2["value"].to_list() == ["c"]
|
183
|
-
|
184
|
-
await dataset.delete()
|
185
|
-
|
186
|
-
|
187
|
-
@pytest.mark.asyncio
|
188
|
-
async def test_dataset_read_with_filter(client):
|
189
|
-
"""Test reading data with Ibis filtering."""
|
190
|
-
dataset = await PlanarDataset.create("test_filter")
|
191
|
-
|
192
|
-
# Write test data
|
193
|
-
df = pl.DataFrame({"id": range(1, 11), "value": range(10, 101, 10)})
|
194
|
-
await dataset.write(df, mode="overwrite")
|
195
|
-
|
196
|
-
table = await dataset.read()
|
197
|
-
filtered_table = table.filter(table.value > literal(50))
|
198
|
-
filtered_df = filtered_table.to_polars()
|
199
|
-
|
200
|
-
assert len(filtered_df) == 5
|
201
|
-
assert all(v > 50 for v in filtered_df["value"].to_list())
|
202
|
-
|
203
|
-
# Cleanup
|
204
|
-
await dataset.delete()
|
205
|
-
|
206
|
-
|
207
|
-
@pytest.mark.asyncio
|
208
|
-
async def test_dataset_read_with_columns_and_limit(client):
|
209
|
-
"""Test reading specific columns with limit."""
|
210
|
-
dataset = await PlanarDataset.create("test_select")
|
211
|
-
|
212
|
-
# Write test data
|
213
|
-
df = pl.DataFrame(
|
214
|
-
{
|
215
|
-
"id": range(1, 11),
|
216
|
-
"name": [f"user_{i}" for i in range(1, 11)],
|
217
|
-
"value": range(10, 101, 10),
|
218
|
-
}
|
219
|
-
)
|
220
|
-
await dataset.write(df, mode="overwrite")
|
221
|
-
|
222
|
-
# Read specific columns with limit
|
223
|
-
table = await dataset.read(columns=["id", "name"], limit=5)
|
224
|
-
result_df = table.to_polars()
|
225
|
-
|
226
|
-
# Verify
|
227
|
-
assert len(result_df) == 5
|
228
|
-
assert set(result_df.columns) == {"id", "name"}
|
229
|
-
assert "value" not in result_df.columns
|
230
|
-
|
231
|
-
# Cleanup
|
232
|
-
await dataset.delete()
|
233
|
-
|
234
|
-
|
235
|
-
@pytest.mark.asyncio
|
236
|
-
async def test_dataset_not_found(client):
|
237
|
-
"""Test reading from non-existent dataset."""
|
238
|
-
dataset = PlanarDataset(name="nonexistent")
|
239
|
-
|
240
|
-
# Check exists returns False
|
241
|
-
assert not await dataset.exists()
|
242
|
-
|
243
|
-
# Try to read - should raise
|
244
|
-
with pytest.raises(DatasetNotFoundError):
|
245
|
-
await dataset.read()
|
246
|
-
|
247
|
-
|
248
|
-
@pytest.mark.asyncio
|
249
|
-
async def test_dataset_delete(client):
|
250
|
-
"""Test deleting a dataset."""
|
251
|
-
dataset = await PlanarDataset.create("test_delete")
|
252
|
-
|
253
|
-
# Write some data
|
254
|
-
df = pl.DataFrame({"id": [1, 2, 3]})
|
255
|
-
await dataset.write(df)
|
256
|
-
|
257
|
-
# Verify it exists
|
258
|
-
assert await dataset.exists()
|
259
|
-
|
260
|
-
# Delete it
|
261
|
-
await dataset.delete()
|
262
|
-
|
263
|
-
# Verify it's gone
|
264
|
-
assert not await dataset.exists()
|
265
|
-
|
266
|
-
|
267
|
-
@pytest.mark.asyncio
|
268
|
-
async def test_dataset_write_list_of_dicts(client):
|
269
|
-
"""Write list-of-dicts input and read back with Polars."""
|
270
|
-
dataset = await PlanarDataset.create("test_list_of_dicts")
|
271
|
-
|
272
|
-
rows = [{"id": 1, "name": "a"}, {"id": 2, "name": "b"}]
|
273
|
-
await dataset.write(rows, mode="overwrite")
|
274
|
-
|
275
|
-
result = await dataset.to_polars()
|
276
|
-
assert set(result.columns) == {"id", "name"}
|
277
|
-
assert sorted(result["id"].to_list()) == [1, 2]
|
278
|
-
|
279
|
-
await dataset.delete()
|
280
|
-
|
281
|
-
|
282
|
-
@pytest.mark.asyncio
|
283
|
-
async def test_dataset_write_dict_of_lists(client):
|
284
|
-
"""Write dict-of-lists input and read back with Polars."""
|
285
|
-
dataset = await PlanarDataset.create("test_dict_of_lists")
|
286
|
-
|
287
|
-
data = {"id": [1, 2], "name": ["a", "b"]}
|
288
|
-
await dataset.write(data, mode="overwrite")
|
289
|
-
|
290
|
-
result = await dataset.to_polars()
|
291
|
-
assert result.shape == (2, 2)
|
292
|
-
assert set(result["name"].to_list()) == {"a", "b"}
|
293
|
-
|
294
|
-
await dataset.delete()
|
295
|
-
|
296
|
-
|
297
|
-
@pytest.mark.asyncio
|
298
|
-
async def test_dataset_workflow_serialization(client):
|
299
|
-
"""Test that PlanarDataset can be used as workflow input/output."""
|
300
|
-
|
301
|
-
@step()
|
302
|
-
async def create_data() -> PlanarDataset:
|
303
|
-
"""Create a dataset with sample data."""
|
304
|
-
dataset = await PlanarDataset.create("workflow_data")
|
305
|
-
|
306
|
-
df = pl.DataFrame(
|
307
|
-
{"product": ["A", "B", "C", "D"], "sales": [100, 200, 150, 300]}
|
308
|
-
)
|
309
|
-
await dataset.write(df, mode="overwrite")
|
310
|
-
|
311
|
-
return dataset
|
312
|
-
|
313
|
-
@step()
|
314
|
-
async def analyze_data(dataset: PlanarDataset) -> float:
|
315
|
-
"""Analyze the dataset and return total sales."""
|
316
|
-
df = await dataset.to_polars()
|
317
|
-
return float(df["sales"].sum())
|
318
|
-
|
319
|
-
# Test basic workflow functionality without API
|
320
|
-
dataset = await create_data()
|
321
|
-
total = await analyze_data(dataset)
|
322
|
-
|
323
|
-
# Verify results
|
324
|
-
assert total == 750.0 # Sum of [100, 200, 150, 300]
|
325
|
-
|
326
|
-
# Cleanup
|
327
|
-
await dataset.delete()
|
328
|
-
|
329
|
-
|
330
|
-
@pytest.mark.asyncio
|
331
|
-
async def test_no_data_config_error(client):
|
332
|
-
"""Test error when data config is not set."""
|
333
|
-
# Remove data config
|
334
|
-
client.app.config.data = None
|
335
|
-
|
336
|
-
dataset = PlanarDataset(name="test")
|
337
|
-
|
338
|
-
with pytest.raises(DataError, match="Data configuration not found"):
|
339
|
-
await dataset._get_connection()
|
340
|
-
|
341
|
-
|
342
|
-
@pytest.mark.asyncio
|
343
|
-
async def test_write_with_invalid_input_raises(client):
|
344
|
-
"""Unknown input types to write() should raise a DataError."""
|
345
|
-
|
346
|
-
class Foo:
|
347
|
-
pass
|
348
|
-
|
349
|
-
dataset = await PlanarDataset.create("test_invalid_input")
|
350
|
-
|
351
|
-
with pytest.raises(DataError):
|
352
|
-
await dataset.write(Foo(), mode="overwrite") # type: ignore
|
353
|
-
|
354
|
-
await dataset.delete()
|