linkml-store 0.1.11__tar.gz → 0.1.13__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.

Potentially problematic release.


This version of linkml-store might be problematic. Click here for more details.

Files changed (67) hide show
  1. {linkml_store-0.1.11 → linkml_store-0.1.13}/PKG-INFO +6 -2
  2. {linkml_store-0.1.11 → linkml_store-0.1.13}/pyproject.toml +6 -2
  3. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/client.py +2 -0
  4. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/collection.py +58 -9
  5. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/config.py +12 -1
  6. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/database.py +34 -3
  7. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/duckdb/duckdb_database.py +31 -3
  8. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/mongodb/mongodb_database.py +31 -1
  9. linkml_store-0.1.13/src/linkml_store/api/stores/neo4j/neo4j_collection.py +429 -0
  10. linkml_store-0.1.13/src/linkml_store/api/stores/neo4j/neo4j_database.py +154 -0
  11. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/cli.py +29 -2
  12. linkml_store-0.1.13/src/linkml_store/graphs/graph_map.py +24 -0
  13. linkml_store-0.1.13/src/linkml_store/utils/__init__.py +0 -0
  14. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/format_utils.py +132 -14
  15. linkml_store-0.1.13/src/linkml_store/utils/mongodb_utils.py +145 -0
  16. linkml_store-0.1.13/src/linkml_store/utils/neo4j_utils.py +42 -0
  17. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/sql_utils.py +7 -2
  18. linkml_store-0.1.13/src/linkml_store/webapi/__init__.py +0 -0
  19. linkml_store-0.1.13/src/linkml_store/webapi/html/generic.html.j2 +43 -0
  20. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/webapi/main.py +346 -63
  21. linkml_store-0.1.11/src/linkml_store/webapi/html/generic.html.j2 +0 -46
  22. {linkml_store-0.1.11 → linkml_store-0.1.13}/LICENSE +0 -0
  23. {linkml_store-0.1.11 → linkml_store-0.1.13}/README.md +0 -0
  24. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/__init__.py +0 -0
  25. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/__init__.py +0 -0
  26. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/queries.py +0 -0
  27. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/__init__.py +0 -0
  28. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/chromadb/__init__.py +0 -0
  29. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/chromadb/chromadb_collection.py +0 -0
  30. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/chromadb/chromadb_database.py +0 -0
  31. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/duckdb/__init__.py +0 -0
  32. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/duckdb/duckdb_collection.py +0 -0
  33. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/duckdb/mappings.py +0 -0
  34. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/filesystem/__init__.py +0 -0
  35. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/filesystem/filesystem_collection.py +0 -0
  36. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/filesystem/filesystem_database.py +0 -0
  37. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/hdf5/__init__.py +0 -0
  38. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/hdf5/hdf5_collection.py +0 -0
  39. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/hdf5/hdf5_database.py +0 -0
  40. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/mongodb/__init__.py +0 -0
  41. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/mongodb/mongodb_collection.py +0 -0
  42. {linkml_store-0.1.11/src/linkml_store/index/implementations → linkml_store-0.1.13/src/linkml_store/api/stores/neo4j}/__init__.py +0 -0
  43. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/solr/__init__.py +0 -0
  44. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/solr/solr_collection.py +0 -0
  45. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/solr/solr_database.py +0 -0
  46. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/stores/solr/solr_utils.py +0 -0
  47. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/api/types.py +0 -0
  48. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/constants.py +0 -0
  49. {linkml_store-0.1.11/src/linkml_store/utils → linkml_store-0.1.13/src/linkml_store/graphs}/__init__.py +0 -0
  50. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/index/__init__.py +0 -0
  51. {linkml_store-0.1.11/src/linkml_store/webapi → linkml_store-0.1.13/src/linkml_store/index/implementations}/__init__.py +0 -0
  52. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/index/implementations/llm_indexer.py +0 -0
  53. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/index/implementations/simple_indexer.py +0 -0
  54. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/index/indexer.py +0 -0
  55. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/change_utils.py +0 -0
  56. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/file_utils.py +0 -0
  57. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/io.py +0 -0
  58. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/object_utils.py +0 -0
  59. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/pandas_utils.py +0 -0
  60. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/patch_utils.py +0 -0
  61. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/query_utils.py +0 -0
  62. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/utils/schema_utils.py +0 -0
  63. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/webapi/html/__init__.py +0 -0
  64. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/webapi/html/base.html.j2 +0 -0
  65. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/webapi/html/collection_details.html.j2 +0 -0
  66. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/webapi/html/database_details.html.j2 +0 -0
  67. {linkml_store-0.1.11 → linkml_store-0.1.13}/src/linkml_store/webapi/html/databases.html.j2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml-store
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: linkml-store
5
5
  License: MIT
