Skip to content

Commit

Permalink
Create record adjacency list relationship
Browse files Browse the repository at this point in the history
This adds DB-level support for parent-child record hierarchies, which will facilitate API development relating to HasPart/IsPartOf related identifiers.

API tests have been updated to create factory records without parents/children, effectively maintaining the existing API test behaviour. Tests will be reviewed and revised as appropriate with forthcoming record API changes.
  • Loading branch information
marksparkza committed Jul 19, 2023
1 parent c98fe84 commit fc45d0b
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 11 deletions.
Binary file modified ERD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Create parent-child record relationship
Revision ID: aa0153edf5a9
Revises: 48bf65c26f62
Create Date: 2023-07-19 13:58:52.518432
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'aa0153edf5a9'
down_revision = '48bf65c26f62'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - adjusted ###
op.add_column('record', sa.Column('parent_id', sa.String(), nullable=True))
op.create_foreign_key('record_parent_id_fkey', 'record', 'record', ['parent_id'], ['id'], ondelete='RESTRICT')
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - adjusted ###
op.drop_constraint('record_parent_id_fkey', 'record', type_='foreignkey')
op.drop_column('record', 'parent_id')
# ### end Alembic commands ###
2 changes: 1 addition & 1 deletion odp-core
Submodule odp-core updated 1 files
+1 −1 odp/version.py
7 changes: 6 additions & 1 deletion odp/db/models/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ class Record(Base):
schema_type = Column(Enum(SchemaType), nullable=False)
schema = relationship('Schema')

# parent-child relationship for HasPart/IsPartOf related identifiers
parent_id = Column(String, ForeignKey('record.id', ondelete='RESTRICT'))
parent = relationship('Record', remote_side=id)
children = relationship('Record', viewonly=True)

# view of associated tags (one-to-many)
tags = relationship('RecordTag', viewonly=True)

# view of associated catalog records (one-to-many)
catalog_records = relationship('CatalogRecord', viewonly=True)

_repr_ = 'id', 'doi', 'sid', 'collection_id', 'schema_id'
_repr_ = 'id', 'doi', 'sid', 'collection_id', 'schema_id', 'parent_id'


class RecordAudit(Base):
Expand Down
2 changes: 1 addition & 1 deletion test/api/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ def test_delete_collection(api, collection_batch, scopes, collection_auth, has_r
del modified_collection_batch[2]

if has_record:
RecordFactory(collection=collection_batch[2])
RecordFactory(collection=collection_batch[2], is_child_record=False)

r = api(scopes, api_client_collections).delete(f'/collection/{collection_batch[2].id}')

Expand Down
2 changes: 1 addition & 1 deletion test/api/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def test_delete_provider(api, provider_batch, scopes, has_record):

if has_record:
if collection := next((c for c in provider_batch[2].collections), None):
RecordFactory(collection=collection)
RecordFactory(collection=collection, is_child_record=False)
else:
has_record = False

Expand Down
7 changes: 4 additions & 3 deletions test/api/test_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def record_batch():
"""Create and commit a batch of Record instances."""
records = []
for _ in range(randint(3, 5)):
records += [record := RecordFactory()]
records += [record := RecordFactory(is_child_record=False)]
RecordTagFactory.create_batch(randint(0, 3), record=record)
CollectionTagFactory.create_batch(randint(0, 3), collection=record.collection)
return records
Expand All @@ -29,14 +29,14 @@ def record_batch():
def record_batch_no_tags():
"""Create and commit a batch of Record instances
without any tag instances."""
return [RecordFactory() for _ in range(randint(3, 5))]
return [RecordFactory(is_child_record=False) for _ in range(randint(3, 5))]


@pytest.fixture
def record_batch_with_ids():
"""Create and commit a batch of Record instances
with both DOIs and SIDs."""
return [RecordFactory(identifiers='both') for _ in range(randint(3, 5))]
return [RecordFactory(identifiers='both', is_child_record=False) for _ in range(randint(3, 5))]


def record_build(collection=None, collection_tags=None, **id):
Expand All @@ -46,6 +46,7 @@ def record_build(collection=None, collection_tags=None, **id):
**id,
collection=collection or (collection := CollectionFactory()),
collection_id=collection.id,
is_child_record=False,
)
if collection_tags:
for ct in collection_tags:
Expand Down
9 changes: 8 additions & 1 deletion test/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class Meta:
class RecordFactory(ODPModelFactory):
class Meta:
model = Record
exclude = ('identifiers',)
exclude = ('identifiers', 'is_child_record')

identifiers = factory.LazyFunction(lambda: choice(('doi', 'sid', 'both')))
doi = factory.LazyAttributeSequence(lambda r, n: f'10.5555/test-{n}' if r.identifiers in ('doi', 'both') else None)
Expand All @@ -238,6 +238,13 @@ class Meta:
SchemaFactory(id=r.schema_id, type='metadata'))
timestamp = factory.LazyFunction(lambda: datetime.now(timezone.utc))

is_child_record = factory.LazyFunction(lambda: randint(0, 1))
parent = factory.Maybe(
'is_child_record',
yes_declaration=factory.SubFactory('test.factories.RecordFactory', is_child_record=False),
no_declaration=None,
)


class RecordTagFactory(ODPModelFactory):
class Meta:
Expand Down
34 changes: 31 additions & 3 deletions test/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,37 @@ def test_create_provider():

def test_create_record():
record = RecordFactory()
result = Session.execute(select(Record)).scalar_one()
assert (result.id, result.doi, result.sid, result.metadata_, result.validity, result.collection_id, result.schema_id, result.schema_type) \
== (record.id, record.doi, record.sid, record.metadata_, record.validity, record.collection.id, record.schema.id, record.schema.type)
result = Session.execute(
select(Record).where(Record.id == record.id)
).scalar_one()
assert (
result.id,
result.doi,
result.sid,
result.metadata_,
result.validity,
result.collection_id,
result.schema_id,
result.schema_type,
result.parent_id,
) == (
record.id,
record.doi,
record.sid,
record.metadata_,
record.validity,
record.collection.id,
record.schema.id,
record.schema.type,
record.parent_id,
)
if record.parent_id:
parent = Session.execute(
select(Record).where(Record.id == record.parent_id)
).scalar_one()
assert result.parent == parent
assert result.parent_id == parent.id
assert parent.children == [result]


def test_create_record_tag():
Expand Down

0 comments on commit fc45d0b

Please sign in to comment.