polydata-client 0.1.0__tar.gz → 0.1.2__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.
- {polydata_client-0.1.0 → polydata_client-0.1.2}/CHANGELOG.md +15 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/PKG-INFO +1 -1
- polydata_client-0.1.2/polydata/__init__.py +8 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/polydata/client.py +80 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/polydata/common.py +135 -3
- {polydata_client-0.1.0 → polydata_client-0.1.2}/polydata_client.egg-info/PKG-INFO +1 -1
- {polydata_client-0.1.0 → polydata_client-0.1.2}/pyproject.toml +1 -1
- polydata_client-0.1.0/polydata/__init__.py +0 -4
- {polydata_client-0.1.0 → polydata_client-0.1.2}/LICENSE +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/MANIFEST.in +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/NOTICE +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/README.md +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/polydata_client.egg-info/SOURCES.txt +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/polydata_client.egg-info/dependency_links.txt +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/polydata_client.egg-info/requires.txt +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/polydata_client.egg-info/top_level.txt +0 -0
- {polydata_client-0.1.0 → polydata_client-0.1.2}/setup.cfg +0 -0
|
@@ -5,6 +5,21 @@ All notable changes to the Polydata Python client will be documented in this fil
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.2] - 2026-07-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `create_or_update_view()` adds `allow_delete_then_create` option, which helps creators who change their column names.
|
|
13
|
+
- `create_or_update_view()` also adds `dependent_views` which are dropped and rebuilt around the recreate.
|
|
14
|
+
|
|
15
|
+
## [0.1.1] - 2026-01-27
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Added utilities to manage and update the cache
|
|
20
|
+
- `client.get_cache_stats()` Gets the Redis cache statistics.
|
|
21
|
+
- `client.clear_hub_cache()` Clears all cached data app pages for a Hub.
|
|
22
|
+
|
|
8
23
|
## [0.1.0] - 2026-01-16
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -266,6 +266,30 @@ class PolydataClient:
|
|
|
266
266
|
)
|
|
267
267
|
return True
|
|
268
268
|
|
|
269
|
+
def delete_data_view(self, hub_schema: str, view_name: str) -> bool:
|
|
270
|
+
"""
|
|
271
|
+
Delete an existing data view using the API endpoint.
|
|
272
|
+
|
|
273
|
+
Drops both the physical Postgres view and its hub_views metadata
|
|
274
|
+
record.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
hub_schema: The hub schema name
|
|
278
|
+
view_name: Name of the view to delete
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
bool: True if successful, False otherwise
|
|
282
|
+
"""
|
|
283
|
+
response = self.session.delete(
|
|
284
|
+
f"{self.base_url}/data/views/{hub_schema}/{view_name}",
|
|
285
|
+
)
|
|
286
|
+
self._handle_response(
|
|
287
|
+
response,
|
|
288
|
+
success_message=f"✓ Deleted data view: {view_name}",
|
|
289
|
+
error_context=f"Delete view {view_name}",
|
|
290
|
+
)
|
|
291
|
+
return True
|
|
292
|
+
|
|
269
293
|
def get_data_view(
|
|
270
294
|
self, hub_schema: str, view_name: str, include_data: bool = True
|
|
271
295
|
) -> pd.DataFrame:
|
|
@@ -664,3 +688,59 @@ class PolydataClient:
|
|
|
664
688
|
df = pd.DataFrame(endpoints_list)
|
|
665
689
|
print(f"✓ Retrieved {len(df)} endpoints for hub: {hub_schema}")
|
|
666
690
|
return df
|
|
691
|
+
|
|
692
|
+
def get_cache_stats(self) -> dict:
|
|
693
|
+
"""
|
|
694
|
+
Get Redis cache statistics.
|
|
695
|
+
|
|
696
|
+
Returns:
|
|
697
|
+
Dict: Cache statistics including total keys and memory usage
|
|
698
|
+
|
|
699
|
+
Raises:
|
|
700
|
+
Exception: If retrieval fails
|
|
701
|
+
"""
|
|
702
|
+
response = self.session.get(f"{self.base_url}/cache/stats")
|
|
703
|
+
response_data = self._handle_response(
|
|
704
|
+
response, error_context="Get cache stats"
|
|
705
|
+
)
|
|
706
|
+
stats = response_data.get("data", {})
|
|
707
|
+
total_keys = stats.get("total_keys", 0)
|
|
708
|
+
print(f"✓ Cache stats retrieved: {total_keys} total keys")
|
|
709
|
+
return response_data
|
|
710
|
+
|
|
711
|
+
def clear_hub_cache(
|
|
712
|
+
self, hub_schema_name: str, entity_type: str = "data_app_page"
|
|
713
|
+
) -> bool:
|
|
714
|
+
"""
|
|
715
|
+
Clear all cached pages for a hub.
|
|
716
|
+
|
|
717
|
+
This is useful after uploading new data to ensure users see fresh data
|
|
718
|
+
without having to rebuild the entire app.
|
|
719
|
+
|
|
720
|
+
Args:
|
|
721
|
+
hub_schema_name: The hub schema name (e.g., "football_player_ratings")
|
|
722
|
+
|
|
723
|
+
Returns:
|
|
724
|
+
bool: True if successful, False otherwise
|
|
725
|
+
|
|
726
|
+
Raises:
|
|
727
|
+
Exception: If cache clear fails
|
|
728
|
+
"""
|
|
729
|
+
response = self.session.delete(
|
|
730
|
+
f"{self.base_url}/cache/{hub_schema_name}/{entity_type}"
|
|
731
|
+
)
|
|
732
|
+
response_data = self._handle_response(
|
|
733
|
+
response,
|
|
734
|
+
success_message=f"✓ Cleared cache for hub: {hub_schema_name}",
|
|
735
|
+
error_context=f"Clear cache for hub {hub_schema_name}",
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
data = response_data.get("data", {})
|
|
739
|
+
deleted = data.get("deleted_count", 0)
|
|
740
|
+
total = data.get("total_pages_in_hub", 0)
|
|
741
|
+
cached = data.get("cached_pages", 0)
|
|
742
|
+
print(
|
|
743
|
+
f" - Cleared {deleted} cached pages out of {cached} that were cached ({total} total pages)"
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
return True
|
|
@@ -30,6 +30,8 @@ def create_or_update_view(
|
|
|
30
30
|
view_name: str,
|
|
31
31
|
view_query: str,
|
|
32
32
|
hub_views_df: pd.DataFrame,
|
|
33
|
+
allow_delete_then_create: bool = False,
|
|
34
|
+
dependent_views: dict | None = None,
|
|
33
35
|
):
|
|
34
36
|
"""
|
|
35
37
|
Create or update a data view based on whether it exists.
|
|
@@ -43,6 +45,20 @@ def create_or_update_view(
|
|
|
43
45
|
view_name: View name (without schema prefix)
|
|
44
46
|
view_query: SQL query for the view
|
|
45
47
|
hub_views_df: DataFrame from client.list_data_views(hub_schema=hub_schema)
|
|
48
|
+
allow_delete_then_create: If True and an in-place update fails, fall back
|
|
49
|
+
to deleting and recreating the view. The update uses CREATE OR
|
|
50
|
+
REPLACE VIEW, which cannot rename or reorder existing columns; enable
|
|
51
|
+
this when a query change alters the view's column set. Off by default
|
|
52
|
+
because delete-then-create is more destructive. When False, an update
|
|
53
|
+
failure propagates unchanged.
|
|
54
|
+
dependent_views: Mapping of {view_name: view_query} for views that
|
|
55
|
+
depend on this view and would block its DROP. Only consulted on the
|
|
56
|
+
delete-then-create fallback: dependents are deleted first (in the
|
|
57
|
+
given order) so the main view can be dropped without CASCADE, then
|
|
58
|
+
recreated from the provided queries (in reverse order) once the
|
|
59
|
+
main view is rebuilt. Pass each dependent's current query if it is
|
|
60
|
+
unchanged — but note it must still be valid against the NEW main
|
|
61
|
+
view definition.
|
|
46
62
|
|
|
47
63
|
Example:
|
|
48
64
|
```python
|
|
@@ -63,18 +79,51 @@ def create_or_update_view(
|
|
|
63
79
|
"""
|
|
64
80
|
full_view_name = f"{hub_schema}.{view_name}"
|
|
65
81
|
|
|
66
|
-
if hub_view_exists(full_view_name, hub_views_df):
|
|
82
|
+
if not hub_view_exists(full_view_name, hub_views_df):
|
|
83
|
+
client.create_data_view(
|
|
84
|
+
hub_schema=hub_schema,
|
|
85
|
+
view_name=view_name,
|
|
86
|
+
view_query=view_query,
|
|
87
|
+
)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
try:
|
|
67
91
|
client.update_data_view(
|
|
68
92
|
hub_schema=hub_schema,
|
|
69
93
|
view_name=view_name,
|
|
70
94
|
view_query=view_query,
|
|
71
95
|
)
|
|
72
|
-
|
|
96
|
+
except Exception:
|
|
97
|
+
if not allow_delete_then_create:
|
|
98
|
+
raise
|
|
99
|
+
# In-place update failed (e.g. the query renames/reorders columns,
|
|
100
|
+
# which CREATE OR REPLACE VIEW forbids). Rebuild from scratch:
|
|
101
|
+
# dependents are knocked out first so the plain (non-CASCADE) DROP of
|
|
102
|
+
# the main view succeeds, then restored once it is rebuilt.
|
|
103
|
+
dependent_views = dependent_views or {}
|
|
104
|
+
for dependent_name in dependent_views:
|
|
105
|
+
if hub_view_exists(f"{hub_schema}.{dependent_name}", hub_views_df):
|
|
106
|
+
client.delete_data_view(
|
|
107
|
+
hub_schema=hub_schema,
|
|
108
|
+
view_name=dependent_name,
|
|
109
|
+
)
|
|
110
|
+
client.delete_data_view(
|
|
111
|
+
hub_schema=hub_schema,
|
|
112
|
+
view_name=view_name,
|
|
113
|
+
)
|
|
73
114
|
client.create_data_view(
|
|
74
115
|
hub_schema=hub_schema,
|
|
75
116
|
view_name=view_name,
|
|
76
117
|
view_query=view_query,
|
|
77
118
|
)
|
|
119
|
+
for dependent_name, dependent_query in reversed(
|
|
120
|
+
list(dependent_views.items())
|
|
121
|
+
):
|
|
122
|
+
client.create_data_view(
|
|
123
|
+
hub_schema=hub_schema,
|
|
124
|
+
view_name=dependent_name,
|
|
125
|
+
view_query=dependent_query,
|
|
126
|
+
)
|
|
78
127
|
|
|
79
128
|
|
|
80
129
|
def ticker_exists(ticker_name: str, hub_tickers_df: pd.DataFrame) -> bool:
|
|
@@ -161,6 +210,90 @@ def create_or_update_ticker(
|
|
|
161
210
|
)
|
|
162
211
|
|
|
163
212
|
|
|
213
|
+
def endpoint_exists(endpoint_name: str, hub_endpoints_df: pd.DataFrame) -> bool:
|
|
214
|
+
"""
|
|
215
|
+
Check if an endpoint exists in the hub.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
endpoint_name: Endpoint name to check
|
|
219
|
+
hub_endpoints_df: DataFrame from client.list_endpoints()
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
True if the endpoint exists, False otherwise
|
|
223
|
+
"""
|
|
224
|
+
if hub_endpoints_df.empty:
|
|
225
|
+
return False
|
|
226
|
+
return endpoint_name in hub_endpoints_df["hub_endpoint_name"].values
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def create_or_update_endpoint(
|
|
230
|
+
client,
|
|
231
|
+
hub_schema: str,
|
|
232
|
+
endpoint_name: str,
|
|
233
|
+
view_name: str,
|
|
234
|
+
params_config: dict,
|
|
235
|
+
hub_views_df: pd.DataFrame,
|
|
236
|
+
hub_endpoints_df: pd.DataFrame,
|
|
237
|
+
subscription_plan_type_name: str = "enterprise",
|
|
238
|
+
):
|
|
239
|
+
"""
|
|
240
|
+
Create or update an endpoint based on whether it exists.
|
|
241
|
+
|
|
242
|
+
Mirrors create_or_update_view: checks hub_endpoints_df and calls either
|
|
243
|
+
client.update_endpoint() or client.create_endpoint() (resolving the backing
|
|
244
|
+
view's id from hub_views_df) accordingly.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
client: PolydataClient instance
|
|
248
|
+
hub_schema: Schema name
|
|
249
|
+
endpoint_name: Human-readable endpoint name
|
|
250
|
+
view_name: Backing view name (without schema prefix)
|
|
251
|
+
params_config: Endpoint params config (allowed_params/allowed_columns)
|
|
252
|
+
hub_views_df: DataFrame from client.list_data_views()
|
|
253
|
+
hub_endpoints_df: DataFrame from client.list_endpoints()
|
|
254
|
+
subscription_plan_type_name: Plan required when creating the endpoint
|
|
255
|
+
(default: "enterprise"); ignored on update
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
```python
|
|
259
|
+
from polydata import PolydataClient
|
|
260
|
+
from polydata.common import create_or_update_endpoint
|
|
261
|
+
|
|
262
|
+
client = PolydataClient(api_key="...", base_url="...")
|
|
263
|
+
hub_views_df = client.list_data_views(hub_schema="my_schema")
|
|
264
|
+
hub_endpoints_df = client.list_endpoints(hub_schema="my_schema")
|
|
265
|
+
|
|
266
|
+
create_or_update_endpoint(
|
|
267
|
+
client=client,
|
|
268
|
+
hub_schema="my_schema",
|
|
269
|
+
endpoint_name="Available Leagues",
|
|
270
|
+
view_name="available_leagues",
|
|
271
|
+
params_config={...},
|
|
272
|
+
hub_views_df=hub_views_df,
|
|
273
|
+
hub_endpoints_df=hub_endpoints_df,
|
|
274
|
+
)
|
|
275
|
+
```
|
|
276
|
+
"""
|
|
277
|
+
if endpoint_exists(endpoint_name, hub_endpoints_df):
|
|
278
|
+
client.update_endpoint(
|
|
279
|
+
hub_schema=hub_schema,
|
|
280
|
+
hub_endpoint_name=endpoint_name,
|
|
281
|
+
params_config=params_config,
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
full_view_name = f"{hub_schema}.{view_name}"
|
|
285
|
+
matching = hub_views_df[hub_views_df["view_name"] == full_view_name]
|
|
286
|
+
if matching.empty:
|
|
287
|
+
raise ValueError(f"Could not find view {full_view_name}")
|
|
288
|
+
client.create_endpoint(
|
|
289
|
+
hub_schema=hub_schema,
|
|
290
|
+
hub_view_id=int(matching.iloc[0]["hub_view_id"]),
|
|
291
|
+
hub_endpoint_name=endpoint_name,
|
|
292
|
+
subscription_plan_type_name=subscription_plan_type_name,
|
|
293
|
+
params_config=params_config,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
164
297
|
def upload_large_csv_in_batches(
|
|
165
298
|
client,
|
|
166
299
|
file_path: str,
|
|
@@ -299,7 +432,6 @@ def upload_large_csv_in_batches(
|
|
|
299
432
|
time.sleep(delay_between_chunks)
|
|
300
433
|
|
|
301
434
|
finally:
|
|
302
|
-
# Clean up temporary directory
|
|
303
435
|
try:
|
|
304
436
|
os.rmdir(temp_dir)
|
|
305
437
|
print("\n 🧹 Cleaned up temporary directory")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{polydata_client-0.1.0 → polydata_client-0.1.2}/polydata_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|