6
6
  Author: Author 1
@@ -21,6 +21,7 @@ Provides-Extra: h5py
21
21
  Provides-Extra: llm
22
22
  Provides-Extra: map
23
23
  Provides-Extra: mongodb
24
+ Provides-Extra: neo4j
24
25
  Provides-Extra: pyarrow
25
26
  Provides-Extra: renderer
26
27
  Provides-Extra: tests
@@ -28,7 +29,7 @@ Provides-Extra: validation
28
29
  Requires-Dist: black (>=24.0.0) ; extra == "tests"
29
30
  Requires-Dist: chromadb ; extra == "chromadb"
30
31
  Requires-Dist: click
31
- Requires-Dist: duckdb (>=0.10.1,<0.11.0)
32
+ Requires-Dist: duckdb (>=0.10.1)
32
33
  Requires-Dist: duckdb-engine (>=0.11.2)
33
34
  Requires-Dist: fastapi ; extra == "fastapi"
34
35
  Requires-Dist: frictionless ; extra == "frictionless"
@@ -41,8 +42,11 @@ Requires-Dist: linkml_map ; extra == "map"
41
42
  Requires-Dist: linkml_renderer ; extra == "renderer"
42
43
  Requires-Dist: llm ; extra == "llm"
43
44
  Requires-Dist: matplotlib ; extra == "analytics"
45
+ Requires-Dist: neo4j ; extra == "neo4j"
46
+ Requires-Dist: networkx ; extra == "neo4j"
44
47
  Requires-Dist: pandas (>=2.2.1) ; extra == "analytics"
45
48
  Requires-Dist: plotly ; extra == "analytics"
49
+ Requires-Dist: py2neo ; extra == "neo4j"
46
50
  Requires-Dist: pyarrow ; extra == "pyarrow"
47
51
  Requires-Dist: pydantic (>=2.0.0,<3.0.0)
48
52
  Requires-Dist: pymongo ; extra == "mongodb"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "linkml-store"
3
- version = "0.1.11"
3
+ version = "0.1.13"
4
4
  description = "linkml-store"
5
5
  authors = ["Author 1 <author@org.org>"]
6
6
  license = "MIT"
@@ -13,7 +13,7 @@ pydantic = "^2.0.0"
13
13
  linkml-runtime = ">=1.8.0"
14
14
  streamlit = { version = "^1.32.2", optional = true }
15
15
  sqlalchemy = "*"
16
- duckdb = "^0.10.1"
16
+ duckdb = ">=0.10.1"
17
17
  duckdb-engine = ">=0.11.2"
18
18
  matplotlib = { version = "*", optional = true }
19
19
  seaborn = { version = "*", optional = true }
@@ -22,6 +22,9 @@ pystow = "^0.5.4"
22
22
  black = { version=">=24.0.0", optional = true }
23
23
  llm = { version="*", optional = true }
24
24
  pymongo = { version="*", optional = true }
25
+ neo4j = { version="*", optional = true }
26
+ py2neo = { version="*", optional = true }
27
+ networkx = { version="*", optional = true }
25
28
  chromadb = { version="*", optional = true }
26
29
  pyarrow = { version="*", optional = true }
27
30
  h5py = { version="*", optional = true }
@@ -66,6 +69,7 @@ app = ["streamlit"]
66
69
  tests = ["black"]
67
70
  llm = ["llm"]
68
71
  mongodb = ["pymongo"]
72
+ neo4j = ["neo4j", "py2neo", "networkx"]
69
73
  chromadb = ["chromadb"]
70
74
  h5py = ["h5py"]
71
75
  pyarrow = ["pyarrow"]
@@ -11,6 +11,7 @@ from linkml_store.api.stores.chromadb.chromadb_database import ChromaDBDatabase
11
11
  from linkml_store.api.stores.duckdb.duckdb_database import DuckDBDatabase
12
12
  from linkml_store.api.stores.filesystem.filesystem_database import FileSystemDatabase
13
13
  from linkml_store.api.stores.mongodb.mongodb_database import MongoDBDatabase
14
+ from linkml_store.api.stores.neo4j.neo4j_database import Neo4jDatabase
14
15
  from linkml_store.api.stores.solr.solr_database import SolrDatabase
