Skip to content

Commit

Permalink
Merge pull request #483 from bento-platform/chore/id-patterns
Browse files Browse the repository at this point in the history
chore!: impose more lenient ID regex across routers/schemas
  • Loading branch information
davidlougheed committed Feb 8, 2024
2 parents 5a2d645 + b990eb3 commit e9002f5
Show file tree
Hide file tree
Showing 16 changed files with 61 additions and 23 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
python-version: ["3.10", "3.11"]
services:
postgres:
image: postgres:15
image: postgres:16
env:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
Expand All @@ -46,4 +46,6 @@ jobs:
export POSTGRES_USER="postgres" && export POSTGRES_PASSWORD="postgres" && export POSTGRES_PORT=5432
poetry run coverage run ./manage.py test
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
11 changes: 7 additions & 4 deletions chord_metadata_service/chord/tests/test_api_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,13 @@ def test_private_dataset_search_13(self):
self.assertEqual(r.status_code, status.HTTP_200_OK)
c = r.json()
self.assertEqual(len(c["results"]), 1) # 1 phenopacket that contains 2 matching biosamples
self.assertIn("biosample_id:1", [b["id"]
for phenopacket in c["results"]
for b in phenopacket["biosamples"]
])
self.assertIn(
"katsu.biosample_id:1",
[
b["id"]
for phenopacket in c["results"]
for b in phenopacket["biosamples"]
])

def test_private_dataset_search_values_list(self):
# Valid query to search for biosample id in list
Expand Down
2 changes: 2 additions & 0 deletions chord_metadata_service/experiments/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .models import Experiment, ExperimentResult
from .schemas import EXPERIMENT_SCHEMA
from .filters import ExperimentFilter, ExperimentResultFilter
from chord_metadata_service.restapi.constants import MODEL_ID_PATTERN
from chord_metadata_service.restapi.pagination import LargeResultsSetPagination, BatchResultsSetPagination


Expand Down Expand Up @@ -56,6 +57,7 @@ class ExperimentViewSet(viewsets.ModelViewSet):
renderer_classes = tuple(api_settings.DEFAULT_RENDERER_CLASSES)
filter_backends = [DjangoFilterBackend]
filterset_class = ExperimentFilter
lookup_value_regex = MODEL_ID_PATTERN

def dispatch(self, *args, **kwargs):
return super(ExperimentViewSet, self).dispatch(*args, **kwargs)
Expand Down
4 changes: 3 additions & 1 deletion chord_metadata_service/experiments/schemas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pathlib import Path
from .descriptions import EXPERIMENT, EXPERIMENT_RESULT, INSTRUMENT
from chord_metadata_service.restapi.constants import MODEL_ID_PATTERN
from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT
from chord_metadata_service.restapi.schema_utils import tag_ids_and_describe, get_schema_app_id, sub_schema_uri

