planar 0.10.0__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.
Files changed (60) hide show
  1. planar/app.py +18 -6
  2. planar/routers/info.py +79 -36
  3. planar/scaffold_templates/pyproject.toml.j2 +1 -1
  4. planar/testing/fixtures.py +7 -4
  5. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/METADATA +9 -1
  6. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/RECORD +8 -60
  7. planar/ai/test_agent_serialization.py +0 -229
  8. planar/ai/test_agent_tool_step_display.py +0 -78
  9. planar/data/test_dataset.py +0 -358
  10. planar/files/storage/test_azure_blob.py +0 -435
  11. planar/files/storage/test_local_directory.py +0 -162
  12. planar/files/storage/test_s3.py +0 -299
  13. planar/files/test_files.py +0 -282
  14. planar/human/test_human.py +0 -385
  15. planar/logging/test_formatter.py +0 -327
  16. planar/modeling/mixins/test_auditable.py +0 -97
  17. planar/modeling/mixins/test_timestamp.py +0 -134
  18. planar/modeling/mixins/test_uuid_primary_key.py +0 -52
  19. planar/routers/test_agents_router.py +0 -174
  20. planar/routers/test_dataset_router.py +0 -429
  21. planar/routers/test_files_router.py +0 -49
  22. planar/routers/test_object_config_router.py +0 -367
  23. planar/routers/test_routes_security.py +0 -168
  24. planar/routers/test_rule_router.py +0 -470
  25. planar/routers/test_workflow_router.py +0 -564
  26. planar/rules/test_data/account_dormancy_management.json +0 -223
  27. planar/rules/test_data/airline_loyalty_points_calculator.json +0 -262
  28. planar/rules/test_data/applicant_risk_assessment.json +0 -435
  29. planar/rules/test_data/booking_fraud_detection.json +0 -407
  30. planar/rules/test_data/cellular_data_rollover_system.json +0 -258
  31. planar/rules/test_data/clinical_trial_eligibility_screener.json +0 -437
  32. planar/rules/test_data/customer_lifetime_value.json +0 -143
  33. planar/rules/test_data/import_duties_calculator.json +0 -289
  34. planar/rules/test_data/insurance_prior_authorization.json +0 -443
  35. planar/rules/test_data/online_check_in_eligibility_system.json +0 -254
  36. planar/rules/test_data/order_consolidation_system.json +0 -375
  37. planar/rules/test_data/portfolio_risk_monitor.json +0 -471
  38. planar/rules/test_data/supply_chain_risk.json +0 -253
  39. planar/rules/test_data/warehouse_cross_docking.json +0 -237
  40. planar/rules/test_rules.py +0 -1494
  41. planar/security/tests/test_auth_middleware.py +0 -162
  42. planar/security/tests/test_authorization_context.py +0 -78
  43. planar/security/tests/test_cedar_basics.py +0 -41
  44. planar/security/tests/test_cedar_policies.py +0 -158
  45. planar/security/tests/test_jwt_principal_context.py +0 -179
  46. planar/test_app.py +0 -142
  47. planar/test_cli.py +0 -394
  48. planar/test_config.py +0 -515
  49. planar/test_object_config.py +0 -527
  50. planar/test_object_registry.py +0 -14
  51. planar/test_sqlalchemy.py +0 -193
  52. planar/test_utils.py +0 -105
  53. planar/testing/test_memory_storage.py +0 -143
  54. planar/workflows/test_concurrency_detection.py +0 -120
  55. planar/workflows/test_lock_timeout.py +0 -140
  56. planar/workflows/test_serialization.py +0 -1203
  57. planar/workflows/test_suspend_deserialization.py +0 -231
  58. planar/workflows/test_workflow.py +0 -2005
  59. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/WHEEL +0 -0
  60. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/entry_points.txt +0 -0