15
16
 
16
17
  logger = logging.getLogger(__name__)
@@ -21,6 +22,7 @@ HANDLE_MAP = {
21
22
  "solr": SolrDatabase,
22
23
  "mongodb": MongoDBDatabase,
23
24
  "chromadb": ChromaDBDatabase,
25
+ "neo4j": Neo4jDatabase,
24
26
  "file": FileSystemDatabase,
25
27
  }
26
28
 
@@ -4,7 +4,21 @@ import hashlib
4
4
  import logging
5
5
  from collections import defaultdict
6
6
  from pathlib import Path
7
- from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generic, Iterator, List, Optional, TextIO, Tuple, Type, Union
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ ClassVar,
11
+ Dict,
12
+ Generic,
13
+ Iterable,
14
+ Iterator,
15
+ List,
16
+ Optional,
17
+ TextIO,
18
+ Tuple,
19
+ Type,
20
+ Union,
21
+ )
8
22
 
9
23
  import numpy as np
10
24
  from linkml_runtime import SchemaView
@@ -202,6 +216,12 @@ class Collection(Generic[DatabaseType]):
202
216
  self._materialize_derivations()
203
217
  self._initialized = True
204
218
 
219
+ def _pre_insert_hook(self, objs: List[OBJECT], **kwargs):
220
+ if self.metadata.validate_modifications:
221
+ errors = list(self.iter_validate_collection(objs))
222
+ if errors:
223
+ raise ValueError(f"Validation errors: {errors}")
224
+
205
225
  def _post_insert_hook(self, objs: List[OBJECT], **kwargs):
206
226
  self._initialized = True
207
227
  patches = [{"op": "add", "path": "/0", "value": obj} for obj in objs]
@@ -346,7 +366,10 @@ class Collection(Generic[DatabaseType]):
346
366
  id_field = self.identifier_attribute_name
347
367
  if not id_field:
348
368
  raise ValueError(f"No identifier for {self.name}")
349
- return self.find({id_field: ids})
369
+ if len(ids) == 1:
370
+ return self.find({id_field: ids[0]})
371
+ else:
372
+ return self.find({id_field: {"$in": ids}})
350
373
 
351
374
  def get_one(self, id: IDENTIFIER, **kwargs) -> Optional[OBJECT]:
352
375
  """
@@ -518,7 +541,7 @@ class Collection(Generic[DatabaseType]):
518
541
  :return:
519
542
  """
520
543
  cd = self.class_definition()
521
- return cd is not None
544
+ return cd is not None and cd.attributes
522
545
 
523
546
  def load_from_source(self, load_if_exists=False):
524
547
  """
@@ -535,11 +558,19 @@ class Collection(Generic[DatabaseType]):
535
558
  kwargs = source.arguments or {}
536
559
  if source.local_path:
537
560
  objects = load_objects(
538
- metadata.source.local_path, format=source.format, expected_type=source.expected_type, **kwargs
561
+ metadata.source.local_path,
562
+ format=source.format,
563
+ expected_type=source.expected_type,
564
+ compression=source.compression,
565
+ **kwargs,
539
566
  )
540
567
  elif metadata.source.url:
541
568
  objects = load_objects_from_url(
542
- metadata.source.url, format=source.format, expected_type=source.expected_type, **kwargs
569
+ metadata.source.url,
570
+ format=source.format,
571
+ expected_type=source.expected_type,
572
+ compression=source.compression,
573
+ **kwargs,
543
574
  )
544
575
  self.insert(objects)
545
576
 
@@ -746,6 +777,7 @@ class Collection(Generic[DatabaseType]):
746
777
  sv: SchemaView = self.parent.schema_view
747
778
  if sv:
748
779
  cls = sv.get_class(self.target_class_name)
780
+ # cls = sv.schema.classes[self.target_class_name]
749
781
  if cls and not cls.attributes:
750
782
  if not sv.class_induced_slots(cls.name):
751
783
  for att in self._induce_attributes():
@@ -868,7 +900,7 @@ class Collection(Generic[DatabaseType]):
868
900
  exact_dimensions_list.append(v.shape)
869
901
  break
870
902
  if isinstance(v, list):
871
- v = v[0]
903
+ v = v[0] if v else None
872
904
  multivalueds.append(True)
873
905
  elif isinstance(v, dict):
874
906
  v = list(v.values())[0]
@@ -966,11 +998,14 @@ class Collection(Generic[DatabaseType]):
966
998
  patches_from_objects_lists(src_objs, tgt_objs, primary_key=primary_key)
967
999
  return patches_from_objects_lists(src_objs, tgt_objs, primary_key=primary_key)
968
1000
 
969
- def iter_validate_collection(self, **kwargs) -> Iterator["ValidationResult"]:
1001
+ def iter_validate_collection(
1002
+ self, objects: Optional[Iterable[OBJECT]] = None, **kwargs
1003
+ ) -> Iterator["ValidationResult"]:
970
1004
  """
