bluecore-models 0.2.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.
@@ -0,0 +1,13 @@
1
+
2
+ from bluecore.models.base import Base
3
+ from bluecore.models.resource import ResourceBase
4
+ from bluecore.models.bf_classes import BibframeClass, ResourceBibframeClass
5
+ from bluecore.models.instance import Instance
6
+ from bluecore.models.version import Version
7
+ from bluecore.models.work import Work
8
+ from bluecore.models.other_resource import OtherResource, BibframeOtherResources
9
+
10
+
11
+
12
+
13
+
@@ -0,0 +1,3 @@
1
+ from sqlalchemy.orm import declarative_base
2
+
3
+ Base = declarative_base()
@@ -0,0 +1,49 @@
1
+ from datetime import datetime
2
+
3
+
4
+ from sqlalchemy import (
5
+ DateTime,
6
+ ForeignKey,
7
+ Integer,
8
+ String,
9
+ )
10
+
11
+ from sqlalchemy.orm import (
12
+ mapped_column,
13
+ Mapped,
14
+ relationship,
15
+ )
16
+ from bluecore.models.base import Base
17
+ from bluecore.models.resource import ResourceBase
18
+
19
+
20
+ class BibframeClass(Base):
21
+ __tablename__ = "bibframe_classes"
22
+
23
+ id: Mapped[int] = mapped_column(primary_key=True)
24
+ name: Mapped[str] = mapped_column(String, nullable=False)
25
+ uri: Mapped[str] = mapped_column(String, nullable=False, unique=True)
26
+ created_at = mapped_column(DateTime, default=datetime.utcnow)
27
+ updated_at = mapped_column(
28
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
29
+ )
30
+
31
+ def __repr__(self):
32
+ return f"<BibframeClass {self.name}>"
33
+
34
+
35
+ class ResourceBibframeClass(Base):
36
+ __tablename__ = "resource_bibframe_classes"
37
+
38
+ id: Mapped[int] = mapped_column(primary_key=True)
39
+ bf_class_id: Mapped[int] = mapped_column(
40
+ Integer, ForeignKey("bibframe_classes.id"), nullable=False
41
+ )
42
+ bf_class: Mapped[BibframeClass] = relationship("BibframeClass")
43
+ resource_id: Mapped[int] = mapped_column(
44
+ Integer, ForeignKey("resource_base.id"), nullable=False
45
+ )
46
+ resource: Mapped[ResourceBase] = relationship("ResourceBase", backref="classes")
47
+
48
+ def __repr__(self):
49
+ return f"<ResourceBibframeClass {self.bf_class.name} for {self.resource.uri}>"
@@ -0,0 +1,30 @@
1
+ from sqlalchemy import (
2
+ ForeignKey,
3
+ Integer,
4
+ )
5
+
6
+ from sqlalchemy.orm import (
7
+ mapped_column,
8
+ Mapped,
9
+ relationship,
10
+ )
11
+
12
+ from bluecore.models.resource import ResourceBase
13
+
14
+ class Instance(ResourceBase):
15
+ __tablename__ = "instances"
16
+
17
+ id: Mapped[int] = mapped_column(
18
+ Integer, ForeignKey("resource_base.id"), primary_key=True
19
+ )
20
+ work_id: Mapped[int] = mapped_column(Integer, ForeignKey("works.id"), nullable=True)
21
+ work: Mapped["Work"] = relationship(
22
+ "Work", foreign_keys=work_id, backref="instances"
23
+ )
24
+
25
+ __mapper_args__ = {
26
+ "polymorphic_identity": "instances",
27
+ }
28
+
29
+ def __repr__(self):
30
+ return f"<Instance {self.uri}>"
@@ -0,0 +1,66 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy import (
4
+ Boolean,
5
+ DateTime,
6
+ Integer,
7
+ ForeignKey
8
+ )
9
+
10
+ from sqlalchemy.orm import (
11
+ mapped_column,
12
+ Mapped,
13
+ relationship,
14
+ )
15
+
16
+ from bluecore.models.base import Base
17
+ from bluecore.models.resource import ResourceBase
18
+
19
+
20
+ class OtherResource(ResourceBase):
21
+ """
22
+ Stores JSON or JSON-LD resources used to support Work and Instances.
23
+ """
24
+
25
+ __tablename__ = "other_resources"
26
+ id: Mapped[int] = mapped_column(
27
+ Integer, ForeignKey("resource_base.id"), primary_key=True
28
+ )
29
+ is_profile: Mapped[bool] = mapped_column(Boolean, default=False)
30
+
31
+ __mapper_args__ = {
32
+ "polymorphic_identity": "other_resources",
33
+ }
34
+
35
+ def __repr__(self):
36
+ return f"<OtherResource {self.uri or self.id}>"
37
+
38
+
39
+ class BibframeOtherResources(Base):
40
+ """
41
+ Creates relationships between Work or Instance and supporting resources
42
+ """
43
+
44
+ __tablename__ = "bibframe_other_resources"
45
+
46
+ id: Mapped[int] = mapped_column(primary_key=True)
47
+ other_resource_id: Mapped[int] = mapped_column(
48
+ Integer, ForeignKey("other_resources.id"), nullable=False
49
+ )
50
+ other_resource: Mapped[OtherResource] = relationship(
51
+ "OtherResource", foreign_keys=other_resource_id
52
+ )
53
+ bibframe_resource_id: Mapped[int] = mapped_column(
54
+ Integer, ForeignKey("resource_base.id"), nullable=False
55
+ )
56
+ bibframe_resource: Mapped[ResourceBase] = relationship(
57
+ "ResourceBase", backref="other_resources"
58
+ )
59
+
60
+ created_at = mapped_column(DateTime, default=datetime.utcnow)
61
+ updated_at = mapped_column(
62
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
63
+ )
64
+
65
+ def __repr__(self):
66
+ return f"<BibframeOtherResources {self.other_resource.uri or self.other_resource.id} for {self.bibframe_resource.uri}>"
@@ -0,0 +1,32 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy import (
4
+ DateTime,
5
+ String,
6
+ )
7
+
8
+ from sqlalchemy.orm import (
9
+ mapped_column,
10
+ Mapped,
11
+ )
12
+
13
+ from sqlalchemy.dialects.postgresql import JSONB
14
+
15
+ from bluecore.models.base import Base
16
+
17
+ class ResourceBase(Base):
18
+ __tablename__ = "resource_base"
19
+
20
+ id: Mapped[int] = mapped_column(primary_key=True)
21
+ type: Mapped[str] = mapped_column(String, nullable=False)
22
+ data: Mapped[bytes] = mapped_column(JSONB, nullable=False)
23
+ uri: Mapped[str] = mapped_column(String, nullable=True, unique=True)
24
+ created_at = mapped_column(DateTime, default=datetime.utcnow)
25
+ updated_at = mapped_column(
26
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
27
+ )
28
+
29
+ __mapper_args__ = {
30
+ "polymorphic_on": type,
31
+ "polymorphic_identity": "resource_base",
32
+ }
@@ -0,0 +1,35 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy import (
4
+ DateTime,
5
+ Integer,
6
+ ForeignKey
7
+ )
8
+
9
+ from sqlalchemy.orm import (
10
+ mapped_column,
11
+ Mapped,
12
+ relationship,
13
+ )
14
+
15
+ from sqlalchemy.dialects.postgresql import JSONB
16
+
17
+ from bluecore.models.base import Base
18
+ from bluecore.models.resource import ResourceBase
19
+
20
+ class Version(Base):
21
+ __tablename__ = "versions"
22
+
23
+ id: Mapped[int] = mapped_column(primary_key=True)
24
+ resource_id: Mapped[int] = mapped_column(
25
+ Integer, ForeignKey("resource_base.id"), nullable=False
26
+ )
27
+ resource: Mapped[ResourceBase] = relationship("ResourceBase", backref="versions")
28
+ data: Mapped[bytes] = mapped_column(JSONB, nullable=False)
29
+ created_at = mapped_column(DateTime, default=datetime.utcnow)
30
+ updated_at = mapped_column(
31
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
32
+ )
33
+
34
+ def __repr__(self):
35
+ return f"<Version at {self.created_at} for {self.resource.uri}>"
@@ -0,0 +1,25 @@
1
+ from sqlalchemy import (
2
+ ForeignKey,
3
+ Integer,
4
+ )
5
+
6
+ from sqlalchemy.orm import (
7
+ mapped_column,
8
+ Mapped,
9
+ )
10
+
11
+ from bluecore.models.resource import ResourceBase
12
+
13
+
14
+ class Work(ResourceBase):
15
+ __tablename__ = "works"
16
+ id: Mapped[int] = mapped_column(
17
+ Integer, ForeignKey("resource_base.id"), primary_key=True
18
+ )
19
+
20
+ __mapper_args__ = {
21
+ "polymorphic_identity": "works",
22
+ }
23
+
24
+ def __repr__(self):
25
+ return f"<Work {self.uri}>"
@@ -0,0 +1,88 @@
1
+ """Utility functions for working with RDF graphs."""
2
+ import rdflib
3
+
4
+ BF = rdflib.Namespace("http://id.loc.gov/ontologies/bibframe/")
5
+ BFLC = rdflib.Namespace("http://id.loc.gov/ontologies/bflc/")
6
+ LCLOCAL = rdflib.Namespace("http://id.loc.gov/ontologies/lclocal/")
7
+ MADS = rdflib.Namespace("http://www.loc.gov/mads/rdf/v1#")
8
+
9
+ def init_graph() -> rdflib.Graph:
10
+ """Initialize a new RDF graph with the necessary namespaces."""
11
+ new_graph = rdflib.Graph()
12
+ new_graph.namespace_manager.bind("bf", BF)
13
+ new_graph.namespace_manager.bind("bflc", BFLC)
14
+ new_graph.namespace_manager.bind("mads", MADS)
15
+ new_graph.namespace_manager.bind("lclocal", LCLOCAL)
16
+ return new_graph
17
+
18
+
19
+ def _check_for_namespace(node: rdflib.URIRef) -> bool:
20
+ """Check if a node is in the LCLOCAL or DCTERMS namespace."""
21
+ return node in LCLOCAL or node in rdflib.DCTERMS
22
+
23
+
24
+ def _exclude_uri_from_other_resources(uri: rdflib.URIRef) -> bool:
25
+ """Checks if uri is in the BF, MADS, or RDF namespaces"""
26
+ return (
27
+ uri in BF or uri in MADS or uri in rdflib.RDF
28
+ )
29
+
30
+
31
+ def _expand_bnode(graph: rdflib.Graph, entity_graph: rdflib.Graph, bnode: rdflib.BNode):
32
+ """Expand a blank node in the entity graph."""
33
+ for pred, obj in graph.predicate_objects(subject=bnode):
34
+ if _check_for_namespace(pred) or _check_for_namespace(obj):
35
+ continue
36
+ entity_graph.add((bnode, pred, obj))
37
+ if isinstance(obj, rdflib.BNode):
38
+ _expand_bnode(graph, entity_graph, obj)
39
+
40
+
41
+ def _is_work_or_instance(uri: rdflib.URIRef, graph: rdflib.Graph) -> bool:
42
+ """Checks if uri is a BIBFRAME Work or Instance"""
43
+ for class_ in graph.objects(subject=uri, predicate=rdflib.RDF.type):
44
+ # In the future we may want to include Work and Instances subclasses
45
+ # maybe through inference
46
+ if class_ == BF.Work or class_ == BF.Instance:
47
+ return True
48
+ return False
49
+
50
+
51
+ def generate_entity_graph(graph: rdflib.Graph, entity: rdflib.URIRef) -> rdflib.Graph:
52
+ """Generate an entity graph from a larger RDF graph."""
53
+ entity_graph = init_graph()
54
+ for pred, obj in graph.predicate_objects(subject=entity):
55
+ if _check_for_namespace(pred) or _check_for_namespace(obj):
56
+ continue
57
+ entity_graph.add((entity, pred, obj))
58
+ if isinstance(obj, rdflib.BNode):
59
+ _expand_bnode(graph, entity_graph, obj)
60
+ return entity_graph
61
+
62
+
63
+ def generate_other_resources(record_graph: rdflib.Graph, entity_graph: rdflib.Graph) -> list:
64
+ """
65
+ Takes a Record Graph and Entity Graph and returns a list of dictionaries
66
+ where each dict contains the sub-graphs and URIs that referenced in the
67
+ entity graph and present in the record graph.
68
+ """
69
+ other_resources = []
70
+ for row in entity_graph.query("""
71
+ SELECT DISTINCT ?object
72
+ WHERE {
73
+ ?subject ?predicate ?object .
74
+ FILTER(isIRI(?object))
75
+ }
76
+ """):
77
+ uri = row[0]
78
+ if _exclude_uri_from_other_resources(uri) or _is_work_or_instance(uri, record_graph):
79
+ continue
80
+ other_resource_graph = generate_entity_graph(record_graph, uri)
81
+ if len(other_resource_graph) > 0:
82
+ other_resources.append(
83
+ {
84
+ "uri": str(uri),
85
+ "graph": other_resource_graph
86
+ }
87
+ )
88
+ return other_resources
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.2
2
+ Name: bluecore-models
3
+ Version: 0.2.0
4
+ Summary: Blue Core BIBFRAME Data Models
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: alembic>=1.14.1
8
+ Requires-Dist: psycopg2-binary>=2.9.10
9
+ Requires-Dist: rdflib>=7.1.3
10
+
11
+ # Blue Core Data Models
12
+ The Blue Core Data Models are used in [Blue Core API](https://github.com/blue-core-lod/bluecore_api)
13
+ and in the [Blue Core Workflows](https://github.com/blue-core-lod/bluecore-workflows) services.
14
+
15
+ ## Run Postgres with Docker
16
+ To run the Postgres with the Blue Core Database, run the following command from this directory:
17
+
18
+ `docker run --name bluecore_db -e POSTGRES_USER=bluecore_admin -e POSTGRES_PASSWORD=bluecore_admin -v ./create-db.sql:/docker-entrypoint-initdb.d/create_database.sql -p 5432:5432 postgres:17`
19
+
20
+ ## Installing
21
+ - Install via pip: `pip install blue-core-data-models`
22
+ - Install via uv: `uv add blue-core-data-models`
23
+
24
+ ## Database Management
25
+ The [SQLAlchemy](https://www.sqlalchemy.org/) Object Relational Mapper (ORM) is used to create
26
+ the Bluecore database models.
27
+
28
+ ```mermaid
29
+ erDiagram
30
+ ResourceBase ||--o{ Instance : "has"
31
+ ResourceBase ||--o{ Work : "has"
32
+ ResourceBase ||--o{ OtherResource : "has"
33
+ ResourceBase ||--o{ ResourceBibframeClass : "has classes"
34
+ ResourceBase ||--o{ Version : "has versions"
35
+ ResourceBase ||--o{ BibframeOtherResources : "has other resources"
36
+
37
+ Work ||--o{ Instance : "has"
38
+
39
+ BibframeClass ||--o{ ResourceBibframeClass : "classifies"
40
+
41
+ OtherResource ||--o{ BibframeOtherResources : "links to"
42
+ ```
43
+
44
+ ### Database Migrations with Alembic
45
+ The [Alembic](https://alembic.sqlalchemy.org/en/latest/) database migration package is used
46
+ to manage database changes with the Bluecore Data models.
47
+
48
+ To create a new migration, ensure that the Postgres database is available and then run:
49
+ - `uv run alembic revision --autogenerate -m "{short message describing change}`
50
+
51
+ A new migration script will be created in the `bluecore_store_migration` directory. Be sure
52
+ to add the new script to the repository with `git`.
53
+
54
+ #### Applying Migrations
55
+ To apply all of the migrations, run the following command:
56
+ - `uv run alembic upgrade head`
@@ -0,0 +1,13 @@
1
+ bluecore/models/__init__.py,sha256=N8woyAHr-TJw5fNy9OZOacgp-LpZA7U_ZC88vOtH4Ys,379
2
+ bluecore/models/base.py,sha256=kha9xmklzhuQAK8QEkNBn-mAHq8dUKbOM-3abaBpWmQ,71
3
+ bluecore/models/bf_classes.py,sha256=ex3ezo2d2OA1tlL8i_8JRXXehL3AuEX7NcFqKN4uSgg,1425
4
+ bluecore/models/instance.py,sha256=kFfNpyCD_sjnhWg50zvVBMZGSgzYhPEcrBq38xmRVUA,695
5
+ bluecore/models/other_resource.py,sha256=XWZLCP3RkNejh_kSeaBbjwXfFmN3HonpB1irVBmVXa0,1837
6
+ bluecore/models/resource.py,sha256=BtK5VIZ4fIJ4jUx7hG-R5rzftIvnohrf4RLBb8yBg1M,835
7
+ bluecore/models/version.py,sha256=H4Qy_9n4ZAWMk2R_gNYlY1O8kvMKnmD-cVy7D7zWwJQ,962
8
+ bluecore/models/work.py,sha256=7pFY3-YG_NY8gkwA7uNpSKbt4KKzf2LF-RqVgdqrXlc,467
9
+ bluecore/utils/graph.py,sha256=twfFVL4fmajbRWtHnArlwgESNVPAmKen8HOvimZNxVs,3371
10
+ bluecore_models-0.2.0.dist-info/METADATA,sha256=EqlrVbovHlwpySg8dzY8Xw1y2TVzSrl4Hj7aEty5MW8,2193
11
+ bluecore_models-0.2.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
12
+ bluecore_models-0.2.0.dist-info/top_level.txt,sha256=Vzof03mtU80tX4CgPYUVf__PE3y8CxEISf2XxaJYSfw,9
13
+ bluecore_models-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ bluecore