Expand Down Expand Up @@ -134,7 +135,8 @@
"type": "object",
"properties": {
"id": {
"type": "string"
"type": "string",
"pattern": MODEL_ID_PATTERN,
},
"study_type": {
"type": "string",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"experiments": [
{
"id": "experiment:1",
"id": "katsu.experiment:1",
"biosample": "sample1",
"study_type": "Epigenomics",
"experiment_type": "WES",
Expand Down Expand Up @@ -69,7 +69,7 @@
}
},
{
"id": "experiment:2",
"id": "katsu.experiment:2",
"biosample": "sample8",
"study_type": "Epigenomics",
"experiment_type": "WES",
Expand Down
10 changes: 8 additions & 2 deletions chord_metadata_service/experiments/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ def test_get_experiments(self):
self.assertEqual(response_data["count"], 2)
self.assertEqual(len(response_data["results"]), 2)

def test_get_experiment_one(self):
response = self.client.get('/api/experiments/katsu.experiment:1')
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_data = response.json()
self.assertEqual(response_data['id'], 'katsu.experiment:1')

def test_get_experiment_schema(self):
response = self.client.get('/api/experiment_schema')
self.assertEqual(response.status_code, status.HTTP_200_OK)
Expand Down Expand Up @@ -148,11 +154,11 @@ def test_post_experiment_batch_no_data(self):
self.assertEqual(len(response.json()), 2)

def test_post_experiment_batch_with_ids(self):
response = self.client.post('/api/batch/experiments', {'id': ['experiment:1']}, format='json')
response = self.client.post('/api/batch/experiments', {'id': ['katsu.experiment:1']}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_data = response.json()
self.assertEqual(len(response_data), 1)
self.assertEqual(response_data[0]['id'], 'experiment:1')
self.assertEqual(response_data[0]['id'], 'katsu.experiment:1')


class TestExperimentCSVRenderer(TestCase):
Expand Down
2 changes: 2 additions & 0 deletions chord_metadata_service/patients/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
ARGORenderer,
IndividualBentoSearchRenderer,
)
from chord_metadata_service.restapi.constants import MODEL_ID_PATTERN
from chord_metadata_service.restapi.pagination import LargeResultsSetPagination, BatchResultsSetPagination
from chord_metadata_service.restapi.utils import (
get_field_options,
Expand Down Expand Up @@ -66,6 +67,7 @@ class IndividualViewSet(viewsets.ModelViewSet):
*(f"biosamples__{p}" for p in BIOSAMPLE_PREFETCH),
*(f"phenopackets__{p}" for p in PHENOPACKET_PREFETCH if p != "subject"),
).order_by("id")
lookup_value_regex = MODEL_ID_PATTERN

def list(self, request, *args, **kwargs):
if request.query_params.get("format") == OUTPUT_FORMAT_BENTO_SEARCH_RESULT:
Expand Down
20 changes: 17 additions & 3 deletions chord_metadata_service/patients/schemas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
from chord_metadata_service.restapi.schema_utils import DATE_TIME, DRAFT_07, SchemaTypes, array_of, base_type, \
enum_of, tag_ids_and_describe, get_schema_app_id, sub_schema_uri
from chord_metadata_service.restapi.constants import MODEL_ID_PATTERN
from chord_metadata_service.restapi.schema_utils import (
DATE_TIME,
DRAFT_07,
SchemaTypes,
array_of,
base_type,
string_with_pattern,
enum_of,
tag_ids_and_describe,
get_schema_app_id,
sub_schema_uri,
)
from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, EXTRA_PROPERTIES_SCHEMA, TIME_ELEMENT_SCHEMA
from pathlib import Path
from .descriptions import INDIVIDUAL, VITAL_STATUS
Expand Down Expand Up @@ -28,7 +39,10 @@
"type": "object",
"properties": {
# Phenopacket V2 Individual fields
"id": base_type(SchemaTypes.STRING, description="Unique researcher-specified identifier for the individual."),
"id": string_with_pattern(
MODEL_ID_PATTERN,
description="Unique researcher-specified identifier for the individual.",
),
"alternate_ids": array_of(base_type(SchemaTypes.STRING)),
"date_of_birth": DATE_TIME,
"time_at_last_encounter": TIME_ELEMENT_SCHEMA,
Expand Down
3 changes: 3 additions & 0 deletions chord_metadata_service/phenopackets/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from chord_metadata_service.restapi.api_renderers import (PhenopacketsRenderer, FHIRRenderer,
BiosamplesCSVRenderer, ARGORenderer,
IndividualBentoSearchRenderer)
from chord_metadata_service.restapi.constants import MODEL_ID_PATTERN
from chord_metadata_service.restapi.pagination import LargeResultsSetPagination, BatchResultsSetPagination
from chord_metadata_service.restapi.negociation import FormatInPostContentNegotiation
from chord_metadata_service.phenopackets.schemas import PHENOPACKET_SCHEMA
Expand Down Expand Up @@ -93,6 +94,7 @@ class BiosampleViewSet(ExtendedPhenopacketsModelViewSet):
filter_backends = [DjangoFilterBackend]
filterset_class = f.BiosampleFilter
queryset = m.Biosample.objects.all().prefetch_related(*BIOSAMPLE_PREFETCH).order_by("id")
lookup_value_regex = MODEL_ID_PATTERN


class BiosampleBatchViewSet(ExtendedPhenopacketsModelViewSet):
Expand Down Expand Up @@ -162,6 +164,7 @@ class PhenopacketViewSet(ExtendedPhenopacketsModelViewSet):
filter_backends = [DjangoFilterBackend]
filterset_class = f.PhenopacketFilter
queryset = m.Phenopacket.objects.all().prefetch_related(*PHENOPACKET_PREFETCH).order_by("id")
lookup_value_regex = MODEL_ID_PATTERN


class GenomicInterpretationViewSet(PhenopacketsModelViewSet):
Expand Down
12 changes: 7 additions & 5 deletions chord_metadata_service/phenopackets/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from referencing.jsonschema import DRAFT7
from chord_metadata_service.patients.schemas import INDIVIDUAL_SCHEMA
from chord_metadata_service.resources.schemas import RESOURCE_SCHEMA
from chord_metadata_service.restapi.constants import MODEL_ID_PATTERN
from chord_metadata_service.restapi.schemas import (
AGE,
AGE_RANGE,
Expand All @@ -19,6 +20,7 @@
SchemaTypes,
array_of,
base_type,
string_with_pattern,
enum_of,
named_one_of,
sub_schema_uri,
Expand Down Expand Up @@ -72,7 +74,7 @@
"title": "External reference schema",
"type": "object",
"properties": {
"id": base_type(SchemaTypes.STRING),
"id": string_with_pattern(MODEL_ID_PATTERN),
"reference": base_type(SchemaTypes.STRING),
"description": base_type(SchemaTypes.STRING)
}
Expand Down Expand Up @@ -277,9 +279,9 @@
"$id": sub_schema_uri(base_uri, "biosample"),
"type": "object",
"properties": {
"id": base_type(SchemaTypes.STRING),
"individual_id": base_type(SchemaTypes.STRING),
"derived_from_id": base_type(SchemaTypes.STRING),
"id": string_with_pattern(MODEL_ID_PATTERN),
"individual_id": string_with_pattern(MODEL_ID_PATTERN),
"derived_from_id": string_with_pattern(MODEL_ID_PATTERN),
"description": base_type(SchemaTypes.STRING),
"sampled_tissue": ONTOLOGY_CLASS,
"sample_type": ONTOLOGY_CLASS,
Expand Down Expand Up @@ -647,7 +649,7 @@
"description": "Schema for metadata service datasets",
"type": "object",
"properties": {
"id": base_type(SchemaTypes.STRING),
"id": string_with_pattern(MODEL_ID_PATTERN),
"subject": INDIVIDUAL_SCHEMA,
"phenotypic_features": array_of(PHENOPACKET_PHENOTYPIC_FEATURE_SCHEMA),
"measurements": array_of(PHENOPACKET_MEASUREMENT_SCHEMA),
Expand Down
2 changes: 1 addition & 1 deletion chord_metadata_service/phenopackets/tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ def valid_phenopacket(subject, meta_data):

def valid_biosample_1(individual, procedure=VALID_PROCEDURE_1):
return dict(
id='biosample_id:1',
id='katsu.biosample_id:1',
individual_id=individual,
description='This is a test biosample.',
sampled_tissue={
Expand Down
2 changes: 1 addition & 1 deletion chord_metadata_service/phenopackets/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def test_create_biosample(self):
response = get_post_response('biosamples-list', self.valid_payload)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(m.Biosample.objects.count(), 1)
self.assertEqual(m.Biosample.objects.get().id, 'biosample_id:1')
self.assertEqual(m.Biosample.objects.get().id, 'katsu.biosample_id:1')

def test_create_invalid_biosample(self):
""" POST a new biosample with invalid data. """
Expand Down
2 changes: 1 addition & 1 deletion chord_metadata_service/phenopackets/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_biosample(self):
tumor_progression__label='Primary Malignant Neoplasm',
sampled_tissue__label__icontains='urinary bladder'
)
self.assertEqual(biosample_one.id, 'biosample_id:1')
self.assertEqual(biosample_one.id, 'katsu.biosample_id:1')
self.assertEqual(biosample_one.schema_type, SchemaType.BIOSAMPLE)
self.assertEqual(biosample_one.get_project_id(), self.project.identifier)

Expand Down
1 change: 1 addition & 0 deletions chord_metadata_service/restapi/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MODEL_ID_PATTERN = r"[a-zA-Z0-9._\-:]+"
1 change: 1 addition & 0 deletions chord_metadata_service/restapi/schema_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"validation_schema_list",
"get_schema_app_id",
"base_type",
"string_with_pattern",
"array_of",
"enum_of",
"named_one_of",
Expand Down
2 changes: 1 addition & 1 deletion chord_metadata_service/restapi/tests/test_fhir.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def test_get_fhir(self):
'description')
self.assertIsNotNone(get_resp_obj['observations'][0]['specimen'])
self.assertIsInstance(get_resp_obj['observations'][0]['specimen'], dict)
self.assertEqual(get_resp_obj['observations'][0]['specimen']['reference'], 'biosample_id:1')
self.assertEqual(get_resp_obj['observations'][0]['specimen']['reference'], 'katsu.biosample_id:1')


class FHIRBiosampleTest(APITestCase):
Expand Down

0 comments on commit e9002f5

Please sign in to comment.