971
1005
  Validate the contents of the collection
972
1006
 
973
1007
  :param kwargs:
1008
+ :param objects: objects to validate
974
1009
  :return: iterator over validation results
975
1010
  """
976
1011
  from linkml.validator import JsonschemaValidationPlugin, Validator
@@ -980,10 +1015,24 @@ class Collection(Generic[DatabaseType]):
980
1015
  cd = self.class_definition()
981
1016
  if not cd:
982
1017
  raise ValueError(f"Cannot find class definition for {self.target_class_name}")
1018
+ type_designator = None
1019
+ for att in self.parent.schema_view.class_induced_slots(cd.name):
1020
+ if att.designates_type:
1021
+ type_designator = att.name
983
1022
  class_name = cd.name
984
- for obj in self.find_iter(**kwargs):
1023
+ if objects is None:
1024
+ objects = self.find_iter(**kwargs)
1025
+ for obj in objects:
985
1026
  obj = clean_empties(obj)
986
- yield from validator.iter_results(obj, class_name)
1027
+ v_class_name = class_name
1028
+ if type_designator is not None:
1029
+ # TODO: move type designator logic to core linkml
1030
+ this_class_name = obj.get(type_designator)
1031
+ if this_class_name:
1032
+ if ":" in this_class_name:
1033
+ this_class_name = this_class_name.split(":")[-1]
1034
+ v_class_name = this_class_name
1035
+ yield from validator.iter_results(obj, v_class_name)
987
1036
 
988
1037
  def commit(self):
989
1038
  """
@@ -2,6 +2,8 @@ from typing import Any, Dict, List, Optional
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
5
+ from linkml_store.graphs.graph_map import GraphProjection
6
+
5
7
 
6
8
  class ConfiguredBaseModel(BaseModel, extra="forbid"):
7
9
  """
@@ -33,6 +35,7 @@ class CollectionSource(ConfiguredBaseModel):
33
35
  refresh_interval_days: Optional[float] = None
34
36
  expected_type: Optional[str] = None
35
37
  format: Optional[str] = None
38
+ compression: Optional[str] = None
36
39
  arguments: Optional[Dict[str, Any]] = None
37
40
 
38
41
 
@@ -73,11 +76,19 @@ class CollectionConfig(ConfiguredBaseModel):
73
76
  default=None,
74
77
  description="Metadata about the source",
75
78
  )
76
- # TODO: derived_from
77
79
  derived_from: Optional[List[DerivationConfiguration]] = Field(
78
80
  default=None,
79
81
  description="LinkML-Map derivations",
80
82
  )
83
+ page_size: Optional[int] = Field(default=None, description="Suggested page size (items per page) in apps and APIs")
84
+ graph_projection: Optional[GraphProjection] = Field(
85
+ default=None,
86
+ description="Optional graph projection configuration",
87
+ )
88
+ validate_modifications: Optional[bool] = Field(
89
+ default=False,
90
+ description="Whether to validate inserts, updates, and deletes",
91
+ )
81
92
 
82
93
 
83
94
  class DatabaseConfig(ConfiguredBaseModel):
@@ -19,7 +19,7 @@ from typing import (
19
19
  )
20
20
 
21
21
  from linkml_store.api.types import CollectionType
22
- from linkml_store.utils.format_utils import load_objects, render_output
22
+ from linkml_store.utils.format_utils import Format, load_objects, render_output
23
23
  from linkml_store.utils.patch_utils import PatchDict
24
24
 
25
25
  try:
@@ -505,8 +505,10 @@ class Database(ABC, Generic[CollectionType]):
505
505
  if isinstance(schema_view, str):
506
506
  schema_view = SchemaView(schema_view)
507
507
  self._schema_view = schema_view
508
+ # self._schema_view = SchemaView(schema_view.materialize_derived_schema())
508
509
  if not self._collections:
509
510
  return
511
+
510
512
  # align with induced schema
511
513
  roots = [c for c in schema_view.all_classes().values() if c.tree_root]
512
514
  if len(roots) == 0:
@@ -705,7 +707,7 @@ class Database(ABC, Generic[CollectionType]):
705
707
  """