@@ -1,174 +0,0 @@
1
- """
2
- Tests for agent router endpoints.
3
-
4
- This module tests the agent router endpoints to ensure they work correctly
5
- with the new serialization changes.
6
- """
7
-
8
- import pytest
9
- from sqlmodel.ext.asyncio.session import AsyncSession
10
-
11
- from planar.ai.agent import Agent
12
- from planar.app import PlanarApp
13
- from planar.config import sqlite_config
14
- from planar.testing.planar_test_client import PlanarTestClient
15
-
16
-
17
- @pytest.fixture(name="app")
18
- def app_fixture(tmp_db_path: str):
19
- """Create a test app with agents."""
20
- app = PlanarApp(
21
- config=sqlite_config(tmp_db_path),
22
- title="Test app for agent router",
23
- description="Testing agent endpoints",
24
- )
25
-
26
- # Register a simple agent
27
- simple_agent = Agent(
28
- name="simple_test_agent",
29
- system_prompt="Simple system prompt",
30
- user_prompt="Simple user prompt: {input}",
31
- model="openai:gpt-4o",
32
- max_turns=2,
33
- )
34
- app.register_agent(simple_agent)
35
-
36
- # Register an agent with tools
37
- async def test_tool(param: str) -> str:
38
- """A test tool."""
39
- return f"Processed: {param}"
40
-
41
- agent_with_tools = Agent(
42
- name="agent_with_tools",
43
- system_prompt="System with tools",
44
- user_prompt="User: {input}",
45
- model="anthropic:claude-3-5-sonnet-latest",
46
- max_turns=5,
47
- tools=[test_tool],
48
- )
49
- app.register_agent(agent_with_tools)
50
-
51
- return app
52
-
53
-
54
- async def test_get_agents_endpoint(
55
- client: PlanarTestClient, app: PlanarApp, session: AsyncSession
56
- ):
57
- """Test the GET /agents endpoint returns agents with configs field."""
58
- response = await client.get("/planar/v1/agents/")
59
- assert response.status_code == 200
60
-
61
- agents = response.json()
62
- assert len(agents) == 2
63
-
64
- # Check first agent
65
- simple_agent = next(a for a in agents if a["name"] == "simple_test_agent")
66
- assert simple_agent["name"] == "simple_test_agent"
67
- assert "configs" in simple_agent
68
- assert isinstance(simple_agent["configs"], list)
69
- assert len(simple_agent["configs"]) == 1 # Default config always present
70
-
71
- # Verify the default config is present and correct
72
- default_config = simple_agent["configs"][-1]
73
- assert default_config["version"] == 0
74
- assert default_config["data"]["system_prompt"] == "Simple system prompt"
75
- assert default_config["data"]["user_prompt"] == "Simple user prompt: {input}"
76
- assert default_config["data"]["model"] == "openai:gpt-4o"
77
- assert default_config["data"]["max_turns"] == 2
78
-
79
- # Verify removed fields are not present
80
- assert "system_prompt" not in simple_agent
81
- assert "user_prompt" not in simple_agent
82
- assert "model" not in simple_agent
83
- assert "max_turns" not in simple_agent
84
- assert "overwrites" not in simple_agent
85
-
86
- # Check agent with tools
87
- tools_agent = next(a for a in agents if a["name"] == "agent_with_tools")
88
- assert len(tools_agent["tool_definitions"]) == 1
89
- assert tools_agent["tool_definitions"][0]["name"] == "test_tool"
90
-
91
-
92
- async def test_update_agent_endpoint(
93
- client: PlanarTestClient, app: PlanarApp, session: AsyncSession
94
- ):
95
- """Test the PATCH /agents/{agent_name} endpoint creates configs."""
96
- # Get agents first
97
- response = await client.get("/planar/v1/agents/")
98
- assert response.status_code == 200
99
- agents = response.json()
100
- assert len(agents) == 2
101
-
102
- # Update the agent
103
- update_data = {
104
- "system_prompt": "Updated system prompt",
105
- "user_prompt": "Updated user prompt: {input}",
106
- }
107
- response = await client.patch(
108
- "/planar/v1/agents/simple_test_agent", json=update_data
109
- )
110
- assert response.status_code == 200
111
-
112
- updated_agent = response.json()
113
- assert "configs" in updated_agent
114
- assert len(updated_agent["configs"]) == 2
115
-
116
- # Check the config data
117
- config = updated_agent["configs"][0]
118
- assert config["data"]["system_prompt"] == "Updated system prompt"
119
- assert config["data"]["user_prompt"] == "Updated user prompt: {input}"
120
- assert config["version"] == 1
121
- assert config["object_type"] == "agent"
122
- assert config["object_name"] == "simple_test_agent"
123
-
124
-
125
- async def test_agent_with_multiple_configs(
126
- client: PlanarTestClient, app: PlanarApp, session: AsyncSession
127
- ):
128
- """Test that agents return all configs when multiple exist."""
129
- # Get the agent ID first
130
- response = await client.get("/planar/v1/agents/")
131
- agents = response.json()
132
- simple_agent = next(a for a in agents if a["name"] == "simple_test_agent")
133
-
134
- # Create first config via PATCH endpoint
135
- config1_data = {
136
- "system_prompt": "Config 1 system",
137
- "user_prompt": "Config 1 user: {input}",
138
- "model": "openai:gpt-4o",
139
- "max_turns": 2,
140
- "model_parameters": {"temperature": 0.7},
141
- }
142
- response = await client.patch(
143
- f"/planar/v1/agents/{simple_agent['name']}", json=config1_data
144
- )
145
- assert response.status_code == 200
146
-
147
- # Create second config via PATCH endpoint
148
- config2_data = {
149
- "system_prompt": "Config 2 system",
150
- "user_prompt": "Config 2 user: {input}",
151
- "model": "anthropic:claude-3-opus",
152
- "max_turns": 4,
153
- "model_parameters": {"temperature": 0.9},
154
- }
155
- response = await client.patch(
156
- f"/planar/v1/agents/{simple_agent['name']}", json=config2_data
157
- )
158
- assert response.status_code == 200
159
-
160
- # Get agents
161
- response = await client.get("/planar/v1/agents/")
162
- agents = response.json()
163
- simple_agent = next(a for a in agents if a["name"] == "simple_test_agent")
164
-
165
- # Verify all configs are returned (including default config)
166
- assert len(simple_agent["configs"]) == 3
167
- assert simple_agent["configs"][0]["version"] == 2 # Latest first
168
- assert simple_agent["configs"][1]["version"] == 1
169
- assert simple_agent["configs"][2]["version"] == 0 # Default config
170
-
171
- # Verify config data
172
- assert simple_agent["configs"][0]["data"]["system_prompt"] == "Config 2 system"
173
- assert simple_agent["configs"][1]["data"]["system_prompt"] == "Config 1 system"
174
- assert simple_agent["configs"][2]["data"]["system_prompt"] == "Simple system prompt"
@@ -1,429 +0,0 @@
1
- import math
2
-
3
- import polars as pl
4
- import pyarrow as pa
5
- import pytest
6
-
7
- from planar.data.dataset import PlanarDataset
8
- from planar.testing.planar_test_client import PlanarTestClient
9
-
10
-
11
- @pytest.fixture(name="app")
12
- def app_fixture(app_with_data):
13
- """Use the shared app_with_data fixture as 'app' for this test module."""
14
- return app_with_data
15
-
16
-
17
- async def test_stream_arrow_chunks(
18
- client: PlanarTestClient,
19
- ):
20
- dataset_name = "test_streaming"
21
- dataset_size = 10_000
22
- batch_size = 1000
23
-
24
- dataset = await PlanarDataset.create(dataset_name)
25
-
26
- df = pl.DataFrame({"id": range(dataset_size)}).with_columns(
27
- pl.format("value_{}", pl.col("id")).alias("value")
28
- )
29
-
30
- await dataset.write(df)
31
-
32
- response = await client.get(
33
- f"/planar/v1/datasets/content/{dataset_name}/arrow-stream",
34
- params={"batch_size": batch_size, "limit": dataset_size},
35
- )
36
-
37
- assert response.status_code == 200
38
- assert response.headers["content-type"] == "application/vnd.apache.arrow.stream"
39
- assert "test_streaming.arrow" in response.headers.get("content-disposition", "")
40
- assert response.headers.get("x-batch-size") == str(batch_size)
41
-
42
- content = await response.aread()
43
- buffer = pa.py_buffer(content)
44
- reader = pa.ipc.open_stream(buffer)
45
-
46
- batch_info = []
47
- total_rows_received = 0
48
- all_ids = []
49
-
50
- try:
51
- while True:
52
- arrow_batch = reader.read_next_batch()
53
- batch_info.append(
54
- {
55
- "rows": arrow_batch.num_rows,
56
- "columns": arrow_batch.num_columns,
57
- }
58
- )
59
- total_rows_received += arrow_batch.num_rows
60
-
61
- id_column = arrow_batch.column("id")
62
- batch_ids = id_column.to_pylist()
63
- all_ids.extend(batch_ids)
64
- except StopIteration:
65
- pass
66
-
67
- expected_batches = math.ceil(dataset_size / batch_size)
68
-
69
- assert len(batch_info) == expected_batches
70
- assert total_rows_received == dataset_size
71
-
72
- # Verify data integrity - check that we received all expected IDs
73
- assert len(all_ids) == dataset_size
74
- assert set(all_ids) == set(range(dataset_size))
75
- assert sum(all_ids) == sum(range(dataset_size))
76
-
77
-
78
- async def test_stream_arrow_with_limit(
79
- client: PlanarTestClient,
80
- ):
81
- """Test that the limit parameter properly restricts the number of rows streamed."""
82
- dataset_name = "test_streaming_limit"
83
- dataset_size = 1000
84
- batch_size = 100
85
- row_limit = 250 # Should get 3 batches (100 + 100 + 50)
86
-
87
- dataset = await PlanarDataset.create(dataset_name)
88
-
89
- # Create test data
90
- df = pl.DataFrame({"id": range(dataset_size)}).with_columns(
91
- pl.format("value_{}", pl.col("id")).alias("value")
92
- )
93
-
94
- await dataset.write(df)
95
-
96
- response = await client.get(
97
- f"/planar/v1/datasets/content/{dataset_name}/arrow-stream",
98
- params={"batch_size": batch_size, "limit": row_limit},
99
- )
100
-
101
- assert response.status_code == 200
102
- assert response.headers["x-row-limit"] == str(row_limit)
103
-
104
- content = await response.aread()
105
- buffer = pa.py_buffer(content)
106
- reader = pa.ipc.open_stream(buffer)
107
-
108
- total_rows_received = 0
109
- batch_count = 0
110
-
111
- try:
112
- while True:
113
- arrow_batch = reader.read_next_batch()
114
- total_rows_received += arrow_batch.num_rows
115
- batch_count += 1
116
- except StopIteration:
117
- pass
118
-
119
- # Should receive exactly the limited number of rows
120
- assert total_rows_received == row_limit
121
- # Should receive expected number of batches (3: 100, 100, 50)
122
- expected_batches = math.ceil(row_limit / batch_size)
123
- assert batch_count == expected_batches
124
-
125
-
126
- async def test_stream_arrow_empty_dataset(
127
- client: PlanarTestClient,
128
- ):
129
- """Test streaming behavior with an empty dataset."""
130
- dataset_name = "test_empty_stream"
131
- batch_size = 100
132
-
133
- dataset = await PlanarDataset.create(dataset_name)
134
-
135
- # Create empty dataset
136
- df = pl.DataFrame(
137
- {"id": [], "value": []}, schema={"id": pl.Int64, "value": pl.Utf8}
138
- )
139
- await dataset.write(df)
140
-
141
- response = await client.get(
142
- f"/planar/v1/datasets/content/{dataset_name}/arrow-stream",
143
- params={"batch_size": batch_size},
144
- )
145
-
146
- assert response.status_code == 200
147
-
148
- content = await response.aread()
149
- buffer = pa.py_buffer(content)
150
- reader = pa.ipc.open_stream(buffer)
151
-
152
- # Should be able to read the schema and get one empty batch
153
- total_rows = 0
154
- batch_count = 0
155
-
156
- try:
157
- while True:
158
- arrow_batch = reader.read_next_batch()
159
- total_rows += arrow_batch.num_rows
160
- batch_count += 1
161
- except StopIteration:
162
- pass
163
-
164
- # Should have exactly 1 empty batch (our fallback for empty datasets)
165
- assert batch_count == 1
166
- assert total_rows == 0
167
-
168
-
169
- async def test_stream_arrow_single_batch(
170
- client: PlanarTestClient,
171
- ):
172
- """Test streaming when dataset size is smaller than batch size."""
173
- dataset_name = "test_single_batch"
174
- dataset_size = 50
175
- batch_size = 100
176
-
177
- dataset = await PlanarDataset.create(dataset_name)
178
-
179
- df = pl.DataFrame({"id": range(dataset_size)}).with_columns(
180
- pl.format("value_{}", pl.col("id")).alias("value")
181
- )
182
-
183
- await dataset.write(df)
184
-
185
- response = await client.get(
186
- f"/planar/v1/datasets/content/{dataset_name}/arrow-stream",
187
- params={"batch_size": batch_size},
188
- )
189
-
190
- assert response.status_code == 200
191
-
192
- content = await response.aread()
193
- buffer = pa.py_buffer(content)
194
- reader = pa.ipc.open_stream(buffer)
195
-
196
- total_rows = 0
197
- batch_count = 0
198
-
199
- try:
200
- while True:
201
- arrow_batch = reader.read_next_batch()
202
- total_rows += arrow_batch.num_rows
203
- batch_count += 1
204
- except StopIteration:
205
- pass
206
-
207
- assert batch_count == 1
208
- assert total_rows == dataset_size
209
-
210
-
211
- async def test_get_schemas_endpoint(
212
- client: PlanarTestClient,
213
- ):
214
- """Test the GET /schemas endpoint."""
215
- response = await client.get("/planar/v1/datasets/schemas")
216
-
217
- assert response.status_code == 200
218
- schemas = response.json()
219
- assert isinstance(schemas, list)
220
- assert "main" in schemas # Default schema should exist
221
-
222
-
223
- async def test_list_datasets_metadata_endpoint(
224
- client: PlanarTestClient,
225
- ):
226
- """Test the GET /metadata endpoint (list all datasets)."""
227
- # Create a test dataset first
228
- dataset_name = "test_list_datasets"
229
- dataset = await PlanarDataset.create(dataset_name)
230
-
231
- df = pl.DataFrame({"id": [1, 2, 3], "name": ["a", "b", "c"]})
232
- await dataset.write(df)
233
-
234
- response = await client.get("/planar/v1/datasets/metadata")
235
-
236
- assert response.status_code == 200
237
- datasets = response.json()
238
- assert isinstance(datasets, list)
239
-
240
- # Find our test dataset
241
- test_dataset = next((d for d in datasets if d["name"] == dataset_name), None)
242
- assert test_dataset is not None
243
- assert test_dataset["row_count"] == 3
244
- assert "id" in test_dataset["table_schema"]
245
- assert "name" in test_dataset["table_schema"]
246
-
247
-
248
- async def test_list_datasets_metadata_with_pagination(
249
- client: PlanarTestClient,
250
- ):
251
- """Test the GET /metadata endpoint with pagination parameters."""
252
- response = await client.get(
253
- "/planar/v1/datasets/metadata",
254
- params={"limit": 5, "offset": 0, "schema_name": "main"},
255
- )
256
-
257
- assert response.status_code == 200
258
- datasets = response.json()
259
- assert isinstance(datasets, list)
260
- assert len(datasets) <= 5 # Should respect limit
261
-
262
-
263
- async def test_get_dataset_metadata_endpoint(
264
- client: PlanarTestClient,
265
- ):
266
- """Test the GET /metadata/{dataset_name} endpoint."""
267
- dataset_name = "test_single_metadata"
268
- dataset = await PlanarDataset.create(dataset_name)
269
-
270
- df = pl.DataFrame(
271
- {
272
- "id": [1, 2, 3, 4, 5],
273
- "value": ["apple", "banana", "cherry", "date", "elderberry"],
274
- }
275
- )
276
- await dataset.write(df)
277
-
278
- response = await client.get(f"/planar/v1/datasets/metadata/{dataset_name}")
279
-
280
- assert response.status_code == 200
281
- metadata = response.json()
282
- assert metadata["name"] == dataset_name
283
- assert metadata["row_count"] == 5
284
- assert "id" in metadata["table_schema"]
285
- assert "value" in metadata["table_schema"]
286
-
287
-
288
- async def test_get_dataset_metadata_not_found(
289
- client: PlanarTestClient,
290
- ):
291
- """Test the GET /metadata/{dataset_name} endpoint with non-existent dataset."""
292
- response = await client.get("/planar/v1/datasets/metadata/nonexistent_dataset")
293
-
294
- assert response.status_code == 404
295
- error = response.json()
296
- assert "not found" in error["detail"].lower()
297
-
298
-
299
- async def test_download_dataset_endpoint(
300
- client: PlanarTestClient,
301
- ):
302
- """Test the GET /content/{dataset_name}/download endpoint."""
303
- dataset_name = "test_download"
304
- dataset = await PlanarDataset.create(dataset_name)
305
-
306
- df = pl.DataFrame({"id": [1, 2, 3], "value": ["x", "y", "z"]})
307
- await dataset.write(df)
308
-
309
- response = await client.get(f"/planar/v1/datasets/content/{dataset_name}/download")
310
-
311
- assert response.status_code == 200
312
- assert response.headers["content-type"] == "application/x-parquet"
313
- assert f"{dataset_name}.parquet" in response.headers.get("content-disposition", "")
314
-
315
- # Verify we get valid parquet content
316
- content = await response.aread()
317
- assert len(content) > 0
318
-
319
- # Verify it's valid parquet by reading it back
320
- import pyarrow.parquet as pq
321
-
322
- parquet_buffer = pa.py_buffer(content)
323
- table = pq.read_table(parquet_buffer)
324
- assert table.num_rows == 3
325
- assert table.num_columns == 2
326
-
327
-
328
- async def test_download_dataset_not_found(
329
- client: PlanarTestClient,
330
- ):
331
- """Test the GET /content/{dataset_name}/download endpoint with non-existent dataset."""
332
- response = await client.get(
333
- "/planar/v1/datasets/content/nonexistent_dataset/download"
334
- )
335
-
336
- assert response.status_code == 404
337
- error = response.json()
338
- assert "not found" in error["detail"].lower()
339
-
340
-
341
- async def test_stream_arrow_dataset_not_found(
342
- client: PlanarTestClient,
343
- ):
344
- """Test the GET /content/{dataset_name}/arrow-stream endpoint with non-existent dataset."""
345
- response = await client.get(
346
- "/planar/v1/datasets/content/nonexistent_dataset/arrow-stream"
347
- )
348
-
349
- assert response.status_code == 404
350
- error = response.json()
351
- assert "not found" in error["detail"].lower()
352
-
353
-
354
- async def test_get_dataset_metadata_empty_dataset(
355
- client: PlanarTestClient,
356
- ):
357
- """Test GET /metadata/{dataset_name} with empty dataset."""
358
- dataset_name = "test_empty_metadata"
359
- dataset = await PlanarDataset.create(dataset_name)
360
-
361
- # Create empty dataset
362
- df = pl.DataFrame(
363
- {"id": [], "value": []}, schema={"id": pl.Int64, "value": pl.Utf8}
364
- )
365
- await dataset.write(df)
366
-
367
- response = await client.get(f"/planar/v1/datasets/metadata/{dataset_name}")
368
- assert response.status_code == 200
369
-
370
- metadata = response.json()
371
- assert metadata["name"] == dataset_name
372
- assert metadata["row_count"] == 0
373
- assert "id" in metadata["table_schema"]
374
- assert "value" in metadata["table_schema"]
375
-
376
-
377
- async def test_list_datasets_metadata_empty_dataset(
378
- client: PlanarTestClient,
379
- ):
380
- """Test GET /metadata with empty dataset in the list."""
381
- dataset_name = "test_empty_in_list"
382
- dataset = await PlanarDataset.create(dataset_name)
383
-
384
- # Create empty dataset
385
- df = pl.DataFrame(
386
- {"id": [], "value": []}, schema={"id": pl.Int64, "value": pl.Utf8}
387
- )
388
- await dataset.write(df)
389
-
390
- response = await client.get("/planar/v1/datasets/metadata")
391
- assert response.status_code == 200
392
-
393
- datasets = response.json()
394
- empty_dataset = next((d for d in datasets if d["name"] == dataset_name), None)
395
- assert empty_dataset is not None
396
- assert empty_dataset["row_count"] == 0
397
-
398
-
399
- async def test_download_empty_dataset(
400
- client: PlanarTestClient,
401
- ):
402
- """Test GET /content/{dataset_name}/download with empty dataset."""
403
- dataset_name = "test_empty_download"
404
- dataset = await PlanarDataset.create(dataset_name)
405
-
406
- # Create empty dataset
407
- df = pl.DataFrame(
408
- {"id": [], "value": []}, schema={"id": pl.Int64, "value": pl.Utf8}
409
- )
410
- await dataset.write(df)
411
-
412
- response = await client.get(f"/planar/v1/datasets/content/{dataset_name}/download")
413
- assert response.status_code == 200
414
- assert response.headers["content-type"] == "application/x-parquet"
415
- assert f"{dataset_name}.parquet" in response.headers.get("content-disposition", "")
416
-
417
- # Verify we get valid parquet content (even if empty)
418
- content = await response.aread()
419
- assert len(content) > 0 # Should have parquet metadata even for empty data
420
-
421
- # Verify it's valid parquet by reading it back
422
- import pyarrow.parquet as pq
423
-
424
- parquet_buffer = pa.py_buffer(content)
425
- table = pq.read_table(parquet_buffer)
426
- assert table.num_rows == 0
427
- assert table.num_columns == 2 # id and value columns
428
- assert table.schema.field("id").type == pa.int64()
429
- assert table.schema.field("value").type == pa.string()
@@ -1,49 +0,0 @@
1
- import io
2
- from uuid import UUID
3
-
4
- import pytest
5
- from sqlmodel.ext.asyncio.session import AsyncSession
6
-
7
- from planar import PlanarApp, sqlite_config
8
- from planar.files.models import PlanarFileMetadata
9
- from planar.testing.planar_test_client import PlanarTestClient
10
-
11
-
12
- @pytest.fixture(name="app")
13
- def app_fixture(tmp_db_path: str):
14
- return PlanarApp(
15
- config=sqlite_config(tmp_db_path),
16
- title="Test app for files router",
17
- description="Testing files endpoints",
18
- )
19
-
20
-
21
- async def test_upload_parquet_sets_content_type(
22
- client: PlanarTestClient, session: AsyncSession
23
- ):
24
- """Uploading a .parquet file should persist application/x-parquet in metadata."""
25
-
26
- # Prepare a small in-memory payload and intentionally send an octet-stream
27
- # to simulate browsers that don't know parquet. The route should override
28
- # this using mimetypes.guess_type.
29
- filename = "test_data.parquet"
30
- payload = b"PAR1" # content doesn't matter for MIME guessing by filename
31
-
32
- files = {
33
- "files": (filename, io.BytesIO(payload), "application/octet-stream"),
34
- }
35
-
36
- resp = await client.post("/planar/v1/file/upload", files=files)
37
- assert resp.status_code == 200
38
-
39
- body = resp.json()
40
- assert isinstance(body, list) and len(body) == 1
41
- file_item = body[0]
42
- assert file_item["filename"] == filename
43
-
44
- # Verify the database record has the correct MIME type
45
- file_id = UUID(file_item["id"])
46
- meta = await session.get(PlanarFileMetadata, file_id)
47
-
48
- assert meta is not None
49
- assert meta.content_type == "application/x-parquet"