dsi-workflow 1.2.4__tar.gz → 1.2.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.
Files changed (98) hide show
  1. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/PKG-INFO +1 -1
  2. dsi_workflow-1.2.6/dsi/_version.py +1 -0
  3. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/duckdb.py +1 -1
  4. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/ndp.py +1 -1
  5. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/osti.py +1 -1
  6. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/rcsbpdb.py +113 -29
  7. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_rcsbpdb.py +71 -12
  8. dsi_workflow-1.2.6/dsi/backends/tests/test_zenodo.py +1329 -0
  9. dsi_workflow-1.2.6/dsi/backends/zenodo.py +1683 -0
  10. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/core.py +15 -3
  11. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/dsi.py +152 -14
  12. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/sync.py +3 -3
  13. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/tests/test_core.py +1 -1
  14. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/tests/test_dsi.py +60 -1
  15. dsi_workflow-1.2.6/dsi/utils/version_control/__init__.py +4 -0
  16. dsi_workflow-1.2.6/dsi/utils/version_control/__main__.py +5 -0
  17. dsi_workflow-1.2.6/dsi/utils/version_control/cli.py +109 -0
  18. dsi_workflow-1.2.6/dsi/utils/version_control/dsi_vcs.py +1193 -0
  19. dsi_workflow-1.2.6/dsi/utils/version_control/plots/plot-add-commit.py +297 -0
  20. dsi_workflow-1.2.6/dsi/utils/version_control/plots/plot-commit-diff.py +257 -0
  21. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/__init__.py +32 -0
  22. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/chunking.py +538 -0
  23. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/content_chunking/csv_chunking.py +254 -0
  24. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/content_chunking/db_chunking.py +176 -0
  25. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/content_chunking/json_chunking.py +585 -0
  26. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/content_chunking/npy_chunking.py +298 -0
  27. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/content_chunking/xlsx_chunking.py +288 -0
  28. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/content_chunking/xml_chunking.py +372 -0
  29. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/exceptions.py +40 -0
  30. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/file_store.py +151 -0
  31. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/frame_codec.py +147 -0
  32. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/log_record.py +264 -0
  33. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/merkle.py +171 -0
  34. dsi_workflow-1.2.6/dsi/utils/version_control/repolog/repository_log.py +400 -0
  35. dsi_workflow-1.2.6/dsi/utils/version_control/tests/task1_dsivcs_tests.py +223 -0
  36. dsi_workflow-1.2.6/dsi/utils/version_control/tests/task1_git_tests.py +176 -0
  37. dsi_workflow-1.2.6/dsi/utils/version_control/tests/task2_dsivcs_tests.py +484 -0
  38. dsi_workflow-1.2.6/dsi/utils/version_control/tests/task2_git_tests.py +501 -0
  39. dsi_workflow-1.2.6/dsi/utils/version_control/tests/task3_dsivcs_tests.py +187 -0
  40. dsi_workflow-1.2.6/dsi/utils/version_control/tests/task3_git_tests.py +183 -0
  41. dsi_workflow-1.2.6/dsi/utils/version_control/tests/test_vcs.py +144 -0
  42. dsi_workflow-1.2.6/dsi/utils/version_control/vcs_db.py +93 -0
  43. dsi_workflow-1.2.6/dsi/utils/version_control/vcs_metadata_helper.py +551 -0
  44. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi_workflow.egg-info/PKG-INFO +1 -1
  45. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi_workflow.egg-info/SOURCES.txt +31 -0
  46. dsi_workflow-1.2.6/dsi_workflow.egg-info/entry_points.txt +3 -0
  47. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/pyproject.toml +2 -1
  48. dsi_workflow-1.2.4/dsi/_version.py +0 -1
  49. dsi_workflow-1.2.4/dsi_workflow.egg-info/entry_points.txt +0 -2
  50. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/README.rst +0 -0
  51. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/__init__.py +0 -0
  52. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/backend.py +0 -0
  53. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/filesystem.py +0 -0
  54. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/gufi.py +0 -0
  55. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/hpss.py +0 -0
  56. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/oceans11.py +0 -0
  57. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/sqlalchemy.py +0 -0
  58. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/sqlite.py +0 -0
  59. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_duckdb.py +0 -0
  60. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_gufi.py +0 -0
  61. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_hpss.py +0 -0
  62. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_ndp.py +0 -0
  63. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_oceans11.py +0 -0
  64. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_osti.py +0 -0
  65. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_sqlalchemy.py +0 -0
  66. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/tests/test_sqlite.py +0 -0
  67. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/backends/webserver.py +0 -0
  68. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/cli.py +0 -0
  69. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/dsifederated.py +0 -0
  70. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/collection_reader.py +0 -0
  71. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/dashboard.py +0 -0
  72. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/env.py +0 -0
  73. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/file_reader.py +0 -0
  74. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/file_writer.py +0 -0
  75. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/metadata.py +0 -0
  76. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/ml_emulator.py +0 -0
  77. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/plugin.py +0 -0
  78. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/plugin_models.py +0 -0
  79. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/tests/test_collection_reader.py +0 -0
  80. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/tests/test_env.py +0 -0
  81. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/tests/test_file_reader.py +0 -0
  82. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/tests/test_file_writer.py +0 -0
  83. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/plugins/vtk_reader.py +0 -0
  84. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/__init__.py +0 -0
  85. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/dsi_utils.py +0 -0
  86. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/federated/__init__.py +0 -0
  87. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/federated/federate_datasets.py +0 -0
  88. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/federation_utils.py +0 -0
  89. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/git_utils.py +0 -0
  90. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/hpc_kerberos.py +0 -0
  91. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/rsync_utils.py +0 -0
  92. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/s3_utils.py +0 -0
  93. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi/utils/web_utils.py +0 -0
  94. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi_workflow.egg-info/dependency_links.txt +0 -0
  95. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi_workflow.egg-info/requires.txt +0 -0
  96. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/dsi_workflow.egg-info/top_level.txt +0 -0
  97. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/requirements.txt +0 -0
  98. {dsi_workflow-1.2.4 → dsi_workflow-1.2.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dsi-workflow
3
- Version: 1.2.4
3
+ Version: 1.2.6
4
4
  Summary: A Data Science Infrastructure Project
5
5
  Author-email: Jesus Pulido <pulido@lanl.gov>, James Ahrens <ahrens@lanl.gov>, Divya Banesh <dbanesh@lanl.gov>, Hugh Greenberg <hng@lanl.gov>, Pascal Grosset <pascalgrosset@lanl.gov>, Vedant Iyer <iyer@lanl.gov>, Benjamin Sims <bsims@lanl.gov>, Terece Turton <tlturton@lanl.gov>
6
6
  License-Expression: BSD-3-Clause
@@ -0,0 +1 @@
1
+ __version__ = "1.2.6"
@@ -407,7 +407,7 @@ class DuckDB(Filesystem):
407
407
  """
408
408
  data = None
409
409
  command = query.strip().split(None, 1)[0].lower()
410
- if command in {"select", "pragma"}:
410
+ if command in {"select", "pragma", "with"}:
411
411
  try:
412
412
  data = self.cur.execute(query).fetch_df()
413
413
  if isVerbose:
@@ -106,7 +106,7 @@ class NDP(Webserver):
106
106
  if self.api_key:
107
107
  self.headers["Authorization"] = self.api_key
108
108
 
109
- # skip data retrieval if only checking connection to oceans11
109
+ # skip data retrieval if only checking connection to ndp
110
110
  if kwargs.get("only_validate", False):
111
111
  return
112
112
 
@@ -120,7 +120,7 @@ class OSTI(Webserver):
120
120
  if self.api_key:
121
121
  self.headers["Authorization"] = self.api_key
122
122
 
123
- # skip data retrieval if only checking connection to oceans11
123
+ # skip data retrieval if only checking connection to osti
124
124
  if kwargs.get("only_validate", False):
125
125
  return
126
126
 
@@ -230,7 +230,7 @@ class RCSBPDB(Webserver):
230
230
  self.params = params or {}
231
231
  self.validate_error_msg = None
232
232
 
233
- # skip data retrieval if only checking connection to oceans11
233
+ # skip data retrieval if only checking connection to rcsbpdb
234
234
  if kwargs.get("only_validate", False):
235
235
  return
236
236
 
@@ -257,7 +257,7 @@ class RCSBPDB(Webserver):
257
257
 
258
258
  if self.validate_on_init and not self.validate_connection():
259
259
  self._loaded = False
260
- raise ConnectionError(self.validate_error_msg or "RCSBPDB connection validation failed.")
260
+ raise ConnectionError(self.validate_error_msg or "Validating RCSBPDB connection failed.")
261
261
 
262
262
  if self.auto_load:
263
263
  try:
@@ -1081,13 +1081,19 @@ class RCSBPDB(Webserver):
1081
1081
 
1082
1082
  def get_table(self, table_name: str, dict_return=False):
1083
1083
  resolved = self._resolve_table_name(table_name)
1084
+
1085
+ if resolved not in self.schemas:
1086
+ raise ValueError(
1087
+ f"Table '{table_name}' not found. Available tables: {list(self.schemas.keys())}"
1088
+ )
1089
+
1084
1090
  table = self.tables.get(resolved, OrderedDict())
1085
1091
 
1086
1092
  if dict_return:
1087
1093
  return table
1088
1094
 
1089
1095
  if not table:
1090
- return pd.DataFrame(columns=self.get_schema(resolved))
1096
+ return pd.DataFrame(columns=self.schemas.get(resolved, []))
1091
1097
 
1092
1098
  return pd.DataFrame(table)
1093
1099
 
@@ -1202,10 +1208,56 @@ class RCSBPDB(Webserver):
1202
1208
  return self.tables
1203
1209
 
1204
1210
  def get_schema(self, table_name: str | None = None):
1205
- if table_name is None:
1206
- return self.schemas
1207
- resolved = self._resolve_table_name(table_name)
1208
- return self.schemas.get(resolved, [])
1211
+ """
1212
+ Return SQL-style CREATE TABLE schema text.
1213
+
1214
+ Parameters
1215
+ ----------
1216
+ table_name : str or None
1217
+ If provided, return schema text for one table.
1218
+ If None, return schema text for all loaded/known tables.
1219
+ """
1220
+ table_names = [self._resolve_table_name(table_name)] if table_name else list(self.schemas.keys())
1221
+ schema_lines = []
1222
+
1223
+ for name in table_names:
1224
+ if name not in self.schemas:
1225
+ raise ValueError(
1226
+ f"Table '{table_name}' not found. Available tables: {list(self.schemas.keys())}"
1227
+ )
1228
+
1229
+ table = self.tables.get(name, OrderedDict())
1230
+ cols = []
1231
+
1232
+ for col_name in self.schemas[name]:
1233
+ dtype = "TEXT"
1234
+
1235
+ values = table.get(col_name, []) if isinstance(table, dict) else []
1236
+
1237
+ for value in values:
1238
+ if value is None:
1239
+ continue
1240
+
1241
+ if isinstance(value, bool):
1242
+ dtype = "BOOLEAN"
1243
+ elif isinstance(value, int):
1244
+ dtype = "INTEGER"
1245
+ elif isinstance(value, float):
1246
+ dtype = "REAL"
1247
+
1248
+ break
1249
+
1250
+ cols.append(f" {col_name} {dtype}")
1251
+
1252
+ create_stmt = (
1253
+ f"CREATE TABLE {name} (\n"
1254
+ + ",\n".join(cols)
1255
+ + "\n);"
1256
+ )
1257
+
1258
+ schema_lines.append(create_stmt)
1259
+
1260
+ return "\n\n".join(schema_lines)
1209
1261
 
1210
1262
  def num_tables(self):
1211
1263
  table_count = len(self.tables)
@@ -1353,16 +1405,25 @@ class RCSBPDB(Webserver):
1353
1405
 
1354
1406
  return matches
1355
1407
 
1356
- def find_cell(self, query_object, **kwargs):
1408
+ def find_cell(self, query_object, row=True, **kwargs):
1357
1409
  """
1358
1410
  Finds all cells that match the given query_object.
1359
1411
 
1412
+ Parameters
1413
+ ----------
1414
+ query_object : Any
1415
+ Value to search for.
1416
+ row : bool, optional
1417
+ If True, return full matching rows.
1418
+ If False, return individual matching cells.
1419
+
1360
1420
  Matching behavior:
1361
1421
  - Exact match for all data types
1362
1422
  - Case-insensitive partial match for strings
1363
1423
  - String representation match for complex objects
1364
1424
  """
1365
1425
  matches = []
1426
+ seen_rows = set()
1366
1427
 
1367
1428
  is_str_query = isinstance(query_object, str)
1368
1429
  query_lower = query_object.lower() if is_str_query else None
@@ -1374,9 +1435,14 @@ class RCSBPDB(Webserver):
1374
1435
  cols = list(table_data.keys())
1375
1436
  df = pd.DataFrame(table_data)
1376
1437
 
1377
- for row_idx, row in df.iterrows():
1438
+ if df.empty:
1439
+ continue
1440
+
1441
+ for row_idx, row_data in df.iterrows():
1442
+ row_matched = False
1443
+
1378
1444
  for col in cols:
1379
- cell = row[col]
1445
+ cell = row_data[col]
1380
1446
  match = False
1381
1447
 
1382
1448
  if (
@@ -1389,21 +1455,42 @@ class RCSBPDB(Webserver):
1389
1455
  )
1390
1456
  ):
1391
1457
  match = True
1392
-
1393
1458
  elif is_str_query and isinstance(cell, (dict, list, tuple)):
1394
1459
  cell_str = str(cell).lower()
1460
+
1395
1461
  if query_lower in cell_str:
1396
1462
  match = True
1397
1463
 
1398
- if match:
1399
- val = ValueObject()
1400
- val.t_name = table_name
1401
- val.c_name = cols
1402
- val.row_num = row_idx
1403
- val.value = row.tolist()
1404
- val.type = "cell"
1464
+ if not match:
1465
+ continue
1466
+
1467
+ if row:
1468
+ row_matched = True
1469
+ break
1470
+
1471
+ val = ValueObject()
1472
+ val.t_name = table_name
1473
+ val.c_name = [col]
1474
+ val.row_num = int(row_idx)
1475
+ val.value = cell
1476
+ val.type = "cell"
1477
+ matches.append(val)
1405
1478
 
1406
- matches.append(val)
1479
+ if row and row_matched:
1480
+ key = (table_name, int(row_idx))
1481
+
1482
+ if key in seen_rows:
1483
+ continue
1484
+
1485
+ seen_rows.add(key)
1486
+
1487
+ val = ValueObject()
1488
+ val.t_name = table_name
1489
+ val.c_name = cols
1490
+ val.row_num = int(row_idx)
1491
+ val.value = row_data.to_dict()
1492
+ val.type = "row"
1493
+ matches.append(val)
1407
1494
 
1408
1495
  return matches
1409
1496
 
@@ -1549,25 +1636,19 @@ class RCSBPDB(Webserver):
1549
1636
  print(f" - num of rows: {df.shape[0]}")
1550
1637
  print()
1551
1638
 
1552
- def display(self, table_name=None, num_rows=25, display_cols=None, **kwargs):
1639
+ def display(self, table_name, num_rows=25, display_cols=None, **kwargs):
1553
1640
  """
1554
- Print data from a table.
1641
+ Return a preview DataFrame from a table.
1555
1642
 
1556
1643
  Parameters
1557
1644
  ----------
1558
1645
  table_name : str
1559
1646
  Name of the table to display.
1560
1647
  num_rows : int, optional
1561
- Number of rows to print.
1648
+ Number of rows to return.
1562
1649
  display_cols : list[str], optional
1563
1650
  Specific columns to display.
1564
1651
  """
1565
- if table_name is None:
1566
- raise ValueError(
1567
- "display() requires a table_name. "
1568
- f"Available tables: {self.list(True)}"
1569
- )
1570
-
1571
1652
  resolved = self._resolve_table_name(table_name)
1572
1653
 
1573
1654
  if resolved not in self.schemas:
@@ -1592,7 +1673,10 @@ class RCSBPDB(Webserver):
1592
1673
 
1593
1674
  df = df[display_cols]
1594
1675
 
1595
- print(df.head(num_rows).to_string(index=False))
1676
+ if num_rows:
1677
+ df = df.head(num_rows)
1678
+
1679
+ return df
1596
1680
 
1597
1681
 
1598
1682
  def summary(self, table_name=None, *args, **kwargs):
@@ -257,9 +257,18 @@ def test_rcsbpdb_classify_usability():
257
257
  def test_rcsbpdb_initialization_no_auto_load(empty_backend):
258
258
  assert empty_backend._loaded is True
259
259
  assert empty_backend.list(True) == []
260
- assert empty_backend.get_schema("datasets") == RCSBPDB.DATASET_SCHEMA
261
- assert empty_backend.get_schema("resources") == RCSBPDB.RESOURCE_SCHEMA
262
- assert empty_backend.get_schema("errors") == RCSBPDB.ERROR_SCHEMA
260
+
261
+ datasets_schema = empty_backend.get_schema("datasets")
262
+ resources_schema = empty_backend.get_schema("resources")
263
+ errors_schema = empty_backend.get_schema("errors")
264
+
265
+ assert isinstance(datasets_schema, str)
266
+ assert isinstance(resources_schema, str)
267
+ assert isinstance(errors_schema, str)
268
+
269
+ assert "CREATE TABLE datasets" in datasets_schema
270
+ assert "CREATE TABLE resources" in resources_schema
271
+ assert "CREATE TABLE errors" in errors_schema
263
272
 
264
273
 
265
274
  def test_rcsbpdb_initialization_with_identifiers(loaded_backend):
@@ -406,10 +415,26 @@ def test_rcsbpdb_get_schema(empty_backend):
406
415
  resource_schema = empty_backend.get_schema("resource")
407
416
  error_schema = empty_backend.get_schema("error")
408
417
 
409
- assert isinstance(full_schema, dict)
410
- assert dataset_schema == RCSBPDB.DATASET_SCHEMA
411
- assert resource_schema == RCSBPDB.RESOURCE_SCHEMA
412
- assert error_schema == RCSBPDB.ERROR_SCHEMA
418
+ assert isinstance(full_schema, str)
419
+ assert isinstance(dataset_schema, str)
420
+ assert isinstance(resource_schema, str)
421
+ assert isinstance(error_schema, str)
422
+
423
+ assert "CREATE TABLE datasets" in full_schema
424
+ assert "CREATE TABLE resources" in full_schema
425
+ assert "CREATE TABLE errors" in full_schema
426
+
427
+ assert "CREATE TABLE datasets" in dataset_schema
428
+ assert "dataset_id" in dataset_schema
429
+ assert "doi" in dataset_schema
430
+
431
+ assert "CREATE TABLE resources" in resource_schema
432
+ assert "resource_id" in resource_schema
433
+ assert "download_url" in resource_schema
434
+
435
+ assert "CREATE TABLE errors" in error_schema
436
+ assert "identifier" in error_schema
437
+ assert "status" in error_schema
413
438
 
414
439
 
415
440
  def test_rcsbpdb_table_name_resolution(empty_backend):
@@ -582,7 +607,7 @@ def test_rcsbpdb_find_searches_table_column_and_cell_values(loaded_backend):
582
607
 
583
608
  assert isinstance(matches, list)
584
609
  assert len(matches) >= 1
585
- assert any(match.type == "cell" for match in matches)
610
+ assert any(match.type == "row" for match in matches)
586
611
  assert any(match.t_name == "datasets" for match in matches)
587
612
 
588
613
 
@@ -624,7 +649,7 @@ def test_rcsbpdb_find_cell_matches_value(loaded_backend):
624
649
 
625
650
  assert isinstance(matches, list)
626
651
  assert len(matches) >= 1
627
- assert all(match.type == "cell" for match in matches)
652
+ assert all(match.type == "row" for match in matches)
628
653
 
629
654
  dataset_matches = [
630
655
  match for match in matches
@@ -632,8 +657,28 @@ def test_rcsbpdb_find_cell_matches_value(loaded_backend):
632
657
  ]
633
658
 
634
659
  assert len(dataset_matches) >= 1
635
- assert any("1CBS" in match.value for match in dataset_matches)
660
+ assert any(
661
+ match.value["dataset_id"] == "1CBS"
662
+ for match in dataset_matches
663
+ )
664
+
665
+ def test_rcsbpdb_find_cell_cell_type_support(loaded_backend):
666
+ matches = loaded_backend.find_cell("1CBS", row=False)
667
+
668
+ assert isinstance(matches, list)
669
+ assert len(matches) >= 1
670
+ assert all(match.type == "cell" for match in matches)
636
671
 
672
+ dataset_matches = [
673
+ match for match in matches
674
+ if match.t_name == "datasets"
675
+ ]
676
+
677
+ assert len(dataset_matches) >= 1
678
+ assert any(
679
+ match.c_name == ["dataset_id"] and match.value == "1CBS"
680
+ for match in dataset_matches
681
+ )
637
682
 
638
683
  def test_rcsbpdb_find_relation_condition_string(loaded_backend):
639
684
  matches = loaded_backend.find_relation("dataset_id = 1CBS")
@@ -842,11 +887,25 @@ def test_rcsbpdb_summary_single_table(loaded_backend):
842
887
  assert "dataset_id" in set(summary["column"])
843
888
 
844
889
 
845
- def test_rcsbpdb_display_returns_none(loaded_backend):
890
+ def test_rcsbpdb_display_returns_dataframe(loaded_backend):
846
891
  result = loaded_backend.display("datasets", num_rows=1)
847
892
 
848
- assert result is None
893
+ assert isinstance(result, pd.DataFrame)
894
+ assert len(result) == 1
849
895
 
896
+ def test_rcsbpdb_display_cols(loaded_backend):
897
+ result = loaded_backend.display(
898
+ "datasets",
899
+ num_rows=2,
900
+ display_cols=["dataset_id", "title"],
901
+ )
902
+
903
+ assert isinstance(result, pd.DataFrame)
904
+ assert list(result.columns) == ["dataset_id", "title"]
905
+
906
+ def test_rcsbpdb_display_requires_table_name(loaded_backend):
907
+ with pytest.raises(TypeError):
908
+ loaded_backend.display()
850
909
 
851
910
  def test_rcsbpdb_display_missing_table_raises_value_error(empty_backend):
852
911
  with pytest.raises(ValueError):