706
708
  raise NotImplementedError()
707
709
 
708
- def import_database(self, location: str, source_format: Optional[str] = None, **kwargs):
710
+ def import_database(self, location: str, source_format: Optional[Union[str, Format]] = None, **kwargs):
709
711
  """
710
712
  Import a database from a file or location.
711
713
 
@@ -713,11 +715,27 @@ class Database(ABC, Generic[CollectionType]):
713
715
  :param source_format: source format
714
716
  :param kwargs: additional arguments
715
717
  """
718
+ if isinstance(source_format, str):
719
+ source_format = Format(source_format)
720
+ if isinstance(source_format, Format):
721
+ if source_format.is_dump_format() and source_format in [Format.SQLDUMP_DUCKDB, Format.DUMP_MONGODB]:
722
+ # import into a test instance
723
+ tmp_handle = source_format.value
724
+ client = self.parent
725
+ tmp_db = client.attach_database(tmp_handle, alias="tmp")
726
+ # TODO: check for infinite recursion
727
+ tmp_db.import_database(location, source_format=source_format)
728
+ obj = {}
729
+ for coll in tmp_db.list_collections():
730
+ qr = coll.find({}, limit=-1)
731
+ obj[coll.alias] = qr.rows
732
+ self.store(obj)
733
+ return
716
734
  objects = load_objects(location, format=source_format)
717
735
  for obj in objects:
718
736
  self.store(obj)
719
737
 
720
- def export_database(self, location: str, target_format: Optional[str] = None, **kwargs):
738
+ def export_database(self, location: str, target_format: Optional[Union[str, Format]] = None, **kwargs):
721
739
  """
722
740
  Export a database to a file or location.
723
741
 
@@ -726,10 +744,23 @@ class Database(ABC, Generic[CollectionType]):
726
744
  :param kwargs: additional arguments
727
745
  """
728
746
  obj = {}
747
+ if isinstance(target_format, str):
748
+ target_format = Format(target_format)
729
749
  for coll in self.list_collections():
730
750
  qr = coll.find({}, limit=-1)
731
751
  obj[coll.alias] = qr.rows
732
752
  logger.info(f"Exporting object with {len(obj)} collections to {location} in {target_format} format")
753
+ if isinstance(target_format, Format):
754
+ if target_format.is_dump_format() and target_format in [Format.SQLDUMP_DUCKDB, Format.DUMP_MONGODB]:
755
+ tmp_handle = target_format.value
756
+ client = self.parent
757
+ tmp_db = client.attach_database(tmp_handle, alias="tmp")
758
+ tmp_db.store(obj)
759
+ # TODO: check for infinite recursion
760
+ tmp_db.export_database(location, target_format=target_format)
761
+ return
762
+ if Path(location).is_dir():
763
+ raise ValueError(f"{location} is a directory; cannot write {target_format} to a dir")
733
764
  with open(location, "w", encoding="utf-8") as stream:
734
765
  stream.write(render_output(obj, format=target_format))
735
766
 
@@ -1,11 +1,10 @@
1
1
  import json
2
2
  import logging
3
3
  from pathlib import Path
4
- from typing import Optional
4
+ from typing import Optional, Union
5
5
 
6
6
  import pandas as pd
7
7
  import sqlalchemy
8
- from duckdb import DuckDBPyConnection
9
8
  from linkml_runtime import SchemaView
10
9
  from linkml_runtime.linkml_model import ClassDefinition, SlotDefinition
11
10
  from linkml_runtime.utils.schema_builder import SchemaBuilder
@@ -14,6 +13,7 @@ from sqlalchemy import NullPool, text
14
13
  from linkml_store.api import Database
15
14
  from linkml_store.api.queries import Query, QueryResult
16
15
  from linkml_store.api.stores.duckdb.duckdb_collection import DuckDBCollection
16
+ from linkml_store.utils.format_utils import Format
17
17
  from linkml_store.utils.sql_utils import introspect_schema, query_to_sql
18
18
 
19
19
  TYPE_MAP = {
@@ -45,7 +45,7 @@ class DuckDBDatabase(Database):
45
45
  types are used for nested inlined objects.
46
46
  """
47
47
 
48
- _connection: DuckDBPyConnection = None
48
+ # _connection: DuckDBPyConnection = None
49
49
  _engine: sqlalchemy.Engine = None
50
50
  collection_class = DuckDBCollection
51
51
 
@@ -202,3 +202,31 @@ class DuckDBDatabase(Database):
202
202
  cls = ClassDefinition(name=collection_metadata.type, attributes=collection_metadata.attributes)
203
203
  schema.classes[cls.name] = cls
204
204
  return SchemaView(schema)
205
+
206
+ def export_database(self, location: str, target_format: Optional[Union[str, Format]] = None, **kwargs):
207
+ if target_format == "duckdb" or target_format == Format.SQLDUMP_DUCKDB:
208
+ path = Path(location)
209
+ if path.exists():
210
+ if path.is_file():
211
+ path.unlink()
212
+ with self.engine.connect() as conn:
213
+ sql = text(f"EXPORT DATABASE '{location}'")
214
+ conn.execute(sql)
215
+ else:
216
+ super().export_database(location, target_format=target_format, **kwargs)
217
+
218
+ def import_database(self, location: str, source_format: Optional[str] = None, **kwargs):
219
+ """
220
+ Import a database from a file or location.
221
+
222
+ :param location: location of the file
223
+ :param source_format: source format
224
+ :param kwargs: additional arguments
225
+ """
226
+ if source_format == Format.SQLDUMP_DUCKDB.value or source_format == Format.SQLDUMP_DUCKDB:
227
+ with self.engine.connect() as conn:
228
+ sql = text(f"IMPORT DATABASE '{location}'")
229
+ conn.execute(sql)
230
+ conn.commit()
231
+ else:
232
+ super().import_database(location, source_format=source_format, **kwargs)
@@ -1,7 +1,8 @@
1
1
  # mongodb_database.py
2
2
 
3
3
  import logging
4
- from typing import Optional
4
+ from pathlib import Path
5
+ from typing import Optional, Union
5
6
 
6
7
  from pymongo import MongoClient
7
8
  from pymongo.database import Database as NativeDatabase
@@ -9,6 +10,9 @@ from pymongo.database import Database as NativeDatabase
9
10
  from linkml_store.api import Database
10
11
  from linkml_store.api.queries import Query, QueryResult
11
12
  from linkml_store.api.stores.mongodb.mongodb_collection import MongoDBCollection
13
+ from linkml_store.utils.file_utils import safe_remove_directory
14
+ from linkml_store.utils.format_utils import Format
15
+ from linkml_store.utils.mongodb_utils import import_mongodb
12
16
 
13
17
  logger = logging.getLogger(__name__)
14
18
 
@@ -27,6 +31,8 @@ class MongoDBDatabase(Database):
27
31
  def __init__(self, handle: Optional[str] = None, **kwargs):
28
32
  if handle is None:
29
33
  handle = "mongodb://localhost:27017/test"
34
+ if handle == "mongodb":
35
+ handle = "mongodb://localhost:27017/temporary"
30
36
  super().__init__(handle=handle, **kwargs)
31
37
 
32
38
  @property
@@ -77,3 +83,27 @@ class MongoDBDatabase(Database):
77
83
  if collection_name not in self._collections:
78
84
  collection = MongoDBCollection(name=collection_name, parent=self)
79
85
  self._collections[collection_name] = collection
86
+
87
+ def export_database(self, location: str, target_format: Optional[Union[str, Format]] = None, **kwargs):
88
+ if target_format == Format.DUMP_MONGODB.value or target_format == Format.DUMP_MONGODB:
89
+ path = Path(location)
90
+ if path.exists():
91
+ safe_remove_directory(path, no_backup=True)
92
+ from linkml_store.utils.mongodb_utils import export_mongodb
93
+
94
+ export_mongodb(self.handle, location)
95
+ else:
96
+ super().export_database(location, target_format=target_format, **kwargs)
97
+
98
+ def import_database(self, location: str, source_format: Optional[str] = None, **kwargs):
99
+ """
100
+ Import a database from a file or location.
101
+
102
+ :param location: location of the file
103
+ :param source_format: source format
104
+ :param kwargs: additional arguments
105
+ """
106
+ if source_format == Format.DUMP_MONGODB.value or source_format == Format.DUMP_MONGODB:
107
+ import_mongodb(self.handle, location, drop=True)
108
+ else:
109
+ super().import_database(location, source_format=source_format, **kwargs)