From dbed1805066d4fbedae6081f6cf54ef44c18aa47 Mon Sep 17 00:00:00 2001 From: shreddd Date: Wed, 11 Jun 2025 22:42:34 -0700 Subject: [PATCH 01/20] support docker based ingest --- docker-compose.yml | 15 +++++++++++++++ mongodb/README.md | 15 +++++++++++++++ src/server.py | 17 +++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 13dda78..4df879e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,21 @@ services: volumes: - mongo_data:/data/db + ingest: + # Use the same container image as the app service for consistency + build: { context: ".", dockerfile: Dockerfile, target: development } + # This service should not start automatically - only run on demand + profiles: ["tools"] + environment: + # Set the MongoDB connection string to connect to the mongo service + MONGO_URI: "mongodb://admin:root@mongo:27017" + volumes: + # Mount the root directory to access the ingest script and data files + - ".:/app" + depends_on: + - mongo + # Run ingest with data dir mounted to /data + command: ["uv", "run", "python", "/app/mongodb/ingest_data.py", "--mongo-uri", "mongodb://admin:root@mongo:27017", "--input", "/data"] volumes: # Define a named volume that will contain MongoDB data. diff --git a/mongodb/README.md b/mongodb/README.md index b043a73..7aae70e 100644 --- a/mongodb/README.md +++ b/mongodb/README.md @@ -12,6 +12,8 @@ This tool ingests BERtron-formatted data into MongoDB. ## Usage +### Local Python Usage + Run the ingest script with your data file: ```bash @@ -25,6 +27,19 @@ python ingest_data.py --input your_data_file.json - `--schema-path`: Path or URL to the schema JSON file (default: `bertron_schema.json` in the current directory) - `--input`: Path to input JSON file or directory containing JSON files (required) +### Using Docker Compose + +We have a ingester script in docker-compose that lets you run an ingest against a data directory + +```bash +# Start MongoDB and FASTAPI service +docker compose up + +# Pass in an ingest dir and run ingester +docker compose run --rm --volume /path/to/data:/data ingest +``` + + ## Data Format The input data should conform to the `bertron_schema.json` schema. It can be either: diff --git a/src/server.py b/src/server.py index 554984a..c790d99 100644 --- a/src/server.py +++ b/src/server.py @@ -30,6 +30,23 @@ def get_database_names(): db_names = mongo_client.list_database_names() return {"database_names": db_names} +@app.get("/database/{db_name}/{collection_name}") +def get_collection_data(db_name: str, collection_name: str): + r"""Get all documents from a specified collection in a specified database.""" + db = mongo_client[db_name] + collection = db[collection_name] + # Fetch all documents from the collection + if collection is None: + return {"error": "Collection not found"} + + documents = list(collection.find({})) + + # Remove the MongoDB '_id' field from each document for JSON serialization + for doc in documents: + doc.pop('_id', None) + + return {"documents": documents} + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) From b090ddc027bb0a0a43c5c1250da15a27f8a2481f Mon Sep 17 00:00:00 2001 From: shreddd Date: Wed, 11 Jun 2025 22:45:48 -0700 Subject: [PATCH 02/20] reformat w/ ruff --- src/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server.py b/src/server.py index c790d99..25aa961 100644 --- a/src/server.py +++ b/src/server.py @@ -30,6 +30,7 @@ def get_database_names(): db_names = mongo_client.list_database_names() return {"database_names": db_names} + @app.get("/database/{db_name}/{collection_name}") def get_collection_data(db_name: str, collection_name: str): r"""Get all documents from a specified collection in a specified database.""" @@ -38,12 +39,12 @@ def get_collection_data(db_name: str, collection_name: str): # Fetch all documents from the collection if collection is None: return {"error": "Collection not found"} - + documents = list(collection.find({})) # Remove the MongoDB '_id' field from each document for JSON serialization for doc in documents: - doc.pop('_id', None) + doc.pop("_id", None) return {"documents": documents} From cdb0c57ca65a7ce59b1c034d12e59a823ceb76fb Mon Sep 17 00:00:00 2001 From: Shreyas Cholia Date: Wed, 11 Jun 2025 22:49:40 -0700 Subject: [PATCH 03/20] Update src/server.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server.py b/src/server.py index 25aa961..34d4c74 100644 --- a/src/server.py +++ b/src/server.py @@ -35,11 +35,11 @@ def get_database_names(): def get_collection_data(db_name: str, collection_name: str): r"""Get all documents from a specified collection in a specified database.""" db = mongo_client[db_name] - collection = db[collection_name] - # Fetch all documents from the collection - if collection is None: + # Check if the collection exists in the database + if collection_name not in db.list_collection_names(): return {"error": "Collection not found"} + collection = db[collection_name] documents = list(collection.find({})) # Remove the MongoDB '_id' field from each document for JSON serialization From a7812427c1a710a81de1e720f123f55909d76d78 Mon Sep 17 00:00:00 2001 From: Shreyas Cholia Date: Thu, 12 Jun 2025 10:13:58 -0700 Subject: [PATCH 04/20] Update mongodb/README.md Co-authored-by: eecavanna <134325062+eecavanna@users.noreply.github.com> --- mongodb/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongodb/README.md b/mongodb/README.md index 7aae70e..78ae358 100644 --- a/mongodb/README.md +++ b/mongodb/README.md @@ -35,7 +35,7 @@ We have a ingester script in docker-compose that lets you run an ingest against # Start MongoDB and FASTAPI service docker compose up -# Pass in an ingest dir and run ingester +# Mount the directory whose contents you want to ingest, and run the ingester docker compose run --rm --volume /path/to/data:/data ingest ``` From 16527d1d2905539a1a8a1b52bd344aeaea7692a2 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 10:21:44 -0700 Subject: [PATCH 05/20] raise 404 --- src/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server.py b/src/server.py index 34d4c74..63e88aa 100644 --- a/src/server.py +++ b/src/server.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException import uvicorn from fastapi.responses import RedirectResponse from pymongo import MongoClient @@ -37,7 +37,7 @@ def get_collection_data(db_name: str, collection_name: str): db = mongo_client[db_name] # Check if the collection exists in the database if collection_name not in db.list_collection_names(): - return {"error": "Collection not found"} + raise HTTPException(status_code=404, detail="Collection not found") collection = db[collection_name] documents = list(collection.find({})) From 46931aa6abbc8bf3a4b4890b1ddd24da905ab848 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 14:21:15 -0700 Subject: [PATCH 06/20] Add API endpoints --- src/server.py | 255 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 238 insertions(+), 17 deletions(-) diff --git a/src/server.py b/src/server.py index 63e88aa..37875e1 100644 --- a/src/server.py +++ b/src/server.py @@ -1,7 +1,10 @@ -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, Query import uvicorn from fastapi.responses import RedirectResponse from pymongo import MongoClient +from typing import Optional, Dict, Any +from pydantic import BaseModel, Field +import json # Connect to MongoDB. # TODO: Get these values from environment variables instead of hard-coding them. @@ -23,23 +26,16 @@ def get_health(): return {"web_server": "ok", "database": is_database_healthy} -# TODO: Delete this endpoint once we're confident in our database connection. -@app.get("/experimental/database_names") -def get_database_names(): - r"""Get the names of all databases in the MongoDB server.""" - db_names = mongo_client.list_database_names() - return {"database_names": db_names} +@app.get("/bertron") +def get_all_entities(): + r"""Get all documents from the entities collection.""" + db = mongo_client["bertron"] + + # Check if the collection exists + if "entities" not in db.list_collection_names(): + raise HTTPException(status_code=404, detail="Entities collection not found") - -@app.get("/database/{db_name}/{collection_name}") -def get_collection_data(db_name: str, collection_name: str): - r"""Get all documents from a specified collection in a specified database.""" - db = mongo_client[db_name] - # Check if the collection exists in the database - if collection_name not in db.list_collection_names(): - raise HTTPException(status_code=404, detail="Collection not found") - - collection = db[collection_name] + collection = db["entities"] documents = list(collection.find({})) # Remove the MongoDB '_id' field from each document for JSON serialization @@ -49,5 +45,230 @@ def get_collection_data(db_name: str, collection_name: str): return {"documents": documents} +class MongoDBQuery(BaseModel): + filter: Dict[str, Any] = Field(default={}, description="MongoDB find query filter") + projection: Optional[Dict[str, Any]] = Field(default=None, description="Fields to include or exclude") + skip: Optional[int] = Field(default=0, ge=0, description="Number of documents to skip") + limit: Optional[int] = Field(default=100, ge=1, le=1000, description="Maximum number of documents to return") + sort: Optional[Dict[str, int]] = Field(default=None, description="Sort criteria (1 for ascending, -1 for descending)") + + +@app.post("/bertron/find") +def find_entities( + query: MongoDBQuery +): + r"""Execute a MongoDB find operation on the entities collection with filter, projection, skip, limit, and sort options. + + Example query body: + { + "filter": {"field": "value", "number_field": {"$gt": 100}}, + "projection": {"field1": 1, "field2": 1}, + "skip": 0, + "limit": 100, + "sort": {"field1": 1, "field2": -1} + } + """ + db = mongo_client["bertron"] + + # Check if the collection exists + if "entities" not in db.list_collection_names(): + raise HTTPException(status_code=404, detail="Entities collection not found") + + collection = db["entities"] + + try: + # Execute find with query parameters + cursor = collection.find( + filter=query.filter, + projection=query.projection + ) + + # Apply skip, limit, and sort if provided + if query.sort: + cursor = cursor.sort(list(query.sort.items())) + if query.skip: + cursor = cursor.skip(query.skip) + if query.limit: + cursor = cursor.limit(query.limit) + + # Convert cursor to list and remove MongoDB _id + documents = list(cursor) + for doc in documents: + doc.pop("_id", None) + + return {"documents": documents, "count": len(documents)} + + except Exception as e: + raise HTTPException(status_code=400, detail=f"Query error: {str(e)}") + + +@app.get("/bertron/geo/nearby") +def find_nearby_entities( + latitude: float = Query(..., ge=-90, le=90, description="Center latitude in degrees"), + longitude: float = Query(..., ge=-180, le=180, description="Center longitude in degrees"), + radius_meters: float = Query(..., gt=0, description="Search radius in meters") +): + r"""Find entities within a specified radius of a geographic point using MongoDB's $near operator. + + This endpoint uses MongoDB's geospatial $near query which requires a 2dsphere index + on the coordinates field for optimal performance. + + Example: /bertron/geo/nearby?latitude=47.6062&longitude=-122.3321&radius_meters=10000 + """ + db = mongo_client["bertron"] + + # Check if the collection exists + if "entities" not in db.list_collection_names(): + raise HTTPException(status_code=404, detail="Entities collection not found") + + collection = db["entities"] + + try: + # Build the $near geospatial query + geo_filter = { + "coordinates": { + "$near": { + "$geometry": { + "type": "Point", + "coordinates": [longitude, latitude] # MongoDB uses [lng, lat] format + }, + "$maxDistance": radius_meters + } + } + } + + # Execute find with geospatial filter and fixed projection + cursor = collection.find( + filter=geo_filter, + projection={"id": 1, "name": 1, "uri": 1, "ber_data_source": 1, "coordinates": 1} + ) + + # Convert cursor to list and remove MongoDB _id + documents = list(cursor) + for doc in documents: + doc.pop("_id", None) + + return { + "documents": documents, + "count": len(documents), + "query_type": "nearby", + "center": { + "latitude": latitude, + "longitude": longitude + }, + "radius_meters": radius_meters + } + + except Exception as e: + raise HTTPException(status_code=400, detail=f"Nearby query error: {str(e)}") + + +@app.get("/bertron/geo/bbox") +def find_entities_in_bounding_box( + southwest_lat: float = Query(..., ge=-90, le=90, description="Southwest corner latitude"), + southwest_lng: float = Query(..., ge=-180, le=180, description="Southwest corner longitude"), + northeast_lat: float = Query(..., ge=-90, le=90, description="Northeast corner latitude"), + northeast_lng: float = Query(..., ge=-180, le=180, description="Northeast corner longitude") +): + r"""Find entities within a bounding box using MongoDB's $geoWithin operator. + + This endpoint finds all entities whose coordinates fall within the specified + rectangular bounding box defined by southwest and northeast corners. + + Example: /bertron/geo/bbox?southwest_lat=47.5&southwest_lng=-122.4&northeast_lat=47.7&northeast_lng=-122.2 + """ + db = mongo_client["bertron"] + + # Check if the collection exists + if "entities" not in db.list_collection_names(): + raise HTTPException(status_code=404, detail="Entities collection not found") + + collection = db["entities"] + + try: + # Validate bounding box coordinates + if southwest_lat >= northeast_lat: + raise HTTPException(status_code=400, detail="Southwest latitude must be less than northeast latitude") + if southwest_lng >= northeast_lng: + raise HTTPException(status_code=400, detail="Southwest longitude must be less than northeast longitude") + + # Build the $geoWithin bounding box query + geo_filter = { + "coordinates": { + "$geoWithin": { + "$box": [ + [southwest_lng, southwest_lat], # MongoDB uses [lng, lat] format + [northeast_lng, northeast_lat] + ] + } + } + } + + # Execute find with geospatial filter and fixed projection + cursor = collection.find( + filter=geo_filter, + projection={"id": 1, "name": 1, "uri": 1, "ber_data_source": 1, "coordinates": 1} + ) + + # Convert cursor to list and remove MongoDB _id + documents = list(cursor) + for doc in documents: + doc.pop("_id", None) + + return { + "documents": documents, + "count": len(documents), + "query_type": "bounding_box", + "bounding_box": { + "southwest": { + "latitude": southwest_lat, + "longitude": southwest_lng + }, + "northeast": { + "latitude": northeast_lat, + "longitude": northeast_lng + } + } + } + + except Exception as e: + raise HTTPException(status_code=400, detail=f"Bounding box query error: {str(e)}") + + +@app.get("/bertron/{id}") +def get_entity_by_id(id: str): + r"""Get a single entity by its ID. + + Example: /bertron/emsl:12345 + """ + db = mongo_client["bertron"] + + # Check if the collection exists + if "entities" not in db.list_collection_names(): + raise HTTPException(status_code=404, detail="Entities collection not found") + + collection = db["entities"] + + try: + # Find the entity by ID with fixed projection + document = collection.find_one( + filter={"id": id}, + ) + + if not document: + raise HTTPException(status_code=404, detail=f"Entity with id '{id}' not found") + + # Remove MongoDB _id + document.pop("_id", None) + + return document + + except HTTPException: + # Re-raise HTTP exceptions + raise + except Exception as e: + raise HTTPException(status_code=400, detail=f"Query error: {str(e)}") + + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) From 28f9bafd7c475297f84d602f9169d91fcde376e5 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 14:35:52 -0700 Subject: [PATCH 07/20] ruff formatting --- src/server.py | 187 +++++++++++++++++++++++++++++--------------------- 1 file changed, 110 insertions(+), 77 deletions(-) diff --git a/src/server.py b/src/server.py index 37875e1..34bf05b 100644 --- a/src/server.py +++ b/src/server.py @@ -4,7 +4,6 @@ from pymongo import MongoClient from typing import Optional, Dict, Any from pydantic import BaseModel, Field -import json # Connect to MongoDB. # TODO: Get these values from environment variables instead of hard-coding them. @@ -30,7 +29,7 @@ def get_health(): def get_all_entities(): r"""Get all documents from the entities collection.""" db = mongo_client["bertron"] - + # Check if the collection exists if "entities" not in db.list_collection_names(): raise HTTPException(status_code=404, detail="Entities collection not found") @@ -47,18 +46,24 @@ def get_all_entities(): class MongoDBQuery(BaseModel): filter: Dict[str, Any] = Field(default={}, description="MongoDB find query filter") - projection: Optional[Dict[str, Any]] = Field(default=None, description="Fields to include or exclude") - skip: Optional[int] = Field(default=0, ge=0, description="Number of documents to skip") - limit: Optional[int] = Field(default=100, ge=1, le=1000, description="Maximum number of documents to return") - sort: Optional[Dict[str, int]] = Field(default=None, description="Sort criteria (1 for ascending, -1 for descending)") + projection: Optional[Dict[str, Any]] = Field( + default=None, description="Fields to include or exclude" + ) + skip: Optional[int] = Field( + default=0, ge=0, description="Number of documents to skip" + ) + limit: Optional[int] = Field( + default=100, ge=1, le=1000, description="Maximum number of documents to return" + ) + sort: Optional[Dict[str, int]] = Field( + default=None, description="Sort criteria (1 for ascending, -1 for descending)" + ) @app.post("/bertron/find") -def find_entities( - query: MongoDBQuery -): +def find_entities(query: MongoDBQuery): r"""Execute a MongoDB find operation on the entities collection with filter, projection, skip, limit, and sort options. - + Example query body: { "filter": {"field": "value", "number_field": {"$gt": 100}}, @@ -69,20 +74,17 @@ def find_entities( } """ db = mongo_client["bertron"] - + # Check if the collection exists if "entities" not in db.list_collection_names(): raise HTTPException(status_code=404, detail="Entities collection not found") collection = db["entities"] - + try: # Execute find with query parameters - cursor = collection.find( - filter=query.filter, - projection=query.projection - ) - + cursor = collection.find(filter=query.filter, projection=query.projection) + # Apply skip, limit, and sort if provided if query.sort: cursor = cursor.sort(list(query.sort.items())) @@ -90,39 +92,43 @@ def find_entities( cursor = cursor.skip(query.skip) if query.limit: cursor = cursor.limit(query.limit) - + # Convert cursor to list and remove MongoDB _id documents = list(cursor) for doc in documents: doc.pop("_id", None) - + return {"documents": documents, "count": len(documents)} - + except Exception as e: raise HTTPException(status_code=400, detail=f"Query error: {str(e)}") @app.get("/bertron/geo/nearby") def find_nearby_entities( - latitude: float = Query(..., ge=-90, le=90, description="Center latitude in degrees"), - longitude: float = Query(..., ge=-180, le=180, description="Center longitude in degrees"), - radius_meters: float = Query(..., gt=0, description="Search radius in meters") + latitude: float = Query( + ..., ge=-90, le=90, description="Center latitude in degrees" + ), + longitude: float = Query( + ..., ge=-180, le=180, description="Center longitude in degrees" + ), + radius_meters: float = Query(..., gt=0, description="Search radius in meters"), ): r"""Find entities within a specified radius of a geographic point using MongoDB's $near operator. - - This endpoint uses MongoDB's geospatial $near query which requires a 2dsphere index + + This endpoint uses MongoDB's geospatial $near query which requires a 2dsphere index on the coordinates field for optimal performance. - + Example: /bertron/geo/nearby?latitude=47.6062&longitude=-122.3321&radius_meters=10000 """ db = mongo_client["bertron"] - + # Check if the collection exists if "entities" not in db.list_collection_names(): raise HTTPException(status_code=404, detail="Entities collection not found") collection = db["entities"] - + try: # Build the $near geospatial query geo_filter = { @@ -130,139 +136,166 @@ def find_nearby_entities( "$near": { "$geometry": { "type": "Point", - "coordinates": [longitude, latitude] # MongoDB uses [lng, lat] format + "coordinates": [ + longitude, + latitude, + ], # MongoDB uses [lng, lat] format }, - "$maxDistance": radius_meters + "$maxDistance": radius_meters, } } } - + # Execute find with geospatial filter and fixed projection cursor = collection.find( filter=geo_filter, - projection={"id": 1, "name": 1, "uri": 1, "ber_data_source": 1, "coordinates": 1} + projection={ + "id": 1, + "name": 1, + "uri": 1, + "ber_data_source": 1, + "coordinates": 1, + }, ) - + # Convert cursor to list and remove MongoDB _id documents = list(cursor) for doc in documents: doc.pop("_id", None) - + return { "documents": documents, "count": len(documents), "query_type": "nearby", - "center": { - "latitude": latitude, - "longitude": longitude - }, - "radius_meters": radius_meters + "center": {"latitude": latitude, "longitude": longitude}, + "radius_meters": radius_meters, } - + except Exception as e: raise HTTPException(status_code=400, detail=f"Nearby query error: {str(e)}") @app.get("/bertron/geo/bbox") def find_entities_in_bounding_box( - southwest_lat: float = Query(..., ge=-90, le=90, description="Southwest corner latitude"), - southwest_lng: float = Query(..., ge=-180, le=180, description="Southwest corner longitude"), - northeast_lat: float = Query(..., ge=-90, le=90, description="Northeast corner latitude"), - northeast_lng: float = Query(..., ge=-180, le=180, description="Northeast corner longitude") + southwest_lat: float = Query( + ..., ge=-90, le=90, description="Southwest corner latitude" + ), + southwest_lng: float = Query( + ..., ge=-180, le=180, description="Southwest corner longitude" + ), + northeast_lat: float = Query( + ..., ge=-90, le=90, description="Northeast corner latitude" + ), + northeast_lng: float = Query( + ..., ge=-180, le=180, description="Northeast corner longitude" + ), ): r"""Find entities within a bounding box using MongoDB's $geoWithin operator. - - This endpoint finds all entities whose coordinates fall within the specified + + This endpoint finds all entities whose coordinates fall within the specified rectangular bounding box defined by southwest and northeast corners. - + Example: /bertron/geo/bbox?southwest_lat=47.5&southwest_lng=-122.4&northeast_lat=47.7&northeast_lng=-122.2 """ db = mongo_client["bertron"] - + # Check if the collection exists if "entities" not in db.list_collection_names(): raise HTTPException(status_code=404, detail="Entities collection not found") collection = db["entities"] - + try: # Validate bounding box coordinates if southwest_lat >= northeast_lat: - raise HTTPException(status_code=400, detail="Southwest latitude must be less than northeast latitude") + raise HTTPException( + status_code=400, + detail="Southwest latitude must be less than northeast latitude", + ) if southwest_lng >= northeast_lng: - raise HTTPException(status_code=400, detail="Southwest longitude must be less than northeast longitude") - + raise HTTPException( + status_code=400, + detail="Southwest longitude must be less than northeast longitude", + ) + # Build the $geoWithin bounding box query geo_filter = { "coordinates": { "$geoWithin": { "$box": [ - [southwest_lng, southwest_lat], # MongoDB uses [lng, lat] format - [northeast_lng, northeast_lat] + [ + southwest_lng, + southwest_lat, + ], # MongoDB uses [lng, lat] format + [northeast_lng, northeast_lat], ] } } } - + # Execute find with geospatial filter and fixed projection cursor = collection.find( filter=geo_filter, - projection={"id": 1, "name": 1, "uri": 1, "ber_data_source": 1, "coordinates": 1} + projection={ + "id": 1, + "name": 1, + "uri": 1, + "ber_data_source": 1, + "coordinates": 1, + }, ) - + # Convert cursor to list and remove MongoDB _id documents = list(cursor) for doc in documents: doc.pop("_id", None) - + return { "documents": documents, "count": len(documents), "query_type": "bounding_box", "bounding_box": { - "southwest": { - "latitude": southwest_lat, - "longitude": southwest_lng - }, - "northeast": { - "latitude": northeast_lat, - "longitude": northeast_lng - } - } + "southwest": {"latitude": southwest_lat, "longitude": southwest_lng}, + "northeast": {"latitude": northeast_lat, "longitude": northeast_lng}, + }, } - + except Exception as e: - raise HTTPException(status_code=400, detail=f"Bounding box query error: {str(e)}") + raise HTTPException( + status_code=400, detail=f"Bounding box query error: {str(e)}" + ) @app.get("/bertron/{id}") def get_entity_by_id(id: str): r"""Get a single entity by its ID. - + Example: /bertron/emsl:12345 """ db = mongo_client["bertron"] - + # Check if the collection exists if "entities" not in db.list_collection_names(): raise HTTPException(status_code=404, detail="Entities collection not found") collection = db["entities"] - + try: # Find the entity by ID with fixed projection document = collection.find_one( filter={"id": id}, ) - + if not document: - raise HTTPException(status_code=404, detail=f"Entity with id '{id}' not found") - + raise HTTPException( + status_code=404, detail=f"Entity with id '{id}' not found" + ) + # Remove MongoDB _id document.pop("_id", None) - + return document - + except HTTPException: # Re-raise HTTP exceptions raise From 6be38532cd6aac105830abcef976513e8138c607 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 14:44:13 -0700 Subject: [PATCH 08/20] fix merge conflict --- docker-compose.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6d96f8d..06811e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,8 +50,6 @@ services: - mongo # Run ingest with data dir mounted to /data command: ["uv", "run", "python", "/app/mongodb/ingest_data.py", "--mongo-uri", "mongodb://admin:root@mongo:27017", "--input", "/data"] -<<<<<<< HEAD -======= test: # Use the same container image as the app service for consistency @@ -62,7 +60,6 @@ services: - app - mongo command: ["uv", "run", "pytest", "-v"] ->>>>>>> main volumes: # Define a named volume that will contain MongoDB data. From aaba9286aee33f543f86b3ab873e6f46e95be866 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 17:20:14 -0700 Subject: [PATCH 09/20] Add support for pydantic schema model --- docker-compose.yml | 2 +- mongodb/gold-example.json | 34 ---------------------------------- mongodb/ingest_data.py | 25 +++++++++++++++++++++++-- src/server.py | 33 ++++++++++++++++++++++++++------- 4 files changed, 50 insertions(+), 44 deletions(-) delete mode 100644 mongodb/gold-example.json diff --git a/docker-compose.yml b/docker-compose.yml index 06811e5..febf6bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,7 +49,7 @@ services: depends_on: - mongo # Run ingest with data dir mounted to /data - command: ["uv", "run", "python", "/app/mongodb/ingest_data.py", "--mongo-uri", "mongodb://admin:root@mongo:27017", "--input", "/data"] + command: ["uv", "run", "python", "/app/mongodb/ingest_data.py", "--mongo-uri", "mongodb://admin:root@mongo:27017", "--input", "/data", "--clean"] test: # Use the same container image as the app service for consistency diff --git a/mongodb/gold-example.json b/mongodb/gold-example.json deleted file mode 100644 index e052837..0000000 --- a/mongodb/gold-example.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "ber_data_source": "JGI", - "coordinates": { - "latitude": 44.7523206, - "longitude": -110.7253926, - "altitude": null, - "depth": null, - "elevation": { - "has_numeric_value": 2280, - "has_unit": "meter (UO:0000008)" - } - }, - "data_type": [ - "jgi_biosample" - ], - "description": "Small acidic pool on hillside north of Nymph Lake.", - "id": "Gb0051341", - "name": "Hot spring microbial communities from Yellowstone National Park, Wyoming, USA - YNP2 Nymph Lake 10", - "alt_ids": [ - "NCBITaxon:433727" - ], - "alt_names": [ - { - "name": "GOLD biosample ID Gb0051341", - "name_type": "exact_synonym" - }, - { - "name": "hot springs metagenome", - "name_type": "broad_synonym" - } - ], - "part_of_collection": [], - "uri": "https://gold.jgi.doe.gov/biosample?id=Gb0051341" -} diff --git a/mongodb/ingest_data.py b/mongodb/ingest_data.py index c0f3730..e71f852 100644 --- a/mongodb/ingest_data.py +++ b/mongodb/ingest_data.py @@ -44,6 +44,20 @@ def connect(self) -> None: logger.error(f"Failed to connect to MongoDB: {e}") sys.exit(1) + def clean_collections(self) -> None: + """Delete existing collections to start fresh.""" + try: + collection_names = self.db.list_collection_names() + if 'entities' in collection_names: + logger.info("Dropping existing 'entities' collection") + self.db.entities.drop() + logger.info("Successfully dropped 'entities' collection") + else: + logger.info("No existing 'entities' collection found") + except PyMongoError as e: + logger.error(f"Error dropping collections: {e}") + sys.exit(1) + def load_schema(self) -> Dict: """Load the JSON schema from file.""" try: @@ -82,7 +96,7 @@ def insert_entity(self, entity: Dict) -> Optional[str]: if 'coordinates' in entity: coordinates = entity['coordinates'] if isinstance(coordinates, dict) and 'latitude' in coordinates and 'longitude' in coordinates: - entity['coordinates'] = { + entity['geojson'] = { 'type': 'Point', 'coordinates': [coordinates['longitude'], coordinates['latitude']] } @@ -97,7 +111,7 @@ def insert_entity(self, entity: Dict) -> Optional[str]: self.db.entities.create_index('data_type') # Create 2dsphere index for geospatial queries on coordinates - self.db.entities.create_index([('coordinates', pymongo.GEOSPHERE)]) + self.db.entities.create_index([('geojson', pymongo.GEOSPHERE)]) # Insert with upsert to handle potential duplicates based on URI result = self.db.entities.update_one( @@ -167,6 +181,8 @@ def main(): help='Path or URL to the BERtron schema JSON file') parser.add_argument('--input', required=True, help='Path to the input JSON file or directory') + parser.add_argument('--clean', action='store_true', + help='Delete existing collections before ingesting new data') args = parser.parse_args() @@ -180,6 +196,11 @@ def main(): ingestor.connect() ingestor.load_schema() + # Clean collections if requested + if args.clean: + logger.info("Clean flag enabled - removing existing collections") + ingestor.clean_collections() + total_stats = { 'processed': 0, 'valid': 0, diff --git a/src/server.py b/src/server.py index 34bf05b..e173b55 100644 --- a/src/server.py +++ b/src/server.py @@ -4,6 +4,11 @@ from pymongo import MongoClient from typing import Optional, Dict, Any from pydantic import BaseModel, Field +import bertron_schema_pydantic +import logging + +# Set up logging +logger = logging.getLogger(__name__) # Connect to MongoDB. # TODO: Get these values from environment variables instead of hard-coding them. @@ -132,7 +137,7 @@ def find_nearby_entities( try: # Build the $near geospatial query geo_filter = { - "coordinates": { + "geojson": { "$near": { "$geometry": { "type": "Point", @@ -220,7 +225,7 @@ def find_entities_in_bounding_box( # Build the $geoWithin bounding box query geo_filter = { - "coordinates": { + "geojson": { "$geoWithin": { "$box": [ [ @@ -266,7 +271,7 @@ def find_entities_in_bounding_box( ) -@app.get("/bertron/{id}") +@app.get("/bertron/{id}", response_model=bertron_schema_pydantic.Entity) def get_entity_by_id(id: str): r"""Get a single entity by its ID. @@ -281,9 +286,9 @@ def get_entity_by_id(id: str): collection = db["entities"] try: - # Find the entity by ID with fixed projection + # Find the entity by ID - get all fields for proper validation document = collection.find_one( - filter={"id": id}, + filter={"id": id} ) if not document: @@ -293,8 +298,22 @@ def get_entity_by_id(id: str): # Remove MongoDB _id document.pop("_id", None) - - return document + + # Remove metadata added during ingestion if present + document.pop("_metadata", None) + document.pop("geojson", None) + + + # Validate and create Entity instance + try: + entity = bertron_schema_pydantic.Entity(**document) + return entity + except Exception as validation_error: + logger.error(f"Entity validation failed for id '{id}': {validation_error}") + raise HTTPException( + status_code=500, + detail=f"Entity data validation failed: {str(validation_error)}" + ) except HTTPException: # Re-raise HTTP exceptions From c99491cb4ffabadaa6f0ebd261e86da582972a09 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 17:20:32 -0700 Subject: [PATCH 10/20] Add schema file --- src/bertron_schema_pydantic.py | 335 +++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 src/bertron_schema_pydantic.py diff --git a/src/bertron_schema_pydantic.py b/src/bertron_schema_pydantic.py new file mode 100644 index 0000000..e1aefe7 --- /dev/null +++ b/src/bertron_schema_pydantic.py @@ -0,0 +1,335 @@ +from __future__ import annotations + +import re +import sys +from datetime import ( + date, + datetime, + time +) +from decimal import Decimal +from enum import Enum +from typing import ( + Any, + ClassVar, + Literal, + Optional, + Union +) + +from pydantic import ( + BaseModel, + ConfigDict, + Field, + RootModel, + field_validator +) + + +metamodel_version = "None" +version = "0.0.2" + + +class ConfiguredBaseModel(BaseModel): + model_config = ConfigDict( + validate_assignment = True, + validate_default = True, + extra = "forbid", + arbitrary_types_allowed = True, + use_enum_values = True, + strict = False, + ) + pass + + + + +class LinkMLMeta(RootModel): + root: dict[str, Any] = {} + model_config = ConfigDict(frozen=True) + + def __getattr__(self, key:str): + return getattr(self.root, key) + + def __getitem__(self, key:str): + return self.root[key] + + def __setitem__(self, key:str, value): + self.root[key] = value + + def __contains__(self, key:str) -> bool: + return key in self.root + + +linkml_meta = LinkMLMeta({'default_curi_maps': ['semweb_context'], + 'default_prefix': 'bertron', + 'default_range': 'string', + 'description': 'Schema for BERtron common data model.', + 'id': 'https://w3id.org/ber-data/bertron-schema', + 'imports': ['linkml:types', 'bertron_types'], + 'license': 'BSD-3', + 'name': 'bertron-schema', + 'prefixes': {'MIXS': {'prefix_prefix': 'MIXS', + 'prefix_reference': 'https://w3id.org/mixs/'}, + 'UO': {'prefix_prefix': 'UO', + 'prefix_reference': 'http://purl.obolibrary.org/obo/UO_'}, + 'WGS84': {'prefix_prefix': 'WGS84', + 'prefix_reference': 'http://www.w3.org/2003/01/geo/wgs84_pos#'}, + 'bertron': {'prefix_prefix': 'bertron', + 'prefix_reference': 'https://w3id.org/ber-data/bertron-schema/'}, + 'linkml': {'prefix_prefix': 'linkml', + 'prefix_reference': 'https://w3id.org/linkml/'}, + 'schema': {'prefix_prefix': 'schema', + 'prefix_reference': 'http://schema.org/'}}, + 'see_also': ['https://ber-data.github.io/bertron-schema'], + 'source_file': 'src/schema/linkml/bertron_schema.yaml', + 'title': 'BERtron schema'} ) + +class BERSourceType(str, Enum): + """ + The BER data source from whence the entity originated. + """ + EMSL = "EMSL" + ESS_DIVE = "ESS-DIVE" + JGI = "JGI" + MONET = "MONET" + NMDC = "NMDC" + + +class EntityType(str, Enum): + """ + Tags used to describe an entity. + """ + biodata = "biodata" + jgi_biosample = "jgi_biosample" + sample = "sample" + sequence = "sequence" + taxon = "taxon" + + +class NameType(str, Enum): + """ + The relationship between a name and a synonym of that name. + """ + broad_synonym = "broad_synonym" + """ + The synonym refers to a broader group of entities than the name. + """ + exact_synonym = "exact_synonym" + """ + String with exactly the same meaning and connotations as the original name. + """ + narrow_synonym = "narrow_synonym" + """ + The synonym refers to a narrower group of entities than the name. + """ + related_synonym = "related_synonym" + """ + The synonym has overlap with the name but the precise relationship is not defined. + """ + acronym = "acronym" + """ + An acronym or abbreviation for the name. + """ + + + +class AttributeValue(ConfiguredBaseModel): + """ + The value for any value of a attribute for a sample. This object can hold both the un-normalized atomic value and the structured value + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'abstract': True, + 'class_uri': 'nmdc:AttributeValue', + 'from_schema': 'https://w3id.org/ber-data/bertron_types'}) + + has_raw_value: Optional[str] = Field(default=None, description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', + 'domain_of': ['AttributeValue', 'QuantityValue'], + 'mappings': ['nmdc:has_raw_value']} }) + + +class QuantityValue(AttributeValue): + """ + A simple quantity, e.g. 2cm + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'nmdc:QuantityValue', + 'from_schema': 'https://w3id.org/ber-data/bertron_types', + 'mappings': ['schema:QuantityValue'], + 'slot_usage': {'has_numeric_value': {'description': 'The number part of the ' + 'quantity', + 'name': 'has_numeric_value'}, + 'has_raw_value': {'description': 'Unnormalized atomic string ' + 'representation, should in ' + 'syntax {number} {unit}', + 'name': 'has_raw_value'}, + 'has_unit': {'description': 'The unit of the quantity', + 'name': 'has_unit'}}}) + + has_maximum_numeric_value: Optional[float] = Field(default=None, description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_maximum_numeric_value', + 'domain_of': ['QuantityValue'], + 'is_a': 'has_numeric_value', + 'mappings': ['nmdc:has_maximum_numeric_value']} }) + has_minimum_numeric_value: Optional[float] = Field(default=None, description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_minimum_numeric_value', + 'domain_of': ['QuantityValue'], + 'is_a': 'has_numeric_value', + 'mappings': ['nmdc:has_minimum_numeric_value']} }) + has_numeric_value: Optional[float] = Field(default=None, description="""The number part of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_numeric_value', + 'domain_of': ['QuantityValue'], + 'mappings': ['nmdc:has_numeric_value', 'qud:quantityValue', 'schema:value']} }) + has_raw_value: Optional[str] = Field(default=None, description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', + 'domain_of': ['AttributeValue', 'QuantityValue'], + 'mappings': ['nmdc:has_raw_value']} }) + has_unit: Optional[str] = Field(default=None, description="""The unit of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_unit', + 'aliases': ['scale'], + 'domain_of': ['QuantityValue'], + 'mappings': ['nmdc:has_unit', 'qud:unit', 'schema:unitCode']} }) + + +class Entity(ConfiguredBaseModel): + """ + An object retrieved by BERtron from a BER data API. + + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'schema:Thing', + 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) + + ber_data_source: BERSourceType = Field(default=..., description="""The BER member from whence the entity originated.""", json_schema_extra = { "linkml_meta": {'alias': 'ber_data_source', 'domain_of': ['Entity']} }) + coordinates: Coordinates = Field(default=..., description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", json_schema_extra = { "linkml_meta": {'alias': 'coordinates', 'domain_of': ['Entity']} }) + entity_type: list[EntityType] = Field(default=..., description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", json_schema_extra = { "linkml_meta": {'alias': 'entity_type', 'domain_of': ['Entity']} }) + description: Optional[str] = Field(default=None, description="""Textual description of the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'description', + 'domain_of': ['Entity', 'DataCollection'], + 'examples': [{'value': 'River water sample taken by AquaTROLL 9000.'}, + {'value': 'Genome sequence of P. aeruginosa strain IDDQD'}], + 'slot_uri': 'schema:description'} }) + id: Optional[str] = Field(default=None, description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', + 'aliases': ['BER data source internal identifier', 'CURIE'], + 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' + 'that IDs will be unique between all the BER sources.'], + 'domain_of': ['Entity', 'DataCollection'], + 'slot_uri': 'schema:identifier'} }) + name: Optional[str] = Field(default=None, description="""Human-readable string representing an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['Entity', 'Name'], + 'examples': [{'value': 'Pseudomonas aeruginosa strain IDDQD'}, + {'value': 'Soil core FW-106'}], + 'slot_uri': 'schema:name'} }) + alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', + 'aliases': ['CURIEs', + 'database cross-references', + 'dbxrefs', + 'IDs', + 'alternative identifiers', + 'alternative IDs', + 'alternative PIDs', + 'PIDs'], + 'comments': ['The entity `id` should not appear in this list.'], + 'domain_of': ['Entity', 'DataCollection'], + 'examples': [{'value': 'NCBItaxon:172684329'}, {'value': 'ISGN:1986497'}]} }) + alt_names: Optional[list[Name]] = Field(default=None, description="""Textual identifiers for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_names', + 'aliases': ['alternative names', 'synonyms'], + 'comments': ['The entity `name` should not appear in this list.'], + 'domain_of': ['Entity']} }) + part_of_collection: Optional[list[DataCollection]] = Field(default=None, description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", json_schema_extra = { "linkml_meta": {'alias': 'part_of_collection', 'domain_of': ['Entity']} }) + uri: str = Field(default=..., description="""Permanent resolvable URI for the entity at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'uri', 'aliases': ['url'], 'domain_of': ['Entity']} }) + + +class Coordinates(ConfiguredBaseModel): + """ + The coordinates defining the position associated with the entity. + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) + + altitude: Optional[QuantityValue] = Field(default=None, title="altitude", description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", json_schema_extra = { "linkml_meta": {'alias': 'altitude', + 'annotations': {'expected_value': {'tag': 'expected_value', + 'value': 'measurement value'}}, + 'domain_of': ['Coordinates'], + 'examples': [{'value': '100 meter'}], + 'slot_uri': 'MIXS:0000094'} }) + depth: Optional[QuantityValue] = Field(default=None, title="depth", description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", json_schema_extra = { "linkml_meta": {'alias': 'depth', + 'aliases': ['depth'], + 'annotations': {'expected_value': {'tag': 'expected_value', + 'value': 'measurement value'}}, + 'domain_of': ['Coordinates'], + 'examples': [{'value': '10 meter'}], + 'slot_uri': 'MIXS:0000018'} }) + elevation: Optional[QuantityValue] = Field(default=None, title="elevation", description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", json_schema_extra = { "linkml_meta": {'alias': 'elevation', + 'aliases': ['elevation'], + 'annotations': {'expected_value': {'tag': 'expected_value', + 'value': 'measurement value'}}, + 'domain_of': ['Coordinates'], + 'examples': [{'value': '100 meter'}], + 'slot_uri': 'MIXS:0000093'} }) + latitude: float = Field(default=..., description="""latitude""", json_schema_extra = { "linkml_meta": {'alias': 'latitude', + 'broad_mappings': ['MIXS:0000009'], + 'domain_of': ['Coordinates'], + 'examples': [{'value': '-33.460524'}], + 'mappings': ['schema:latitude'], + 'slot_uri': 'WGS84:lat'} }) + longitude: float = Field(default=..., description="""longitude""", json_schema_extra = { "linkml_meta": {'alias': 'longitude', + 'broad_mappings': ['MIXS:0000009'], + 'domain_of': ['Coordinates'], + 'examples': [{'value': '150.168149'}], + 'mappings': ['schema:longitude'], + 'slot_uri': 'WGS84:long'} }) + + +class Name(ConfiguredBaseModel): + """ + The name or label for an entity. This may be a primary name, alternative name, synonym, acronym, or any other label used to refer to an entity. + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) + + name_type: Optional[NameType] = Field(default=None, description="""Brief description of the name and/or its relationship to the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name_type', 'domain_of': ['Name']} }) + name: str = Field(default=..., description="""The string used as a name.""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['Entity', 'Name'], + 'examples': [{'value': 'Heat-inducible transcription repressor HrcA'}, + {'value': 'FW106 groundwater metagenome'}], + 'slot_uri': 'schema:name'} }) + + +class DataCollection(ConfiguredBaseModel): + """ + Administrative unit (e.g. project, proposal, etc.) in which one or more entities is collected. + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'comments': ['May be equivalent to FundingReference in the CreditMetadata ' + 'schema.'], + 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) + + id: Optional[str] = Field(default=None, description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', + 'aliases': ['proposal ID', 'project ID'], + 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' + 'that IDs will be unique between all the BER sources.'], + 'domain_of': ['Entity', 'DataCollection'], + 'slot_uri': 'schema:identifier'} }) + title: Optional[str] = Field(default=None, description="""Human-readable string representing the project.""", json_schema_extra = { "linkml_meta": {'alias': 'title', + 'aliases': ['name'], + 'domain_of': ['DataCollection'], + 'slot_uri': 'schema:name'} }) + description: Optional[str] = Field(default=None, description="""Textual description of the project.""", json_schema_extra = { "linkml_meta": {'alias': 'description', + 'domain_of': ['Entity', 'DataCollection'], + 'slot_uri': 'schema:description'} }) + alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', + 'aliases': ['CURIEs', + 'database cross-references', + 'dbxrefs', + 'IDs', + 'alternative identifiers', + 'alternative IDs', + 'alternative PIDs', + 'PIDs'], + 'comments': ['The project `id` should not appear in this list.'], + 'domain_of': ['Entity', 'DataCollection']} }) + alt_titles: Optional[list[Name]] = Field(default=None, description="""Alternative versions of the title/name of a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_titles', + 'aliases': ['alternative titles'], + 'comments': ['The project `title` should not appear in this list.'], + 'domain_of': ['DataCollection']} }) + url: str = Field(default=..., description="""Permanent resolvable URI for the collection at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'url', 'domain_of': ['DataCollection']} }) + + +# Model rebuild +# see https://pydantic-docs.helpmanual.io/usage/models/#rebuilding-a-model +AttributeValue.model_rebuild() +QuantityValue.model_rebuild() +Entity.model_rebuild() +Coordinates.model_rebuild() +Name.model_rebuild() +DataCollection.model_rebuild() + From 58323419c21df4ca7a4c77b41abe481cc7ef8560 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 19:56:13 -0700 Subject: [PATCH 11/20] return pydantic objects --- src/server.py | 87 +++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 51 deletions(-) diff --git a/src/server.py b/src/server.py index e173b55..900ef63 100644 --- a/src/server.py +++ b/src/server.py @@ -42,11 +42,12 @@ def get_all_entities(): collection = db["entities"] documents = list(collection.find({})) - # Remove the MongoDB '_id' field from each document for JSON serialization + # Convert documents to Entity objects + entities = [] for doc in documents: - doc.pop("_id", None) + entities.append(convert_document_to_entity(doc)) - return {"documents": documents} + return {"documents": entities, "count": len(entities)} class MongoDBQuery(BaseModel): @@ -98,12 +99,13 @@ def find_entities(query: MongoDBQuery): if query.limit: cursor = cursor.limit(query.limit) - # Convert cursor to list and remove MongoDB _id + # Convert cursor to list and convert to Entity objects documents = list(cursor) + entities = [] for doc in documents: - doc.pop("_id", None) + entities.append(convert_document_to_entity(doc)) - return {"documents": documents, "count": len(documents)} + return {"documents": entities, "count": len(entities)} except Exception as e: raise HTTPException(status_code=400, detail=f"Query error: {str(e)}") @@ -151,29 +153,18 @@ def find_nearby_entities( } } - # Execute find with geospatial filter and fixed projection - cursor = collection.find( - filter=geo_filter, - projection={ - "id": 1, - "name": 1, - "uri": 1, - "ber_data_source": 1, - "coordinates": 1, - }, - ) + # Execute find with geospatial filter + cursor = collection.find(filter=geo_filter) - # Convert cursor to list and remove MongoDB _id + # Convert cursor to list and convert to Entity objects documents = list(cursor) + entities = [] for doc in documents: - doc.pop("_id", None) + entities.append(convert_document_to_entity(doc)) return { - "documents": documents, - "count": len(documents), - "query_type": "nearby", - "center": {"latitude": latitude, "longitude": longitude}, - "radius_meters": radius_meters, + "documents": entities, + "count": len(entities) } except Exception as e: @@ -238,31 +229,18 @@ def find_entities_in_bounding_box( } } - # Execute find with geospatial filter and fixed projection - cursor = collection.find( - filter=geo_filter, - projection={ - "id": 1, - "name": 1, - "uri": 1, - "ber_data_source": 1, - "coordinates": 1, - }, - ) + # Execute find with geospatial filter + cursor = collection.find(filter=geo_filter) - # Convert cursor to list and remove MongoDB _id + # Convert cursor to list and convert to Entity objects documents = list(cursor) + entities = [] for doc in documents: - doc.pop("_id", None) + entities.append(convert_document_to_entity(doc)) return { - "documents": documents, - "count": len(documents), - "query_type": "bounding_box", - "bounding_box": { - "southwest": {"latitude": southwest_lat, "longitude": southwest_lng}, - "northeast": {"latitude": northeast_lat, "longitude": northeast_lng}, - }, + "documents": entities, + "count": len(entities) } except Exception as e: @@ -296,13 +274,7 @@ def get_entity_by_id(id: str): status_code=404, detail=f"Entity with id '{id}' not found" ) - # Remove MongoDB _id - document.pop("_id", None) - - # Remove metadata added during ingestion if present - document.pop("_metadata", None) - document.pop("geojson", None) - + entity = convert_document_to_entity(document) # Validate and create Entity instance try: @@ -322,5 +294,18 @@ def get_entity_by_id(id: str): raise HTTPException(status_code=400, detail=f"Query error: {str(e)}") +def convert_document_to_entity(document: Dict[str, Any]) -> Optional[bertron_schema_pydantic.Entity]: + """Convert a MongoDB document to an Entity object.""" + # Remove MongoDB _id and metadata + document.pop("_id", None) + document.pop("_metadata", None) + + document.pop("geojson", None) + + + return bertron_schema_pydantic.Entity(**document) + + + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) From e7affd313865672fe639ca20a40717e7c368e543 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 21:11:09 -0700 Subject: [PATCH 12/20] ruff --- src/bertron_schema_pydantic.py | 710 +++++++++++++++++++++++---------- src/server.py | 28 +- 2 files changed, 510 insertions(+), 228 deletions(-) diff --git a/src/bertron_schema_pydantic.py b/src/bertron_schema_pydantic.py index e1aefe7..2378e90 100644 --- a/src/bertron_schema_pydantic.py +++ b/src/bertron_schema_pydantic.py @@ -1,29 +1,13 @@ -from __future__ import annotations +from __future__ import annotations import re import sys -from datetime import ( - date, - datetime, - time -) -from decimal import Decimal -from enum import Enum -from typing import ( - Any, - ClassVar, - Literal, - Optional, - Union -) +from datetime import date, datetime, time +from decimal import Decimal +from enum import Enum +from typing import Any, ClassVar, Literal, Optional, Union -from pydantic import ( - BaseModel, - ConfigDict, - Field, - RootModel, - field_validator -) +from pydantic import BaseModel, ConfigDict, Field, RootModel, field_validator metamodel_version = "None" @@ -32,63 +16,81 @@ class ConfiguredBaseModel(BaseModel): model_config = ConfigDict( - validate_assignment = True, - validate_default = True, - extra = "forbid", - arbitrary_types_allowed = True, - use_enum_values = True, - strict = False, + validate_assignment=True, + validate_default=True, + extra="forbid", + arbitrary_types_allowed=True, + use_enum_values=True, + strict=False, ) pass - - class LinkMLMeta(RootModel): root: dict[str, Any] = {} model_config = ConfigDict(frozen=True) - def __getattr__(self, key:str): + def __getattr__(self, key: str): return getattr(self.root, key) - def __getitem__(self, key:str): + def __getitem__(self, key: str): return self.root[key] - def __setitem__(self, key:str, value): + def __setitem__(self, key: str, value): self.root[key] = value - def __contains__(self, key:str) -> bool: + def __contains__(self, key: str) -> bool: return key in self.root -linkml_meta = LinkMLMeta({'default_curi_maps': ['semweb_context'], - 'default_prefix': 'bertron', - 'default_range': 'string', - 'description': 'Schema for BERtron common data model.', - 'id': 'https://w3id.org/ber-data/bertron-schema', - 'imports': ['linkml:types', 'bertron_types'], - 'license': 'BSD-3', - 'name': 'bertron-schema', - 'prefixes': {'MIXS': {'prefix_prefix': 'MIXS', - 'prefix_reference': 'https://w3id.org/mixs/'}, - 'UO': {'prefix_prefix': 'UO', - 'prefix_reference': 'http://purl.obolibrary.org/obo/UO_'}, - 'WGS84': {'prefix_prefix': 'WGS84', - 'prefix_reference': 'http://www.w3.org/2003/01/geo/wgs84_pos#'}, - 'bertron': {'prefix_prefix': 'bertron', - 'prefix_reference': 'https://w3id.org/ber-data/bertron-schema/'}, - 'linkml': {'prefix_prefix': 'linkml', - 'prefix_reference': 'https://w3id.org/linkml/'}, - 'schema': {'prefix_prefix': 'schema', - 'prefix_reference': 'http://schema.org/'}}, - 'see_also': ['https://ber-data.github.io/bertron-schema'], - 'source_file': 'src/schema/linkml/bertron_schema.yaml', - 'title': 'BERtron schema'} ) +linkml_meta = LinkMLMeta( + { + "default_curi_maps": ["semweb_context"], + "default_prefix": "bertron", + "default_range": "string", + "description": "Schema for BERtron common data model.", + "id": "https://w3id.org/ber-data/bertron-schema", + "imports": ["linkml:types", "bertron_types"], + "license": "BSD-3", + "name": "bertron-schema", + "prefixes": { + "MIXS": { + "prefix_prefix": "MIXS", + "prefix_reference": "https://w3id.org/mixs/", + }, + "UO": { + "prefix_prefix": "UO", + "prefix_reference": "http://purl.obolibrary.org/obo/UO_", + }, + "WGS84": { + "prefix_prefix": "WGS84", + "prefix_reference": "http://www.w3.org/2003/01/geo/wgs84_pos#", + }, + "bertron": { + "prefix_prefix": "bertron", + "prefix_reference": "https://w3id.org/ber-data/bertron-schema/", + }, + "linkml": { + "prefix_prefix": "linkml", + "prefix_reference": "https://w3id.org/linkml/", + }, + "schema": { + "prefix_prefix": "schema", + "prefix_reference": "http://schema.org/", + }, + }, + "see_also": ["https://ber-data.github.io/bertron-schema"], + "source_file": "src/schema/linkml/bertron_schema.yaml", + "title": "BERtron schema", + } +) + class BERSourceType(str, Enum): """ The BER data source from whence the entity originated. """ + EMSL = "EMSL" ESS_DIVE = "ESS-DIVE" JGI = "JGI" @@ -100,6 +102,7 @@ class EntityType(str, Enum): """ Tags used to describe an entity. """ + biodata = "biodata" jgi_biosample = "jgi_biosample" sample = "sample" @@ -111,6 +114,7 @@ class NameType(str, Enum): """ The relationship between a name and a synonym of that name. """ + broad_synonym = "broad_synonym" """ The synonym refers to a broader group of entities than the name. @@ -133,55 +137,123 @@ class NameType(str, Enum): """ - class AttributeValue(ConfiguredBaseModel): """ The value for any value of a attribute for a sample. This object can hold both the un-normalized atomic value and the structured value """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'abstract': True, - 'class_uri': 'nmdc:AttributeValue', - 'from_schema': 'https://w3id.org/ber-data/bertron_types'}) - has_raw_value: Optional[str] = Field(default=None, description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', - 'domain_of': ['AttributeValue', 'QuantityValue'], - 'mappings': ['nmdc:has_raw_value']} }) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "abstract": True, + "class_uri": "nmdc:AttributeValue", + "from_schema": "https://w3id.org/ber-data/bertron_types", + } + ) + + has_raw_value: Optional[str] = Field( + default=None, + description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_raw_value", + "domain_of": ["AttributeValue", "QuantityValue"], + "mappings": ["nmdc:has_raw_value"], + } + }, + ) class QuantityValue(AttributeValue): """ A simple quantity, e.g. 2cm """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'nmdc:QuantityValue', - 'from_schema': 'https://w3id.org/ber-data/bertron_types', - 'mappings': ['schema:QuantityValue'], - 'slot_usage': {'has_numeric_value': {'description': 'The number part of the ' - 'quantity', - 'name': 'has_numeric_value'}, - 'has_raw_value': {'description': 'Unnormalized atomic string ' - 'representation, should in ' - 'syntax {number} {unit}', - 'name': 'has_raw_value'}, - 'has_unit': {'description': 'The unit of the quantity', - 'name': 'has_unit'}}}) - - has_maximum_numeric_value: Optional[float] = Field(default=None, description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_maximum_numeric_value', - 'domain_of': ['QuantityValue'], - 'is_a': 'has_numeric_value', - 'mappings': ['nmdc:has_maximum_numeric_value']} }) - has_minimum_numeric_value: Optional[float] = Field(default=None, description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_minimum_numeric_value', - 'domain_of': ['QuantityValue'], - 'is_a': 'has_numeric_value', - 'mappings': ['nmdc:has_minimum_numeric_value']} }) - has_numeric_value: Optional[float] = Field(default=None, description="""The number part of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_numeric_value', - 'domain_of': ['QuantityValue'], - 'mappings': ['nmdc:has_numeric_value', 'qud:quantityValue', 'schema:value']} }) - has_raw_value: Optional[str] = Field(default=None, description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', - 'domain_of': ['AttributeValue', 'QuantityValue'], - 'mappings': ['nmdc:has_raw_value']} }) - has_unit: Optional[str] = Field(default=None, description="""The unit of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_unit', - 'aliases': ['scale'], - 'domain_of': ['QuantityValue'], - 'mappings': ['nmdc:has_unit', 'qud:unit', 'schema:unitCode']} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "class_uri": "nmdc:QuantityValue", + "from_schema": "https://w3id.org/ber-data/bertron_types", + "mappings": ["schema:QuantityValue"], + "slot_usage": { + "has_numeric_value": { + "description": "The number part of the quantity", + "name": "has_numeric_value", + }, + "has_raw_value": { + "description": "Unnormalized atomic string " + "representation, should in " + "syntax {number} {unit}", + "name": "has_raw_value", + }, + "has_unit": { + "description": "The unit of the quantity", + "name": "has_unit", + }, + }, + } + ) + + has_maximum_numeric_value: Optional[float] = Field( + default=None, + description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_maximum_numeric_value", + "domain_of": ["QuantityValue"], + "is_a": "has_numeric_value", + "mappings": ["nmdc:has_maximum_numeric_value"], + } + }, + ) + has_minimum_numeric_value: Optional[float] = Field( + default=None, + description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_minimum_numeric_value", + "domain_of": ["QuantityValue"], + "is_a": "has_numeric_value", + "mappings": ["nmdc:has_minimum_numeric_value"], + } + }, + ) + has_numeric_value: Optional[float] = Field( + default=None, + description="""The number part of the quantity""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_numeric_value", + "domain_of": ["QuantityValue"], + "mappings": [ + "nmdc:has_numeric_value", + "qud:quantityValue", + "schema:value", + ], + } + }, + ) + has_raw_value: Optional[str] = Field( + default=None, + description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_raw_value", + "domain_of": ["AttributeValue", "QuantityValue"], + "mappings": ["nmdc:has_raw_value"], + } + }, + ) + has_unit: Optional[str] = Field( + default=None, + description="""The unit of the quantity""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_unit", + "aliases": ["scale"], + "domain_of": ["QuantityValue"], + "mappings": ["nmdc:has_unit", "qud:unit", "schema:unitCode"], + } + }, + ) class Entity(ConfiguredBaseModel): @@ -189,139 +261,358 @@ class Entity(ConfiguredBaseModel): An object retrieved by BERtron from a BER data API. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'schema:Thing', - 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - - ber_data_source: BERSourceType = Field(default=..., description="""The BER member from whence the entity originated.""", json_schema_extra = { "linkml_meta": {'alias': 'ber_data_source', 'domain_of': ['Entity']} }) - coordinates: Coordinates = Field(default=..., description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", json_schema_extra = { "linkml_meta": {'alias': 'coordinates', 'domain_of': ['Entity']} }) - entity_type: list[EntityType] = Field(default=..., description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", json_schema_extra = { "linkml_meta": {'alias': 'entity_type', 'domain_of': ['Entity']} }) - description: Optional[str] = Field(default=None, description="""Textual description of the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'description', - 'domain_of': ['Entity', 'DataCollection'], - 'examples': [{'value': 'River water sample taken by AquaTROLL 9000.'}, - {'value': 'Genome sequence of P. aeruginosa strain IDDQD'}], - 'slot_uri': 'schema:description'} }) - id: Optional[str] = Field(default=None, description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', - 'aliases': ['BER data source internal identifier', 'CURIE'], - 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' - 'that IDs will be unique between all the BER sources.'], - 'domain_of': ['Entity', 'DataCollection'], - 'slot_uri': 'schema:identifier'} }) - name: Optional[str] = Field(default=None, description="""Human-readable string representing an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name', - 'domain_of': ['Entity', 'Name'], - 'examples': [{'value': 'Pseudomonas aeruginosa strain IDDQD'}, - {'value': 'Soil core FW-106'}], - 'slot_uri': 'schema:name'} }) - alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', - 'aliases': ['CURIEs', - 'database cross-references', - 'dbxrefs', - 'IDs', - 'alternative identifiers', - 'alternative IDs', - 'alternative PIDs', - 'PIDs'], - 'comments': ['The entity `id` should not appear in this list.'], - 'domain_of': ['Entity', 'DataCollection'], - 'examples': [{'value': 'NCBItaxon:172684329'}, {'value': 'ISGN:1986497'}]} }) - alt_names: Optional[list[Name]] = Field(default=None, description="""Textual identifiers for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_names', - 'aliases': ['alternative names', 'synonyms'], - 'comments': ['The entity `name` should not appear in this list.'], - 'domain_of': ['Entity']} }) - part_of_collection: Optional[list[DataCollection]] = Field(default=None, description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", json_schema_extra = { "linkml_meta": {'alias': 'part_of_collection', 'domain_of': ['Entity']} }) - uri: str = Field(default=..., description="""Permanent resolvable URI for the entity at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'uri', 'aliases': ['url'], 'domain_of': ['Entity']} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "class_uri": "schema:Thing", + "from_schema": "https://w3id.org/ber-data/bertron-schema", + } + ) + + ber_data_source: BERSourceType = Field( + default=..., + description="""The BER member from whence the entity originated.""", + json_schema_extra={ + "linkml_meta": {"alias": "ber_data_source", "domain_of": ["Entity"]} + }, + ) + coordinates: Coordinates = Field( + default=..., + description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", + json_schema_extra={ + "linkml_meta": {"alias": "coordinates", "domain_of": ["Entity"]} + }, + ) + entity_type: list[EntityType] = Field( + default=..., + description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", + json_schema_extra={ + "linkml_meta": {"alias": "entity_type", "domain_of": ["Entity"]} + }, + ) + description: Optional[str] = Field( + default=None, + description="""Textual description of the entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "description", + "domain_of": ["Entity", "DataCollection"], + "examples": [ + {"value": "River water sample taken by AquaTROLL 9000."}, + {"value": "Genome sequence of P. aeruginosa strain IDDQD"}, + ], + "slot_uri": "schema:description", + } + }, + ) + id: Optional[str] = Field( + default=None, + description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", + json_schema_extra={ + "linkml_meta": { + "alias": "id", + "aliases": ["BER data source internal identifier", "CURIE"], + "comments": [ + "If the data source does not use CURIEs, we cannot guarantee " + "that IDs will be unique between all the BER sources." + ], + "domain_of": ["Entity", "DataCollection"], + "slot_uri": "schema:identifier", + } + }, + ) + name: Optional[str] = Field( + default=None, + description="""Human-readable string representing an entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "name", + "domain_of": ["Entity", "Name"], + "examples": [ + {"value": "Pseudomonas aeruginosa strain IDDQD"}, + {"value": "Soil core FW-106"}, + ], + "slot_uri": "schema:name", + } + }, + ) + alt_ids: Optional[list[str]] = Field( + default=None, + description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_ids", + "aliases": [ + "CURIEs", + "database cross-references", + "dbxrefs", + "IDs", + "alternative identifiers", + "alternative IDs", + "alternative PIDs", + "PIDs", + ], + "comments": ["The entity `id` should not appear in this list."], + "domain_of": ["Entity", "DataCollection"], + "examples": [ + {"value": "NCBItaxon:172684329"}, + {"value": "ISGN:1986497"}, + ], + } + }, + ) + alt_names: Optional[list[Name]] = Field( + default=None, + description="""Textual identifiers for an entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_names", + "aliases": ["alternative names", "synonyms"], + "comments": ["The entity `name` should not appear in this list."], + "domain_of": ["Entity"], + } + }, + ) + part_of_collection: Optional[list[DataCollection]] = Field( + default=None, + description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", + json_schema_extra={ + "linkml_meta": {"alias": "part_of_collection", "domain_of": ["Entity"]} + }, + ) + uri: str = Field( + default=..., + description="""Permanent resolvable URI for the entity at the data source.""", + json_schema_extra={ + "linkml_meta": {"alias": "uri", "aliases": ["url"], "domain_of": ["Entity"]} + }, + ) class Coordinates(ConfiguredBaseModel): """ The coordinates defining the position associated with the entity. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - - altitude: Optional[QuantityValue] = Field(default=None, title="altitude", description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", json_schema_extra = { "linkml_meta": {'alias': 'altitude', - 'annotations': {'expected_value': {'tag': 'expected_value', - 'value': 'measurement value'}}, - 'domain_of': ['Coordinates'], - 'examples': [{'value': '100 meter'}], - 'slot_uri': 'MIXS:0000094'} }) - depth: Optional[QuantityValue] = Field(default=None, title="depth", description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", json_schema_extra = { "linkml_meta": {'alias': 'depth', - 'aliases': ['depth'], - 'annotations': {'expected_value': {'tag': 'expected_value', - 'value': 'measurement value'}}, - 'domain_of': ['Coordinates'], - 'examples': [{'value': '10 meter'}], - 'slot_uri': 'MIXS:0000018'} }) - elevation: Optional[QuantityValue] = Field(default=None, title="elevation", description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", json_schema_extra = { "linkml_meta": {'alias': 'elevation', - 'aliases': ['elevation'], - 'annotations': {'expected_value': {'tag': 'expected_value', - 'value': 'measurement value'}}, - 'domain_of': ['Coordinates'], - 'examples': [{'value': '100 meter'}], - 'slot_uri': 'MIXS:0000093'} }) - latitude: float = Field(default=..., description="""latitude""", json_schema_extra = { "linkml_meta": {'alias': 'latitude', - 'broad_mappings': ['MIXS:0000009'], - 'domain_of': ['Coordinates'], - 'examples': [{'value': '-33.460524'}], - 'mappings': ['schema:latitude'], - 'slot_uri': 'WGS84:lat'} }) - longitude: float = Field(default=..., description="""longitude""", json_schema_extra = { "linkml_meta": {'alias': 'longitude', - 'broad_mappings': ['MIXS:0000009'], - 'domain_of': ['Coordinates'], - 'examples': [{'value': '150.168149'}], - 'mappings': ['schema:longitude'], - 'slot_uri': 'WGS84:long'} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + {"from_schema": "https://w3id.org/ber-data/bertron-schema"} + ) + + altitude: Optional[QuantityValue] = Field( + default=None, + title="altitude", + description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", + json_schema_extra={ + "linkml_meta": { + "alias": "altitude", + "annotations": { + "expected_value": { + "tag": "expected_value", + "value": "measurement value", + } + }, + "domain_of": ["Coordinates"], + "examples": [{"value": "100 meter"}], + "slot_uri": "MIXS:0000094", + } + }, + ) + depth: Optional[QuantityValue] = Field( + default=None, + title="depth", + description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", + json_schema_extra={ + "linkml_meta": { + "alias": "depth", + "aliases": ["depth"], + "annotations": { + "expected_value": { + "tag": "expected_value", + "value": "measurement value", + } + }, + "domain_of": ["Coordinates"], + "examples": [{"value": "10 meter"}], + "slot_uri": "MIXS:0000018", + } + }, + ) + elevation: Optional[QuantityValue] = Field( + default=None, + title="elevation", + description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", + json_schema_extra={ + "linkml_meta": { + "alias": "elevation", + "aliases": ["elevation"], + "annotations": { + "expected_value": { + "tag": "expected_value", + "value": "measurement value", + } + }, + "domain_of": ["Coordinates"], + "examples": [{"value": "100 meter"}], + "slot_uri": "MIXS:0000093", + } + }, + ) + latitude: float = Field( + default=..., + description="""latitude""", + json_schema_extra={ + "linkml_meta": { + "alias": "latitude", + "broad_mappings": ["MIXS:0000009"], + "domain_of": ["Coordinates"], + "examples": [{"value": "-33.460524"}], + "mappings": ["schema:latitude"], + "slot_uri": "WGS84:lat", + } + }, + ) + longitude: float = Field( + default=..., + description="""longitude""", + json_schema_extra={ + "linkml_meta": { + "alias": "longitude", + "broad_mappings": ["MIXS:0000009"], + "domain_of": ["Coordinates"], + "examples": [{"value": "150.168149"}], + "mappings": ["schema:longitude"], + "slot_uri": "WGS84:long", + } + }, + ) class Name(ConfiguredBaseModel): """ The name or label for an entity. This may be a primary name, alternative name, synonym, acronym, or any other label used to refer to an entity. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - name_type: Optional[NameType] = Field(default=None, description="""Brief description of the name and/or its relationship to the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name_type', 'domain_of': ['Name']} }) - name: str = Field(default=..., description="""The string used as a name.""", json_schema_extra = { "linkml_meta": {'alias': 'name', - 'domain_of': ['Entity', 'Name'], - 'examples': [{'value': 'Heat-inducible transcription repressor HrcA'}, - {'value': 'FW106 groundwater metagenome'}], - 'slot_uri': 'schema:name'} }) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + {"from_schema": "https://w3id.org/ber-data/bertron-schema"} + ) + + name_type: Optional[NameType] = Field( + default=None, + description="""Brief description of the name and/or its relationship to the entity.""", + json_schema_extra={ + "linkml_meta": {"alias": "name_type", "domain_of": ["Name"]} + }, + ) + name: str = Field( + default=..., + description="""The string used as a name.""", + json_schema_extra={ + "linkml_meta": { + "alias": "name", + "domain_of": ["Entity", "Name"], + "examples": [ + {"value": "Heat-inducible transcription repressor HrcA"}, + {"value": "FW106 groundwater metagenome"}, + ], + "slot_uri": "schema:name", + } + }, + ) class DataCollection(ConfiguredBaseModel): """ Administrative unit (e.g. project, proposal, etc.) in which one or more entities is collected. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'comments': ['May be equivalent to FundingReference in the CreditMetadata ' - 'schema.'], - 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - - id: Optional[str] = Field(default=None, description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', - 'aliases': ['proposal ID', 'project ID'], - 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' - 'that IDs will be unique between all the BER sources.'], - 'domain_of': ['Entity', 'DataCollection'], - 'slot_uri': 'schema:identifier'} }) - title: Optional[str] = Field(default=None, description="""Human-readable string representing the project.""", json_schema_extra = { "linkml_meta": {'alias': 'title', - 'aliases': ['name'], - 'domain_of': ['DataCollection'], - 'slot_uri': 'schema:name'} }) - description: Optional[str] = Field(default=None, description="""Textual description of the project.""", json_schema_extra = { "linkml_meta": {'alias': 'description', - 'domain_of': ['Entity', 'DataCollection'], - 'slot_uri': 'schema:description'} }) - alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', - 'aliases': ['CURIEs', - 'database cross-references', - 'dbxrefs', - 'IDs', - 'alternative identifiers', - 'alternative IDs', - 'alternative PIDs', - 'PIDs'], - 'comments': ['The project `id` should not appear in this list.'], - 'domain_of': ['Entity', 'DataCollection']} }) - alt_titles: Optional[list[Name]] = Field(default=None, description="""Alternative versions of the title/name of a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_titles', - 'aliases': ['alternative titles'], - 'comments': ['The project `title` should not appear in this list.'], - 'domain_of': ['DataCollection']} }) - url: str = Field(default=..., description="""Permanent resolvable URI for the collection at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'url', 'domain_of': ['DataCollection']} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "comments": [ + "May be equivalent to FundingReference in the CreditMetadata schema." + ], + "from_schema": "https://w3id.org/ber-data/bertron-schema", + } + ) + + id: Optional[str] = Field( + default=None, + description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", + json_schema_extra={ + "linkml_meta": { + "alias": "id", + "aliases": ["proposal ID", "project ID"], + "comments": [ + "If the data source does not use CURIEs, we cannot guarantee " + "that IDs will be unique between all the BER sources." + ], + "domain_of": ["Entity", "DataCollection"], + "slot_uri": "schema:identifier", + } + }, + ) + title: Optional[str] = Field( + default=None, + description="""Human-readable string representing the project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "title", + "aliases": ["name"], + "domain_of": ["DataCollection"], + "slot_uri": "schema:name", + } + }, + ) + description: Optional[str] = Field( + default=None, + description="""Textual description of the project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "description", + "domain_of": ["Entity", "DataCollection"], + "slot_uri": "schema:description", + } + }, + ) + alt_ids: Optional[list[str]] = Field( + default=None, + description="""Fully-qualified URI or CURIE used as an identifier for a project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_ids", + "aliases": [ + "CURIEs", + "database cross-references", + "dbxrefs", + "IDs", + "alternative identifiers", + "alternative IDs", + "alternative PIDs", + "PIDs", + ], + "comments": ["The project `id` should not appear in this list."], + "domain_of": ["Entity", "DataCollection"], + } + }, + ) + alt_titles: Optional[list[Name]] = Field( + default=None, + description="""Alternative versions of the title/name of a project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_titles", + "aliases": ["alternative titles"], + "comments": ["The project `title` should not appear in this list."], + "domain_of": ["DataCollection"], + } + }, + ) + url: str = Field( + default=..., + description="""Permanent resolvable URI for the collection at the data source.""", + json_schema_extra={ + "linkml_meta": {"alias": "url", "domain_of": ["DataCollection"]} + }, + ) # Model rebuild @@ -332,4 +623,3 @@ class DataCollection(ConfiguredBaseModel): Coordinates.model_rebuild() Name.model_rebuild() DataCollection.model_rebuild() - diff --git a/src/server.py b/src/server.py index 900ef63..8be07a7 100644 --- a/src/server.py +++ b/src/server.py @@ -103,7 +103,7 @@ def find_entities(query: MongoDBQuery): documents = list(cursor) entities = [] for doc in documents: - entities.append(convert_document_to_entity(doc)) + entities.append(convert_document_to_entity(doc)) return {"documents": entities, "count": len(entities)} @@ -162,10 +162,7 @@ def find_nearby_entities( for doc in documents: entities.append(convert_document_to_entity(doc)) - return { - "documents": entities, - "count": len(entities) - } + return {"documents": entities, "count": len(entities)} except Exception as e: raise HTTPException(status_code=400, detail=f"Nearby query error: {str(e)}") @@ -238,10 +235,7 @@ def find_entities_in_bounding_box( for doc in documents: entities.append(convert_document_to_entity(doc)) - return { - "documents": entities, - "count": len(entities) - } + return {"documents": entities, "count": len(entities)} except Exception as e: raise HTTPException( @@ -265,9 +259,7 @@ def get_entity_by_id(id: str): try: # Find the entity by ID - get all fields for proper validation - document = collection.find_one( - filter={"id": id} - ) + document = collection.find_one(filter={"id": id}) if not document: raise HTTPException( @@ -283,8 +275,8 @@ def get_entity_by_id(id: str): except Exception as validation_error: logger.error(f"Entity validation failed for id '{id}': {validation_error}") raise HTTPException( - status_code=500, - detail=f"Entity data validation failed: {str(validation_error)}" + status_code=500, + detail=f"Entity data validation failed: {str(validation_error)}", ) except HTTPException: @@ -294,18 +286,18 @@ def get_entity_by_id(id: str): raise HTTPException(status_code=400, detail=f"Query error: {str(e)}") -def convert_document_to_entity(document: Dict[str, Any]) -> Optional[bertron_schema_pydantic.Entity]: +def convert_document_to_entity( + document: Dict[str, Any], +) -> Optional[bertron_schema_pydantic.Entity]: """Convert a MongoDB document to an Entity object.""" # Remove MongoDB _id and metadata document.pop("_id", None) document.pop("_metadata", None) - + document.pop("geojson", None) - return bertron_schema_pydantic.Entity(**document) - if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) From fa26166889fed572f6f6fd37d150592839f0387f Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 21:12:26 -0700 Subject: [PATCH 13/20] example client --- src/bertron_client.py | 361 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 src/bertron_client.py diff --git a/src/bertron_client.py b/src/bertron_client.py new file mode 100644 index 0000000..3d42fe0 --- /dev/null +++ b/src/bertron_client.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python3 +""" +BERtron API Client + +A Python client for interacting with the BERtron API server. +Provides methods to query and retrieve entity data from the BER data sources. +""" + +import requests +from typing import List, Dict, Any, Optional, Union +from dataclasses import dataclass +import logging +from urllib.parse import urljoin, urlencode + +# Import pydantic Entity from bertron_schema_pydantic +from bertron_schema_pydantic import Entity + +# Set up logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +@dataclass +class QueryResponse: + """Represents a response from the BERtron API.""" + + entities: List[Entity] + count: int + query_type: Optional[str] = None + metadata: Optional[Dict[str, Any]] = None + + +class BertronAPIError(Exception): + """Custom exception for BERtron API errors.""" + + pass + + +class BertronClient: + """Client for interacting with the BERtron API.""" + + def __init__(self, base_url: str = "http://localhost:8000", timeout: int = 30): + """ + Initialize the BERtron client. + + Args: + base_url: Base URL of the BERtron API server + timeout: Request timeout in seconds + """ + self.base_url = base_url.rstrip("/") + self.timeout = timeout + self.session = requests.Session() + self.session.headers.update( + {"Content-Type": "application/json", "Accept": "application/json"} + ) + + def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]: + """ + Make a request to the BERtron API. + + Args: + method: HTTP method (GET, POST, etc.) + endpoint: API endpoint + **kwargs: Additional arguments for requests + + Returns: + JSON response as dictionary + + Raises: + BertronAPIError: If the API request fails + """ + url = urljoin(self.base_url, endpoint) + + try: + response = self.session.request( + method=method, url=url, timeout=self.timeout, **kwargs + ) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + logger.error(f"API request failed: {e}") + raise BertronAPIError(f"API request failed: {e}") + + def health_check(self) -> Dict[str, Any]: + """ + Check the health of the BERtron API server. + + Returns: + Health status information + """ + return self._make_request("GET", "/health") + + def get_all_entities(self) -> QueryResponse: + """ + Get all entities from the BERtron database. + + Returns: + QueryResponse containing all entities + """ + response = self._make_request("GET", "/bertron") + entities = [Entity(**doc) for doc in response["documents"]] + return QueryResponse(entities=entities, count=len(entities)) + + def get_entity_by_id(self, entity_id: str) -> Entity: + """ + Get a specific entity by its ID. + + Args: + entity_id: The unique identifier of the entity + + Returns: + Entity object + + Raises: + BertronAPIError: If entity not found or API error + """ + response = self._make_request("GET", f"/bertron/{entity_id}") + return Entity(**response) + + def find_entities( + self, + filter_dict: Optional[Dict[str, Any]] = None, + projection: Optional[Dict[str, Any]] = None, + skip: int = 0, + limit: int = 100, + sort: Optional[Dict[str, int]] = None, + ) -> QueryResponse: + """ + Search for entities using MongoDB query syntax. + + Args: + filter_dict: MongoDB filter criteria + projection: Fields to include/exclude + skip: Number of documents to skip + limit: Maximum number of documents to return + sort: Sort criteria + + Returns: + QueryResponse containing matching entities + """ + query_data = {"filter": filter_dict or {}, "skip": skip, "limit": limit} + if projection: + query_data["projection"] = projection + if sort: + query_data["sort"] = sort + + response = self._make_request("POST", "/bertron/find", json=query_data) + entities = [Entity(**doc) for doc in response["documents"]] + return QueryResponse(entities=entities, count=response["count"]) + + def find_nearby_entities( + self, latitude: float, longitude: float, radius_meters: float + ) -> QueryResponse: + """ + Find entities within a specified radius of a geographic point. + + Args: + latitude: Center latitude in degrees (-90 to 90) + longitude: Center longitude in degrees (-180 to 180) + radius_meters: Search radius in meters + + Returns: + QueryResponse containing nearby entities (sorted by distance) + """ + params = { + "latitude": latitude, + "longitude": longitude, + "radius_meters": radius_meters, + } + + response = self._make_request("GET", "/bertron/geo/nearby", params=params) + entities = [Entity(**doc) for doc in response["documents"]] + + # Create metadata based on the request parameters since server doesn't return it + metadata = { + "center": {"latitude": latitude, "longitude": longitude}, + "radius_meters": radius_meters, + } + + return QueryResponse( + entities=entities, + count=response["count"], + query_type="geospatial_nearby", + metadata=metadata, + ) + + def find_entities_in_bounding_box( + self, + southwest_lat: float, + southwest_lng: float, + northeast_lat: float, + northeast_lng: float, + ) -> QueryResponse: + """ + Find entities within a rectangular bounding box. + + Args: + southwest_lat: Southwest corner latitude + southwest_lng: Southwest corner longitude + northeast_lat: Northeast corner latitude + northeast_lng: Northeast corner longitude + + Returns: + QueryResponse containing entities within the bounding box + """ + params = { + "southwest_lat": southwest_lat, + "southwest_lng": southwest_lng, + "northeast_lat": northeast_lat, + "northeast_lng": northeast_lng, + } + + response = self._make_request("GET", "/bertron/geo/bbox", params=params) + entities = [Entity(**doc) for doc in response["documents"]] + + # Create metadata based on the request parameters since server doesn't return it + metadata = { + "bounding_box": { + "southwest": {"latitude": southwest_lat, "longitude": southwest_lng}, + "northeast": {"latitude": northeast_lat, "longitude": northeast_lng} + } + } + + return QueryResponse( + entities=entities, + count=response["count"], + query_type="geospatial_bounding_box", + metadata=metadata, + ) + + def find_entities_by_source(self, source: str) -> QueryResponse: + """ + Find entities from a specific BER data source. + + Args: + source: BER data source (EMSL, ESS-DIVE, JGI, NMDC, MONET) + + Returns: + QueryResponse containing entities from the specified source + """ + return self.find_entities(filter_dict={"ber_data_source": source}) + + def find_entities_by_entity_type(self, entity_type: str) -> QueryResponse: + """ + Find entities of a specific entity type. + + Args: + entity_type: Entity type (biodata, sample, sequence, taxon, jgi_biosample) + + Returns: + QueryResponse containing entities of the specified type + """ + return self.find_entities(filter_dict={"entity_type": entity_type}) + + def search_entities_by_name( + self, name_pattern: str, case_sensitive: bool = False + ) -> QueryResponse: + """ + Search for entities by name using regex pattern matching. + + Args: + name_pattern: Name pattern to search for + case_sensitive: Whether the search should be case sensitive + + Returns: + QueryResponse containing entities matching the name pattern + """ + regex_filter = {"name": {"$regex": name_pattern}} + if not case_sensitive: + regex_filter["name"]["$options"] = "i" + + return self.find_entities(filter_dict=regex_filter) + + def get_entities_in_region( + self, center_lat: float, center_lng: float, radius_km: float + ) -> QueryResponse: + """ + Convenience method to find entities in a region (radius in kilometers). + + Args: + center_lat: Center latitude + center_lng: Center longitude + radius_km: Radius in kilometers + + Returns: + QueryResponse containing entities in the specified region + """ + radius_meters = radius_km * 1000 + return self.find_nearby_entities(center_lat, center_lng, radius_meters) + + def close(self): + """Close the HTTP session.""" + self.session.close() + + def __enter__(self): + """Context manager entry.""" + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Context manager exit.""" + self.close() + + +# Example usage +if __name__ == "__main__": + # Example usage of the BERtron client + + # Initialize client + client = BertronClient(base_url="http://localhost:8000") + + try: + # Check server health + health = client.health_check() + print(f"Server health: {health}") + + # Get all entities + print("\nGetting all entities...") + all_entities = client.get_all_entities() + print(f"Found {all_entities.count} entities") + + if all_entities.entities: + # Show first entity as example + first_entity = all_entities.entities[0] + print(f"First entity: {first_entity.name} ({first_entity.id})") + + # Get specific entity by ID (if ID is available) + if first_entity.id: + entity = client.get_entity_by_id(first_entity.id) + print(f"Retrieved entity: {entity.name}") + + # Search for entities from EMSL + print("\nSearching for EMSL entities...") + emsl_entities = client.find_entities_by_source("EMSL") + print(f"Found {emsl_entities.count} EMSL entities") + + # Search for sample entities + print("\nSearching for sample entities...") + sample_entities = client.find_entities_by_entity_type("sample") + print(f"Found {sample_entities.count} sample entities") + + # Geographic search near Seattle + print("\nSearching near Florida (10km radius)...") + seattle_entities = client.get_entities_in_region(28.1, -81.4, 100) + print(f"Found {seattle_entities.count} entities near Florida") + + # Bounding box search for Yellowstone region + print("\nSearching Yellowstone region region...") + pnw_entities = client.find_entities_in_bounding_box( + southwest_lat=44.0, + southwest_lng=-125.0, + northeast_lat=49.0, + northeast_lng=-110.0, + ) + print(f"Found {pnw_entities.count} entities in Yellowstone region") + + except BertronAPIError as e: + print(f"API Error: {e}") + except Exception as e: + print(f"Error: {e}") + finally: + client.close() From 3d78515eec5be0dff2eb98462c750c5b0ba876f8 Mon Sep 17 00:00:00 2001 From: shreddd Date: Thu, 12 Jun 2025 21:13:39 -0700 Subject: [PATCH 14/20] update schema --- src/bertron_schema_pydantic.py | 710 ++++++++++----------------------- 1 file changed, 210 insertions(+), 500 deletions(-) diff --git a/src/bertron_schema_pydantic.py b/src/bertron_schema_pydantic.py index 2378e90..e1aefe7 100644 --- a/src/bertron_schema_pydantic.py +++ b/src/bertron_schema_pydantic.py @@ -1,13 +1,29 @@ -from __future__ import annotations +from __future__ import annotations import re import sys -from datetime import date, datetime, time -from decimal import Decimal -from enum import Enum -from typing import Any, ClassVar, Literal, Optional, Union +from datetime import ( + date, + datetime, + time +) +from decimal import Decimal +from enum import Enum +from typing import ( + Any, + ClassVar, + Literal, + Optional, + Union +) -from pydantic import BaseModel, ConfigDict, Field, RootModel, field_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + RootModel, + field_validator +) metamodel_version = "None" @@ -16,81 +32,63 @@ class ConfiguredBaseModel(BaseModel): model_config = ConfigDict( - validate_assignment=True, - validate_default=True, - extra="forbid", - arbitrary_types_allowed=True, - use_enum_values=True, - strict=False, + validate_assignment = True, + validate_default = True, + extra = "forbid", + arbitrary_types_allowed = True, + use_enum_values = True, + strict = False, ) pass + + class LinkMLMeta(RootModel): root: dict[str, Any] = {} model_config = ConfigDict(frozen=True) - def __getattr__(self, key: str): + def __getattr__(self, key:str): return getattr(self.root, key) - def __getitem__(self, key: str): + def __getitem__(self, key:str): return self.root[key] - def __setitem__(self, key: str, value): + def __setitem__(self, key:str, value): self.root[key] = value - def __contains__(self, key: str) -> bool: + def __contains__(self, key:str) -> bool: return key in self.root -linkml_meta = LinkMLMeta( - { - "default_curi_maps": ["semweb_context"], - "default_prefix": "bertron", - "default_range": "string", - "description": "Schema for BERtron common data model.", - "id": "https://w3id.org/ber-data/bertron-schema", - "imports": ["linkml:types", "bertron_types"], - "license": "BSD-3", - "name": "bertron-schema", - "prefixes": { - "MIXS": { - "prefix_prefix": "MIXS", - "prefix_reference": "https://w3id.org/mixs/", - }, - "UO": { - "prefix_prefix": "UO", - "prefix_reference": "http://purl.obolibrary.org/obo/UO_", - }, - "WGS84": { - "prefix_prefix": "WGS84", - "prefix_reference": "http://www.w3.org/2003/01/geo/wgs84_pos#", - }, - "bertron": { - "prefix_prefix": "bertron", - "prefix_reference": "https://w3id.org/ber-data/bertron-schema/", - }, - "linkml": { - "prefix_prefix": "linkml", - "prefix_reference": "https://w3id.org/linkml/", - }, - "schema": { - "prefix_prefix": "schema", - "prefix_reference": "http://schema.org/", - }, - }, - "see_also": ["https://ber-data.github.io/bertron-schema"], - "source_file": "src/schema/linkml/bertron_schema.yaml", - "title": "BERtron schema", - } -) - +linkml_meta = LinkMLMeta({'default_curi_maps': ['semweb_context'], + 'default_prefix': 'bertron', + 'default_range': 'string', + 'description': 'Schema for BERtron common data model.', + 'id': 'https://w3id.org/ber-data/bertron-schema', + 'imports': ['linkml:types', 'bertron_types'], + 'license': 'BSD-3', + 'name': 'bertron-schema', + 'prefixes': {'MIXS': {'prefix_prefix': 'MIXS', + 'prefix_reference': 'https://w3id.org/mixs/'}, + 'UO': {'prefix_prefix': 'UO', + 'prefix_reference': 'http://purl.obolibrary.org/obo/UO_'}, + 'WGS84': {'prefix_prefix': 'WGS84', + 'prefix_reference': 'http://www.w3.org/2003/01/geo/wgs84_pos#'}, + 'bertron': {'prefix_prefix': 'bertron', + 'prefix_reference': 'https://w3id.org/ber-data/bertron-schema/'}, + 'linkml': {'prefix_prefix': 'linkml', + 'prefix_reference': 'https://w3id.org/linkml/'}, + 'schema': {'prefix_prefix': 'schema', + 'prefix_reference': 'http://schema.org/'}}, + 'see_also': ['https://ber-data.github.io/bertron-schema'], + 'source_file': 'src/schema/linkml/bertron_schema.yaml', + 'title': 'BERtron schema'} ) class BERSourceType(str, Enum): """ The BER data source from whence the entity originated. """ - EMSL = "EMSL" ESS_DIVE = "ESS-DIVE" JGI = "JGI" @@ -102,7 +100,6 @@ class EntityType(str, Enum): """ Tags used to describe an entity. """ - biodata = "biodata" jgi_biosample = "jgi_biosample" sample = "sample" @@ -114,7 +111,6 @@ class NameType(str, Enum): """ The relationship between a name and a synonym of that name. """ - broad_synonym = "broad_synonym" """ The synonym refers to a broader group of entities than the name. @@ -137,123 +133,55 @@ class NameType(str, Enum): """ + class AttributeValue(ConfiguredBaseModel): """ The value for any value of a attribute for a sample. This object can hold both the un-normalized atomic value and the structured value """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'abstract': True, + 'class_uri': 'nmdc:AttributeValue', + 'from_schema': 'https://w3id.org/ber-data/bertron_types'}) - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "abstract": True, - "class_uri": "nmdc:AttributeValue", - "from_schema": "https://w3id.org/ber-data/bertron_types", - } - ) - - has_raw_value: Optional[str] = Field( - default=None, - description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_raw_value", - "domain_of": ["AttributeValue", "QuantityValue"], - "mappings": ["nmdc:has_raw_value"], - } - }, - ) + has_raw_value: Optional[str] = Field(default=None, description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', + 'domain_of': ['AttributeValue', 'QuantityValue'], + 'mappings': ['nmdc:has_raw_value']} }) class QuantityValue(AttributeValue): """ A simple quantity, e.g. 2cm """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "class_uri": "nmdc:QuantityValue", - "from_schema": "https://w3id.org/ber-data/bertron_types", - "mappings": ["schema:QuantityValue"], - "slot_usage": { - "has_numeric_value": { - "description": "The number part of the quantity", - "name": "has_numeric_value", - }, - "has_raw_value": { - "description": "Unnormalized atomic string " - "representation, should in " - "syntax {number} {unit}", - "name": "has_raw_value", - }, - "has_unit": { - "description": "The unit of the quantity", - "name": "has_unit", - }, - }, - } - ) - - has_maximum_numeric_value: Optional[float] = Field( - default=None, - description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_maximum_numeric_value", - "domain_of": ["QuantityValue"], - "is_a": "has_numeric_value", - "mappings": ["nmdc:has_maximum_numeric_value"], - } - }, - ) - has_minimum_numeric_value: Optional[float] = Field( - default=None, - description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_minimum_numeric_value", - "domain_of": ["QuantityValue"], - "is_a": "has_numeric_value", - "mappings": ["nmdc:has_minimum_numeric_value"], - } - }, - ) - has_numeric_value: Optional[float] = Field( - default=None, - description="""The number part of the quantity""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_numeric_value", - "domain_of": ["QuantityValue"], - "mappings": [ - "nmdc:has_numeric_value", - "qud:quantityValue", - "schema:value", - ], - } - }, - ) - has_raw_value: Optional[str] = Field( - default=None, - description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_raw_value", - "domain_of": ["AttributeValue", "QuantityValue"], - "mappings": ["nmdc:has_raw_value"], - } - }, - ) - has_unit: Optional[str] = Field( - default=None, - description="""The unit of the quantity""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_unit", - "aliases": ["scale"], - "domain_of": ["QuantityValue"], - "mappings": ["nmdc:has_unit", "qud:unit", "schema:unitCode"], - } - }, - ) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'nmdc:QuantityValue', + 'from_schema': 'https://w3id.org/ber-data/bertron_types', + 'mappings': ['schema:QuantityValue'], + 'slot_usage': {'has_numeric_value': {'description': 'The number part of the ' + 'quantity', + 'name': 'has_numeric_value'}, + 'has_raw_value': {'description': 'Unnormalized atomic string ' + 'representation, should in ' + 'syntax {number} {unit}', + 'name': 'has_raw_value'}, + 'has_unit': {'description': 'The unit of the quantity', + 'name': 'has_unit'}}}) + + has_maximum_numeric_value: Optional[float] = Field(default=None, description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_maximum_numeric_value', + 'domain_of': ['QuantityValue'], + 'is_a': 'has_numeric_value', + 'mappings': ['nmdc:has_maximum_numeric_value']} }) + has_minimum_numeric_value: Optional[float] = Field(default=None, description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_minimum_numeric_value', + 'domain_of': ['QuantityValue'], + 'is_a': 'has_numeric_value', + 'mappings': ['nmdc:has_minimum_numeric_value']} }) + has_numeric_value: Optional[float] = Field(default=None, description="""The number part of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_numeric_value', + 'domain_of': ['QuantityValue'], + 'mappings': ['nmdc:has_numeric_value', 'qud:quantityValue', 'schema:value']} }) + has_raw_value: Optional[str] = Field(default=None, description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', + 'domain_of': ['AttributeValue', 'QuantityValue'], + 'mappings': ['nmdc:has_raw_value']} }) + has_unit: Optional[str] = Field(default=None, description="""The unit of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_unit', + 'aliases': ['scale'], + 'domain_of': ['QuantityValue'], + 'mappings': ['nmdc:has_unit', 'qud:unit', 'schema:unitCode']} }) class Entity(ConfiguredBaseModel): @@ -261,358 +189,139 @@ class Entity(ConfiguredBaseModel): An object retrieved by BERtron from a BER data API. """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "class_uri": "schema:Thing", - "from_schema": "https://w3id.org/ber-data/bertron-schema", - } - ) - - ber_data_source: BERSourceType = Field( - default=..., - description="""The BER member from whence the entity originated.""", - json_schema_extra={ - "linkml_meta": {"alias": "ber_data_source", "domain_of": ["Entity"]} - }, - ) - coordinates: Coordinates = Field( - default=..., - description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", - json_schema_extra={ - "linkml_meta": {"alias": "coordinates", "domain_of": ["Entity"]} - }, - ) - entity_type: list[EntityType] = Field( - default=..., - description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", - json_schema_extra={ - "linkml_meta": {"alias": "entity_type", "domain_of": ["Entity"]} - }, - ) - description: Optional[str] = Field( - default=None, - description="""Textual description of the entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "description", - "domain_of": ["Entity", "DataCollection"], - "examples": [ - {"value": "River water sample taken by AquaTROLL 9000."}, - {"value": "Genome sequence of P. aeruginosa strain IDDQD"}, - ], - "slot_uri": "schema:description", - } - }, - ) - id: Optional[str] = Field( - default=None, - description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", - json_schema_extra={ - "linkml_meta": { - "alias": "id", - "aliases": ["BER data source internal identifier", "CURIE"], - "comments": [ - "If the data source does not use CURIEs, we cannot guarantee " - "that IDs will be unique between all the BER sources." - ], - "domain_of": ["Entity", "DataCollection"], - "slot_uri": "schema:identifier", - } - }, - ) - name: Optional[str] = Field( - default=None, - description="""Human-readable string representing an entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "name", - "domain_of": ["Entity", "Name"], - "examples": [ - {"value": "Pseudomonas aeruginosa strain IDDQD"}, - {"value": "Soil core FW-106"}, - ], - "slot_uri": "schema:name", - } - }, - ) - alt_ids: Optional[list[str]] = Field( - default=None, - description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_ids", - "aliases": [ - "CURIEs", - "database cross-references", - "dbxrefs", - "IDs", - "alternative identifiers", - "alternative IDs", - "alternative PIDs", - "PIDs", - ], - "comments": ["The entity `id` should not appear in this list."], - "domain_of": ["Entity", "DataCollection"], - "examples": [ - {"value": "NCBItaxon:172684329"}, - {"value": "ISGN:1986497"}, - ], - } - }, - ) - alt_names: Optional[list[Name]] = Field( - default=None, - description="""Textual identifiers for an entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_names", - "aliases": ["alternative names", "synonyms"], - "comments": ["The entity `name` should not appear in this list."], - "domain_of": ["Entity"], - } - }, - ) - part_of_collection: Optional[list[DataCollection]] = Field( - default=None, - description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", - json_schema_extra={ - "linkml_meta": {"alias": "part_of_collection", "domain_of": ["Entity"]} - }, - ) - uri: str = Field( - default=..., - description="""Permanent resolvable URI for the entity at the data source.""", - json_schema_extra={ - "linkml_meta": {"alias": "uri", "aliases": ["url"], "domain_of": ["Entity"]} - }, - ) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'schema:Thing', + 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) + + ber_data_source: BERSourceType = Field(default=..., description="""The BER member from whence the entity originated.""", json_schema_extra = { "linkml_meta": {'alias': 'ber_data_source', 'domain_of': ['Entity']} }) + coordinates: Coordinates = Field(default=..., description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", json_schema_extra = { "linkml_meta": {'alias': 'coordinates', 'domain_of': ['Entity']} }) + entity_type: list[EntityType] = Field(default=..., description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", json_schema_extra = { "linkml_meta": {'alias': 'entity_type', 'domain_of': ['Entity']} }) + description: Optional[str] = Field(default=None, description="""Textual description of the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'description', + 'domain_of': ['Entity', 'DataCollection'], + 'examples': [{'value': 'River water sample taken by AquaTROLL 9000.'}, + {'value': 'Genome sequence of P. aeruginosa strain IDDQD'}], + 'slot_uri': 'schema:description'} }) + id: Optional[str] = Field(default=None, description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', + 'aliases': ['BER data source internal identifier', 'CURIE'], + 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' + 'that IDs will be unique between all the BER sources.'], + 'domain_of': ['Entity', 'DataCollection'], + 'slot_uri': 'schema:identifier'} }) + name: Optional[str] = Field(default=None, description="""Human-readable string representing an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['Entity', 'Name'], + 'examples': [{'value': 'Pseudomonas aeruginosa strain IDDQD'}, + {'value': 'Soil core FW-106'}], + 'slot_uri': 'schema:name'} }) + alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', + 'aliases': ['CURIEs', + 'database cross-references', + 'dbxrefs', + 'IDs', + 'alternative identifiers', + 'alternative IDs', + 'alternative PIDs', + 'PIDs'], + 'comments': ['The entity `id` should not appear in this list.'], + 'domain_of': ['Entity', 'DataCollection'], + 'examples': [{'value': 'NCBItaxon:172684329'}, {'value': 'ISGN:1986497'}]} }) + alt_names: Optional[list[Name]] = Field(default=None, description="""Textual identifiers for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_names', + 'aliases': ['alternative names', 'synonyms'], + 'comments': ['The entity `name` should not appear in this list.'], + 'domain_of': ['Entity']} }) + part_of_collection: Optional[list[DataCollection]] = Field(default=None, description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", json_schema_extra = { "linkml_meta": {'alias': 'part_of_collection', 'domain_of': ['Entity']} }) + uri: str = Field(default=..., description="""Permanent resolvable URI for the entity at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'uri', 'aliases': ['url'], 'domain_of': ['Entity']} }) class Coordinates(ConfiguredBaseModel): """ The coordinates defining the position associated with the entity. """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - {"from_schema": "https://w3id.org/ber-data/bertron-schema"} - ) - - altitude: Optional[QuantityValue] = Field( - default=None, - title="altitude", - description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", - json_schema_extra={ - "linkml_meta": { - "alias": "altitude", - "annotations": { - "expected_value": { - "tag": "expected_value", - "value": "measurement value", - } - }, - "domain_of": ["Coordinates"], - "examples": [{"value": "100 meter"}], - "slot_uri": "MIXS:0000094", - } - }, - ) - depth: Optional[QuantityValue] = Field( - default=None, - title="depth", - description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", - json_schema_extra={ - "linkml_meta": { - "alias": "depth", - "aliases": ["depth"], - "annotations": { - "expected_value": { - "tag": "expected_value", - "value": "measurement value", - } - }, - "domain_of": ["Coordinates"], - "examples": [{"value": "10 meter"}], - "slot_uri": "MIXS:0000018", - } - }, - ) - elevation: Optional[QuantityValue] = Field( - default=None, - title="elevation", - description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", - json_schema_extra={ - "linkml_meta": { - "alias": "elevation", - "aliases": ["elevation"], - "annotations": { - "expected_value": { - "tag": "expected_value", - "value": "measurement value", - } - }, - "domain_of": ["Coordinates"], - "examples": [{"value": "100 meter"}], - "slot_uri": "MIXS:0000093", - } - }, - ) - latitude: float = Field( - default=..., - description="""latitude""", - json_schema_extra={ - "linkml_meta": { - "alias": "latitude", - "broad_mappings": ["MIXS:0000009"], - "domain_of": ["Coordinates"], - "examples": [{"value": "-33.460524"}], - "mappings": ["schema:latitude"], - "slot_uri": "WGS84:lat", - } - }, - ) - longitude: float = Field( - default=..., - description="""longitude""", - json_schema_extra={ - "linkml_meta": { - "alias": "longitude", - "broad_mappings": ["MIXS:0000009"], - "domain_of": ["Coordinates"], - "examples": [{"value": "150.168149"}], - "mappings": ["schema:longitude"], - "slot_uri": "WGS84:long", - } - }, - ) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) + + altitude: Optional[QuantityValue] = Field(default=None, title="altitude", description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", json_schema_extra = { "linkml_meta": {'alias': 'altitude', + 'annotations': {'expected_value': {'tag': 'expected_value', + 'value': 'measurement value'}}, + 'domain_of': ['Coordinates'], + 'examples': [{'value': '100 meter'}], + 'slot_uri': 'MIXS:0000094'} }) + depth: Optional[QuantityValue] = Field(default=None, title="depth", description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", json_schema_extra = { "linkml_meta": {'alias': 'depth', + 'aliases': ['depth'], + 'annotations': {'expected_value': {'tag': 'expected_value', + 'value': 'measurement value'}}, + 'domain_of': ['Coordinates'], + 'examples': [{'value': '10 meter'}], + 'slot_uri': 'MIXS:0000018'} }) + elevation: Optional[QuantityValue] = Field(default=None, title="elevation", description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", json_schema_extra = { "linkml_meta": {'alias': 'elevation', + 'aliases': ['elevation'], + 'annotations': {'expected_value': {'tag': 'expected_value', + 'value': 'measurement value'}}, + 'domain_of': ['Coordinates'], + 'examples': [{'value': '100 meter'}], + 'slot_uri': 'MIXS:0000093'} }) + latitude: float = Field(default=..., description="""latitude""", json_schema_extra = { "linkml_meta": {'alias': 'latitude', + 'broad_mappings': ['MIXS:0000009'], + 'domain_of': ['Coordinates'], + 'examples': [{'value': '-33.460524'}], + 'mappings': ['schema:latitude'], + 'slot_uri': 'WGS84:lat'} }) + longitude: float = Field(default=..., description="""longitude""", json_schema_extra = { "linkml_meta": {'alias': 'longitude', + 'broad_mappings': ['MIXS:0000009'], + 'domain_of': ['Coordinates'], + 'examples': [{'value': '150.168149'}], + 'mappings': ['schema:longitude'], + 'slot_uri': 'WGS84:long'} }) class Name(ConfiguredBaseModel): """ The name or label for an entity. This may be a primary name, alternative name, synonym, acronym, or any other label used to refer to an entity. """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - {"from_schema": "https://w3id.org/ber-data/bertron-schema"} - ) - - name_type: Optional[NameType] = Field( - default=None, - description="""Brief description of the name and/or its relationship to the entity.""", - json_schema_extra={ - "linkml_meta": {"alias": "name_type", "domain_of": ["Name"]} - }, - ) - name: str = Field( - default=..., - description="""The string used as a name.""", - json_schema_extra={ - "linkml_meta": { - "alias": "name", - "domain_of": ["Entity", "Name"], - "examples": [ - {"value": "Heat-inducible transcription repressor HrcA"}, - {"value": "FW106 groundwater metagenome"}, - ], - "slot_uri": "schema:name", - } - }, - ) + name_type: Optional[NameType] = Field(default=None, description="""Brief description of the name and/or its relationship to the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name_type', 'domain_of': ['Name']} }) + name: str = Field(default=..., description="""The string used as a name.""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['Entity', 'Name'], + 'examples': [{'value': 'Heat-inducible transcription repressor HrcA'}, + {'value': 'FW106 groundwater metagenome'}], + 'slot_uri': 'schema:name'} }) class DataCollection(ConfiguredBaseModel): """ Administrative unit (e.g. project, proposal, etc.) in which one or more entities is collected. """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "comments": [ - "May be equivalent to FundingReference in the CreditMetadata schema." - ], - "from_schema": "https://w3id.org/ber-data/bertron-schema", - } - ) - - id: Optional[str] = Field( - default=None, - description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", - json_schema_extra={ - "linkml_meta": { - "alias": "id", - "aliases": ["proposal ID", "project ID"], - "comments": [ - "If the data source does not use CURIEs, we cannot guarantee " - "that IDs will be unique between all the BER sources." - ], - "domain_of": ["Entity", "DataCollection"], - "slot_uri": "schema:identifier", - } - }, - ) - title: Optional[str] = Field( - default=None, - description="""Human-readable string representing the project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "title", - "aliases": ["name"], - "domain_of": ["DataCollection"], - "slot_uri": "schema:name", - } - }, - ) - description: Optional[str] = Field( - default=None, - description="""Textual description of the project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "description", - "domain_of": ["Entity", "DataCollection"], - "slot_uri": "schema:description", - } - }, - ) - alt_ids: Optional[list[str]] = Field( - default=None, - description="""Fully-qualified URI or CURIE used as an identifier for a project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_ids", - "aliases": [ - "CURIEs", - "database cross-references", - "dbxrefs", - "IDs", - "alternative identifiers", - "alternative IDs", - "alternative PIDs", - "PIDs", - ], - "comments": ["The project `id` should not appear in this list."], - "domain_of": ["Entity", "DataCollection"], - } - }, - ) - alt_titles: Optional[list[Name]] = Field( - default=None, - description="""Alternative versions of the title/name of a project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_titles", - "aliases": ["alternative titles"], - "comments": ["The project `title` should not appear in this list."], - "domain_of": ["DataCollection"], - } - }, - ) - url: str = Field( - default=..., - description="""Permanent resolvable URI for the collection at the data source.""", - json_schema_extra={ - "linkml_meta": {"alias": "url", "domain_of": ["DataCollection"]} - }, - ) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'comments': ['May be equivalent to FundingReference in the CreditMetadata ' + 'schema.'], + 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) + + id: Optional[str] = Field(default=None, description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', + 'aliases': ['proposal ID', 'project ID'], + 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' + 'that IDs will be unique between all the BER sources.'], + 'domain_of': ['Entity', 'DataCollection'], + 'slot_uri': 'schema:identifier'} }) + title: Optional[str] = Field(default=None, description="""Human-readable string representing the project.""", json_schema_extra = { "linkml_meta": {'alias': 'title', + 'aliases': ['name'], + 'domain_of': ['DataCollection'], + 'slot_uri': 'schema:name'} }) + description: Optional[str] = Field(default=None, description="""Textual description of the project.""", json_schema_extra = { "linkml_meta": {'alias': 'description', + 'domain_of': ['Entity', 'DataCollection'], + 'slot_uri': 'schema:description'} }) + alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', + 'aliases': ['CURIEs', + 'database cross-references', + 'dbxrefs', + 'IDs', + 'alternative identifiers', + 'alternative IDs', + 'alternative PIDs', + 'PIDs'], + 'comments': ['The project `id` should not appear in this list.'], + 'domain_of': ['Entity', 'DataCollection']} }) + alt_titles: Optional[list[Name]] = Field(default=None, description="""Alternative versions of the title/name of a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_titles', + 'aliases': ['alternative titles'], + 'comments': ['The project `title` should not appear in this list.'], + 'domain_of': ['DataCollection']} }) + url: str = Field(default=..., description="""Permanent resolvable URI for the collection at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'url', 'domain_of': ['DataCollection']} }) # Model rebuild @@ -623,3 +332,4 @@ class DataCollection(ConfiguredBaseModel): Coordinates.model_rebuild() Name.model_rebuild() DataCollection.model_rebuild() + From 009cb900999678e2919d715d9b707659deb3a10b Mon Sep 17 00:00:00 2001 From: shreddd Date: Fri, 13 Jun 2025 09:28:50 -0700 Subject: [PATCH 15/20] typos --- src/bertron_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bertron_client.py b/src/bertron_client.py index 3d42fe0..d5882a7 100644 --- a/src/bertron_client.py +++ b/src/bertron_client.py @@ -340,8 +340,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Geographic search near Seattle print("\nSearching near Florida (10km radius)...") - seattle_entities = client.get_entities_in_region(28.1, -81.4, 100) - print(f"Found {seattle_entities.count} entities near Florida") + florida_entities = client.get_entities_in_region(28.1, -81.4, 100) + print(f"Found {florida_entities.count} entities near Florida") # Bounding box search for Yellowstone region print("\nSearching Yellowstone region region...") From 788bbef5dd0c54c34eaed3bb35e5149e601b2d3d Mon Sep 17 00:00:00 2001 From: shreddd Date: Fri, 13 Jun 2025 10:50:01 -0700 Subject: [PATCH 16/20] update pydantic model --- src/bertron_schema_pydantic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bertron_schema_pydantic.py b/src/bertron_schema_pydantic.py index e1aefe7..5ff95b5 100644 --- a/src/bertron_schema_pydantic.py +++ b/src/bertron_schema_pydantic.py @@ -105,6 +105,7 @@ class EntityType(str, Enum): sample = "sample" sequence = "sequence" taxon = "taxon" + unspecified = "unspecified" class NameType(str, Enum): From d5e77b3d71308ebf1933ca55c594e581f3924d7b Mon Sep 17 00:00:00 2001 From: shreddd Date: Fri, 13 Jun 2025 10:53:05 -0700 Subject: [PATCH 17/20] ruff --- src/bertron_client.py | 2 +- src/bertron_schema_pydantic.py | 710 +++++++++++++++++++++++---------- 2 files changed, 501 insertions(+), 211 deletions(-) diff --git a/src/bertron_client.py b/src/bertron_client.py index d5882a7..e9fbed8 100644 --- a/src/bertron_client.py +++ b/src/bertron_client.py @@ -217,7 +217,7 @@ def find_entities_in_bounding_box( metadata = { "bounding_box": { "southwest": {"latitude": southwest_lat, "longitude": southwest_lng}, - "northeast": {"latitude": northeast_lat, "longitude": northeast_lng} + "northeast": {"latitude": northeast_lat, "longitude": northeast_lng}, } } diff --git a/src/bertron_schema_pydantic.py b/src/bertron_schema_pydantic.py index 5ff95b5..a529de1 100644 --- a/src/bertron_schema_pydantic.py +++ b/src/bertron_schema_pydantic.py @@ -1,29 +1,13 @@ -from __future__ import annotations +from __future__ import annotations import re import sys -from datetime import ( - date, - datetime, - time -) -from decimal import Decimal -from enum import Enum -from typing import ( - Any, - ClassVar, - Literal, - Optional, - Union -) +from datetime import date, datetime, time +from decimal import Decimal +from enum import Enum +from typing import Any, ClassVar, Literal, Optional, Union -from pydantic import ( - BaseModel, - ConfigDict, - Field, - RootModel, - field_validator -) +from pydantic import BaseModel, ConfigDict, Field, RootModel, field_validator metamodel_version = "None" @@ -32,63 +16,81 @@ class ConfiguredBaseModel(BaseModel): model_config = ConfigDict( - validate_assignment = True, - validate_default = True, - extra = "forbid", - arbitrary_types_allowed = True, - use_enum_values = True, - strict = False, + validate_assignment=True, + validate_default=True, + extra="forbid", + arbitrary_types_allowed=True, + use_enum_values=True, + strict=False, ) pass - - class LinkMLMeta(RootModel): root: dict[str, Any] = {} model_config = ConfigDict(frozen=True) - def __getattr__(self, key:str): + def __getattr__(self, key: str): return getattr(self.root, key) - def __getitem__(self, key:str): + def __getitem__(self, key: str): return self.root[key] - def __setitem__(self, key:str, value): + def __setitem__(self, key: str, value): self.root[key] = value - def __contains__(self, key:str) -> bool: + def __contains__(self, key: str) -> bool: return key in self.root -linkml_meta = LinkMLMeta({'default_curi_maps': ['semweb_context'], - 'default_prefix': 'bertron', - 'default_range': 'string', - 'description': 'Schema for BERtron common data model.', - 'id': 'https://w3id.org/ber-data/bertron-schema', - 'imports': ['linkml:types', 'bertron_types'], - 'license': 'BSD-3', - 'name': 'bertron-schema', - 'prefixes': {'MIXS': {'prefix_prefix': 'MIXS', - 'prefix_reference': 'https://w3id.org/mixs/'}, - 'UO': {'prefix_prefix': 'UO', - 'prefix_reference': 'http://purl.obolibrary.org/obo/UO_'}, - 'WGS84': {'prefix_prefix': 'WGS84', - 'prefix_reference': 'http://www.w3.org/2003/01/geo/wgs84_pos#'}, - 'bertron': {'prefix_prefix': 'bertron', - 'prefix_reference': 'https://w3id.org/ber-data/bertron-schema/'}, - 'linkml': {'prefix_prefix': 'linkml', - 'prefix_reference': 'https://w3id.org/linkml/'}, - 'schema': {'prefix_prefix': 'schema', - 'prefix_reference': 'http://schema.org/'}}, - 'see_also': ['https://ber-data.github.io/bertron-schema'], - 'source_file': 'src/schema/linkml/bertron_schema.yaml', - 'title': 'BERtron schema'} ) +linkml_meta = LinkMLMeta( + { + "default_curi_maps": ["semweb_context"], + "default_prefix": "bertron", + "default_range": "string", + "description": "Schema for BERtron common data model.", + "id": "https://w3id.org/ber-data/bertron-schema", + "imports": ["linkml:types", "bertron_types"], + "license": "BSD-3", + "name": "bertron-schema", + "prefixes": { + "MIXS": { + "prefix_prefix": "MIXS", + "prefix_reference": "https://w3id.org/mixs/", + }, + "UO": { + "prefix_prefix": "UO", + "prefix_reference": "http://purl.obolibrary.org/obo/UO_", + }, + "WGS84": { + "prefix_prefix": "WGS84", + "prefix_reference": "http://www.w3.org/2003/01/geo/wgs84_pos#", + }, + "bertron": { + "prefix_prefix": "bertron", + "prefix_reference": "https://w3id.org/ber-data/bertron-schema/", + }, + "linkml": { + "prefix_prefix": "linkml", + "prefix_reference": "https://w3id.org/linkml/", + }, + "schema": { + "prefix_prefix": "schema", + "prefix_reference": "http://schema.org/", + }, + }, + "see_also": ["https://ber-data.github.io/bertron-schema"], + "source_file": "src/schema/linkml/bertron_schema.yaml", + "title": "BERtron schema", + } +) + class BERSourceType(str, Enum): """ The BER data source from whence the entity originated. """ + EMSL = "EMSL" ESS_DIVE = "ESS-DIVE" JGI = "JGI" @@ -100,6 +102,7 @@ class EntityType(str, Enum): """ Tags used to describe an entity. """ + biodata = "biodata" jgi_biosample = "jgi_biosample" sample = "sample" @@ -112,6 +115,7 @@ class NameType(str, Enum): """ The relationship between a name and a synonym of that name. """ + broad_synonym = "broad_synonym" """ The synonym refers to a broader group of entities than the name. @@ -134,55 +138,123 @@ class NameType(str, Enum): """ - class AttributeValue(ConfiguredBaseModel): """ The value for any value of a attribute for a sample. This object can hold both the un-normalized atomic value and the structured value """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'abstract': True, - 'class_uri': 'nmdc:AttributeValue', - 'from_schema': 'https://w3id.org/ber-data/bertron_types'}) - has_raw_value: Optional[str] = Field(default=None, description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', - 'domain_of': ['AttributeValue', 'QuantityValue'], - 'mappings': ['nmdc:has_raw_value']} }) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "abstract": True, + "class_uri": "nmdc:AttributeValue", + "from_schema": "https://w3id.org/ber-data/bertron_types", + } + ) + + has_raw_value: Optional[str] = Field( + default=None, + description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_raw_value", + "domain_of": ["AttributeValue", "QuantityValue"], + "mappings": ["nmdc:has_raw_value"], + } + }, + ) class QuantityValue(AttributeValue): """ A simple quantity, e.g. 2cm """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'nmdc:QuantityValue', - 'from_schema': 'https://w3id.org/ber-data/bertron_types', - 'mappings': ['schema:QuantityValue'], - 'slot_usage': {'has_numeric_value': {'description': 'The number part of the ' - 'quantity', - 'name': 'has_numeric_value'}, - 'has_raw_value': {'description': 'Unnormalized atomic string ' - 'representation, should in ' - 'syntax {number} {unit}', - 'name': 'has_raw_value'}, - 'has_unit': {'description': 'The unit of the quantity', - 'name': 'has_unit'}}}) - - has_maximum_numeric_value: Optional[float] = Field(default=None, description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_maximum_numeric_value', - 'domain_of': ['QuantityValue'], - 'is_a': 'has_numeric_value', - 'mappings': ['nmdc:has_maximum_numeric_value']} }) - has_minimum_numeric_value: Optional[float] = Field(default=None, description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", json_schema_extra = { "linkml_meta": {'alias': 'has_minimum_numeric_value', - 'domain_of': ['QuantityValue'], - 'is_a': 'has_numeric_value', - 'mappings': ['nmdc:has_minimum_numeric_value']} }) - has_numeric_value: Optional[float] = Field(default=None, description="""The number part of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_numeric_value', - 'domain_of': ['QuantityValue'], - 'mappings': ['nmdc:has_numeric_value', 'qud:quantityValue', 'schema:value']} }) - has_raw_value: Optional[str] = Field(default=None, description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", json_schema_extra = { "linkml_meta": {'alias': 'has_raw_value', - 'domain_of': ['AttributeValue', 'QuantityValue'], - 'mappings': ['nmdc:has_raw_value']} }) - has_unit: Optional[str] = Field(default=None, description="""The unit of the quantity""", json_schema_extra = { "linkml_meta": {'alias': 'has_unit', - 'aliases': ['scale'], - 'domain_of': ['QuantityValue'], - 'mappings': ['nmdc:has_unit', 'qud:unit', 'schema:unitCode']} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "class_uri": "nmdc:QuantityValue", + "from_schema": "https://w3id.org/ber-data/bertron_types", + "mappings": ["schema:QuantityValue"], + "slot_usage": { + "has_numeric_value": { + "description": "The number part of the quantity", + "name": "has_numeric_value", + }, + "has_raw_value": { + "description": "Unnormalized atomic string " + "representation, should in " + "syntax {number} {unit}", + "name": "has_raw_value", + }, + "has_unit": { + "description": "The unit of the quantity", + "name": "has_unit", + }, + }, + } + ) + + has_maximum_numeric_value: Optional[float] = Field( + default=None, + description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_maximum_numeric_value", + "domain_of": ["QuantityValue"], + "is_a": "has_numeric_value", + "mappings": ["nmdc:has_maximum_numeric_value"], + } + }, + ) + has_minimum_numeric_value: Optional[float] = Field( + default=None, + description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_minimum_numeric_value", + "domain_of": ["QuantityValue"], + "is_a": "has_numeric_value", + "mappings": ["nmdc:has_minimum_numeric_value"], + } + }, + ) + has_numeric_value: Optional[float] = Field( + default=None, + description="""The number part of the quantity""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_numeric_value", + "domain_of": ["QuantityValue"], + "mappings": [ + "nmdc:has_numeric_value", + "qud:quantityValue", + "schema:value", + ], + } + }, + ) + has_raw_value: Optional[str] = Field( + default=None, + description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_raw_value", + "domain_of": ["AttributeValue", "QuantityValue"], + "mappings": ["nmdc:has_raw_value"], + } + }, + ) + has_unit: Optional[str] = Field( + default=None, + description="""The unit of the quantity""", + json_schema_extra={ + "linkml_meta": { + "alias": "has_unit", + "aliases": ["scale"], + "domain_of": ["QuantityValue"], + "mappings": ["nmdc:has_unit", "qud:unit", "schema:unitCode"], + } + }, + ) class Entity(ConfiguredBaseModel): @@ -190,139 +262,358 @@ class Entity(ConfiguredBaseModel): An object retrieved by BERtron from a BER data API. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'class_uri': 'schema:Thing', - 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - - ber_data_source: BERSourceType = Field(default=..., description="""The BER member from whence the entity originated.""", json_schema_extra = { "linkml_meta": {'alias': 'ber_data_source', 'domain_of': ['Entity']} }) - coordinates: Coordinates = Field(default=..., description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", json_schema_extra = { "linkml_meta": {'alias': 'coordinates', 'domain_of': ['Entity']} }) - entity_type: list[EntityType] = Field(default=..., description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", json_schema_extra = { "linkml_meta": {'alias': 'entity_type', 'domain_of': ['Entity']} }) - description: Optional[str] = Field(default=None, description="""Textual description of the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'description', - 'domain_of': ['Entity', 'DataCollection'], - 'examples': [{'value': 'River water sample taken by AquaTROLL 9000.'}, - {'value': 'Genome sequence of P. aeruginosa strain IDDQD'}], - 'slot_uri': 'schema:description'} }) - id: Optional[str] = Field(default=None, description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', - 'aliases': ['BER data source internal identifier', 'CURIE'], - 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' - 'that IDs will be unique between all the BER sources.'], - 'domain_of': ['Entity', 'DataCollection'], - 'slot_uri': 'schema:identifier'} }) - name: Optional[str] = Field(default=None, description="""Human-readable string representing an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name', - 'domain_of': ['Entity', 'Name'], - 'examples': [{'value': 'Pseudomonas aeruginosa strain IDDQD'}, - {'value': 'Soil core FW-106'}], - 'slot_uri': 'schema:name'} }) - alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', - 'aliases': ['CURIEs', - 'database cross-references', - 'dbxrefs', - 'IDs', - 'alternative identifiers', - 'alternative IDs', - 'alternative PIDs', - 'PIDs'], - 'comments': ['The entity `id` should not appear in this list.'], - 'domain_of': ['Entity', 'DataCollection'], - 'examples': [{'value': 'NCBItaxon:172684329'}, {'value': 'ISGN:1986497'}]} }) - alt_names: Optional[list[Name]] = Field(default=None, description="""Textual identifiers for an entity.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_names', - 'aliases': ['alternative names', 'synonyms'], - 'comments': ['The entity `name` should not appear in this list.'], - 'domain_of': ['Entity']} }) - part_of_collection: Optional[list[DataCollection]] = Field(default=None, description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", json_schema_extra = { "linkml_meta": {'alias': 'part_of_collection', 'domain_of': ['Entity']} }) - uri: str = Field(default=..., description="""Permanent resolvable URI for the entity at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'uri', 'aliases': ['url'], 'domain_of': ['Entity']} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "class_uri": "schema:Thing", + "from_schema": "https://w3id.org/ber-data/bertron-schema", + } + ) + + ber_data_source: BERSourceType = Field( + default=..., + description="""The BER member from whence the entity originated.""", + json_schema_extra={ + "linkml_meta": {"alias": "ber_data_source", "domain_of": ["Entity"]} + }, + ) + coordinates: Coordinates = Field( + default=..., + description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", + json_schema_extra={ + "linkml_meta": {"alias": "coordinates", "domain_of": ["Entity"]} + }, + ) + entity_type: list[EntityType] = Field( + default=..., + description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", + json_schema_extra={ + "linkml_meta": {"alias": "entity_type", "domain_of": ["Entity"]} + }, + ) + description: Optional[str] = Field( + default=None, + description="""Textual description of the entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "description", + "domain_of": ["Entity", "DataCollection"], + "examples": [ + {"value": "River water sample taken by AquaTROLL 9000."}, + {"value": "Genome sequence of P. aeruginosa strain IDDQD"}, + ], + "slot_uri": "schema:description", + } + }, + ) + id: Optional[str] = Field( + default=None, + description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", + json_schema_extra={ + "linkml_meta": { + "alias": "id", + "aliases": ["BER data source internal identifier", "CURIE"], + "comments": [ + "If the data source does not use CURIEs, we cannot guarantee " + "that IDs will be unique between all the BER sources." + ], + "domain_of": ["Entity", "DataCollection"], + "slot_uri": "schema:identifier", + } + }, + ) + name: Optional[str] = Field( + default=None, + description="""Human-readable string representing an entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "name", + "domain_of": ["Entity", "Name"], + "examples": [ + {"value": "Pseudomonas aeruginosa strain IDDQD"}, + {"value": "Soil core FW-106"}, + ], + "slot_uri": "schema:name", + } + }, + ) + alt_ids: Optional[list[str]] = Field( + default=None, + description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_ids", + "aliases": [ + "CURIEs", + "database cross-references", + "dbxrefs", + "IDs", + "alternative identifiers", + "alternative IDs", + "alternative PIDs", + "PIDs", + ], + "comments": ["The entity `id` should not appear in this list."], + "domain_of": ["Entity", "DataCollection"], + "examples": [ + {"value": "NCBItaxon:172684329"}, + {"value": "ISGN:1986497"}, + ], + } + }, + ) + alt_names: Optional[list[Name]] = Field( + default=None, + description="""Textual identifiers for an entity.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_names", + "aliases": ["alternative names", "synonyms"], + "comments": ["The entity `name` should not appear in this list."], + "domain_of": ["Entity"], + } + }, + ) + part_of_collection: Optional[list[DataCollection]] = Field( + default=None, + description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", + json_schema_extra={ + "linkml_meta": {"alias": "part_of_collection", "domain_of": ["Entity"]} + }, + ) + uri: str = Field( + default=..., + description="""Permanent resolvable URI for the entity at the data source.""", + json_schema_extra={ + "linkml_meta": {"alias": "uri", "aliases": ["url"], "domain_of": ["Entity"]} + }, + ) class Coordinates(ConfiguredBaseModel): """ The coordinates defining the position associated with the entity. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - - altitude: Optional[QuantityValue] = Field(default=None, title="altitude", description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", json_schema_extra = { "linkml_meta": {'alias': 'altitude', - 'annotations': {'expected_value': {'tag': 'expected_value', - 'value': 'measurement value'}}, - 'domain_of': ['Coordinates'], - 'examples': [{'value': '100 meter'}], - 'slot_uri': 'MIXS:0000094'} }) - depth: Optional[QuantityValue] = Field(default=None, title="depth", description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", json_schema_extra = { "linkml_meta": {'alias': 'depth', - 'aliases': ['depth'], - 'annotations': {'expected_value': {'tag': 'expected_value', - 'value': 'measurement value'}}, - 'domain_of': ['Coordinates'], - 'examples': [{'value': '10 meter'}], - 'slot_uri': 'MIXS:0000018'} }) - elevation: Optional[QuantityValue] = Field(default=None, title="elevation", description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", json_schema_extra = { "linkml_meta": {'alias': 'elevation', - 'aliases': ['elevation'], - 'annotations': {'expected_value': {'tag': 'expected_value', - 'value': 'measurement value'}}, - 'domain_of': ['Coordinates'], - 'examples': [{'value': '100 meter'}], - 'slot_uri': 'MIXS:0000093'} }) - latitude: float = Field(default=..., description="""latitude""", json_schema_extra = { "linkml_meta": {'alias': 'latitude', - 'broad_mappings': ['MIXS:0000009'], - 'domain_of': ['Coordinates'], - 'examples': [{'value': '-33.460524'}], - 'mappings': ['schema:latitude'], - 'slot_uri': 'WGS84:lat'} }) - longitude: float = Field(default=..., description="""longitude""", json_schema_extra = { "linkml_meta": {'alias': 'longitude', - 'broad_mappings': ['MIXS:0000009'], - 'domain_of': ['Coordinates'], - 'examples': [{'value': '150.168149'}], - 'mappings': ['schema:longitude'], - 'slot_uri': 'WGS84:long'} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + {"from_schema": "https://w3id.org/ber-data/bertron-schema"} + ) + + altitude: Optional[QuantityValue] = Field( + default=None, + title="altitude", + description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", + json_schema_extra={ + "linkml_meta": { + "alias": "altitude", + "annotations": { + "expected_value": { + "tag": "expected_value", + "value": "measurement value", + } + }, + "domain_of": ["Coordinates"], + "examples": [{"value": "100 meter"}], + "slot_uri": "MIXS:0000094", + } + }, + ) + depth: Optional[QuantityValue] = Field( + default=None, + title="depth", + description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", + json_schema_extra={ + "linkml_meta": { + "alias": "depth", + "aliases": ["depth"], + "annotations": { + "expected_value": { + "tag": "expected_value", + "value": "measurement value", + } + }, + "domain_of": ["Coordinates"], + "examples": [{"value": "10 meter"}], + "slot_uri": "MIXS:0000018", + } + }, + ) + elevation: Optional[QuantityValue] = Field( + default=None, + title="elevation", + description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", + json_schema_extra={ + "linkml_meta": { + "alias": "elevation", + "aliases": ["elevation"], + "annotations": { + "expected_value": { + "tag": "expected_value", + "value": "measurement value", + } + }, + "domain_of": ["Coordinates"], + "examples": [{"value": "100 meter"}], + "slot_uri": "MIXS:0000093", + } + }, + ) + latitude: float = Field( + default=..., + description="""latitude""", + json_schema_extra={ + "linkml_meta": { + "alias": "latitude", + "broad_mappings": ["MIXS:0000009"], + "domain_of": ["Coordinates"], + "examples": [{"value": "-33.460524"}], + "mappings": ["schema:latitude"], + "slot_uri": "WGS84:lat", + } + }, + ) + longitude: float = Field( + default=..., + description="""longitude""", + json_schema_extra={ + "linkml_meta": { + "alias": "longitude", + "broad_mappings": ["MIXS:0000009"], + "domain_of": ["Coordinates"], + "examples": [{"value": "150.168149"}], + "mappings": ["schema:longitude"], + "slot_uri": "WGS84:long", + } + }, + ) class Name(ConfiguredBaseModel): """ The name or label for an entity. This may be a primary name, alternative name, synonym, acronym, or any other label used to refer to an entity. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - name_type: Optional[NameType] = Field(default=None, description="""Brief description of the name and/or its relationship to the entity.""", json_schema_extra = { "linkml_meta": {'alias': 'name_type', 'domain_of': ['Name']} }) - name: str = Field(default=..., description="""The string used as a name.""", json_schema_extra = { "linkml_meta": {'alias': 'name', - 'domain_of': ['Entity', 'Name'], - 'examples': [{'value': 'Heat-inducible transcription repressor HrcA'}, - {'value': 'FW106 groundwater metagenome'}], - 'slot_uri': 'schema:name'} }) + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + {"from_schema": "https://w3id.org/ber-data/bertron-schema"} + ) + + name_type: Optional[NameType] = Field( + default=None, + description="""Brief description of the name and/or its relationship to the entity.""", + json_schema_extra={ + "linkml_meta": {"alias": "name_type", "domain_of": ["Name"]} + }, + ) + name: str = Field( + default=..., + description="""The string used as a name.""", + json_schema_extra={ + "linkml_meta": { + "alias": "name", + "domain_of": ["Entity", "Name"], + "examples": [ + {"value": "Heat-inducible transcription repressor HrcA"}, + {"value": "FW106 groundwater metagenome"}, + ], + "slot_uri": "schema:name", + } + }, + ) class DataCollection(ConfiguredBaseModel): """ Administrative unit (e.g. project, proposal, etc.) in which one or more entities is collected. """ - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'comments': ['May be equivalent to FundingReference in the CreditMetadata ' - 'schema.'], - 'from_schema': 'https://w3id.org/ber-data/bertron-schema'}) - - id: Optional[str] = Field(default=None, description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", json_schema_extra = { "linkml_meta": {'alias': 'id', - 'aliases': ['proposal ID', 'project ID'], - 'comments': ['If the data source does not use CURIEs, we cannot guarantee ' - 'that IDs will be unique between all the BER sources.'], - 'domain_of': ['Entity', 'DataCollection'], - 'slot_uri': 'schema:identifier'} }) - title: Optional[str] = Field(default=None, description="""Human-readable string representing the project.""", json_schema_extra = { "linkml_meta": {'alias': 'title', - 'aliases': ['name'], - 'domain_of': ['DataCollection'], - 'slot_uri': 'schema:name'} }) - description: Optional[str] = Field(default=None, description="""Textual description of the project.""", json_schema_extra = { "linkml_meta": {'alias': 'description', - 'domain_of': ['Entity', 'DataCollection'], - 'slot_uri': 'schema:description'} }) - alt_ids: Optional[list[str]] = Field(default=None, description="""Fully-qualified URI or CURIE used as an identifier for a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_ids', - 'aliases': ['CURIEs', - 'database cross-references', - 'dbxrefs', - 'IDs', - 'alternative identifiers', - 'alternative IDs', - 'alternative PIDs', - 'PIDs'], - 'comments': ['The project `id` should not appear in this list.'], - 'domain_of': ['Entity', 'DataCollection']} }) - alt_titles: Optional[list[Name]] = Field(default=None, description="""Alternative versions of the title/name of a project.""", json_schema_extra = { "linkml_meta": {'alias': 'alt_titles', - 'aliases': ['alternative titles'], - 'comments': ['The project `title` should not appear in this list.'], - 'domain_of': ['DataCollection']} }) - url: str = Field(default=..., description="""Permanent resolvable URI for the collection at the data source.""", json_schema_extra = { "linkml_meta": {'alias': 'url', 'domain_of': ['DataCollection']} }) + + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( + { + "comments": [ + "May be equivalent to FundingReference in the CreditMetadata schema." + ], + "from_schema": "https://w3id.org/ber-data/bertron-schema", + } + ) + + id: Optional[str] = Field( + default=None, + description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", + json_schema_extra={ + "linkml_meta": { + "alias": "id", + "aliases": ["proposal ID", "project ID"], + "comments": [ + "If the data source does not use CURIEs, we cannot guarantee " + "that IDs will be unique between all the BER sources." + ], + "domain_of": ["Entity", "DataCollection"], + "slot_uri": "schema:identifier", + } + }, + ) + title: Optional[str] = Field( + default=None, + description="""Human-readable string representing the project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "title", + "aliases": ["name"], + "domain_of": ["DataCollection"], + "slot_uri": "schema:name", + } + }, + ) + description: Optional[str] = Field( + default=None, + description="""Textual description of the project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "description", + "domain_of": ["Entity", "DataCollection"], + "slot_uri": "schema:description", + } + }, + ) + alt_ids: Optional[list[str]] = Field( + default=None, + description="""Fully-qualified URI or CURIE used as an identifier for a project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_ids", + "aliases": [ + "CURIEs", + "database cross-references", + "dbxrefs", + "IDs", + "alternative identifiers", + "alternative IDs", + "alternative PIDs", + "PIDs", + ], + "comments": ["The project `id` should not appear in this list."], + "domain_of": ["Entity", "DataCollection"], + } + }, + ) + alt_titles: Optional[list[Name]] = Field( + default=None, + description="""Alternative versions of the title/name of a project.""", + json_schema_extra={ + "linkml_meta": { + "alias": "alt_titles", + "aliases": ["alternative titles"], + "comments": ["The project `title` should not appear in this list."], + "domain_of": ["DataCollection"], + } + }, + ) + url: str = Field( + default=..., + description="""Permanent resolvable URI for the collection at the data source.""", + json_schema_extra={ + "linkml_meta": {"alias": "url", "domain_of": ["DataCollection"]} + }, + ) # Model rebuild @@ -333,4 +624,3 @@ class DataCollection(ConfiguredBaseModel): Coordinates.model_rebuild() Name.model_rebuild() DataCollection.model_rebuild() - From 7c8b2dda39ed0effe5ab10c508d448ce657de180 Mon Sep 17 00:00:00 2001 From: shreddd Date: Fri, 13 Jun 2025 11:12:04 -0700 Subject: [PATCH 18/20] fix linting --- src/bertron_client.py | 4 ++-- src/bertron_schema_pydantic.py | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/bertron_client.py b/src/bertron_client.py index e9fbed8..7a5668a 100644 --- a/src/bertron_client.py +++ b/src/bertron_client.py @@ -7,10 +7,10 @@ """ import requests -from typing import List, Dict, Any, Optional, Union +from typing import List, Dict, Any, Optional from dataclasses import dataclass import logging -from urllib.parse import urljoin, urlencode +from urllib.parse import urljoin # Import pydantic Entity from bertron_schema_pydantic from bertron_schema_pydantic import Entity diff --git a/src/bertron_schema_pydantic.py b/src/bertron_schema_pydantic.py index a529de1..c8e16c6 100644 --- a/src/bertron_schema_pydantic.py +++ b/src/bertron_schema_pydantic.py @@ -1,13 +1,9 @@ from __future__ import annotations -import re -import sys -from datetime import date, datetime, time -from decimal import Decimal from enum import Enum -from typing import Any, ClassVar, Literal, Optional, Union +from typing import Any, ClassVar, Optional -from pydantic import BaseModel, ConfigDict, Field, RootModel, field_validator +from pydantic import BaseModel, ConfigDict, Field, RootModel metamodel_version = "None" From e0ee8b5e1855d9a3d910153a359def8c9549b0d6 Mon Sep 17 00:00:00 2001 From: shreddd Date: Fri, 13 Jun 2025 16:21:09 -0700 Subject: [PATCH 19/20] Switch to bertron-schema installed via pip from git --- pyproject.toml | 3 +- src/bertron_client.py | 2 +- src/bertron_schema_pydantic.py | 622 --------------------- src/server.py | 2 +- uv.lock | 979 ++++++++++++++++++++++++++++++++- 5 files changed, 982 insertions(+), 626 deletions(-) delete mode 100644 src/bertron_schema_pydantic.py diff --git a/pyproject.toml b/pyproject.toml index d0ca180..231401c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,8 +12,9 @@ authors = [ ] description = " Cross-BER Data Integration" readme = "README.md" -requires-python = ">=3.12,<3.14" +requires-python = ">=3.12.9,<3.14" dependencies = [ + "bertron-schema @ git+https://github.com/ber-data/bertron-schema.git", "dtspy @ https://github.com/kbase/dtspy/archive/730828cff3924fc4b2215fe5c1b67bc04aad377f.tar.gz", "fastapi[standard]>=0.115.12", "jsonschema>=4.0.0", diff --git a/src/bertron_client.py b/src/bertron_client.py index 7a5668a..07fc788 100644 --- a/src/bertron_client.py +++ b/src/bertron_client.py @@ -13,7 +13,7 @@ from urllib.parse import urljoin # Import pydantic Entity from bertron_schema_pydantic -from bertron_schema_pydantic import Entity +from schema.datamodel.bertron_schema_pydantic import Entity # Set up logging logging.basicConfig(level=logging.INFO) diff --git a/src/bertron_schema_pydantic.py b/src/bertron_schema_pydantic.py deleted file mode 100644 index c8e16c6..0000000 --- a/src/bertron_schema_pydantic.py +++ /dev/null @@ -1,622 +0,0 @@ -from __future__ import annotations - -from enum import Enum -from typing import Any, ClassVar, Optional - -from pydantic import BaseModel, ConfigDict, Field, RootModel - - -metamodel_version = "None" -version = "0.0.2" - - -class ConfiguredBaseModel(BaseModel): - model_config = ConfigDict( - validate_assignment=True, - validate_default=True, - extra="forbid", - arbitrary_types_allowed=True, - use_enum_values=True, - strict=False, - ) - pass - - -class LinkMLMeta(RootModel): - root: dict[str, Any] = {} - model_config = ConfigDict(frozen=True) - - def __getattr__(self, key: str): - return getattr(self.root, key) - - def __getitem__(self, key: str): - return self.root[key] - - def __setitem__(self, key: str, value): - self.root[key] = value - - def __contains__(self, key: str) -> bool: - return key in self.root - - -linkml_meta = LinkMLMeta( - { - "default_curi_maps": ["semweb_context"], - "default_prefix": "bertron", - "default_range": "string", - "description": "Schema for BERtron common data model.", - "id": "https://w3id.org/ber-data/bertron-schema", - "imports": ["linkml:types", "bertron_types"], - "license": "BSD-3", - "name": "bertron-schema", - "prefixes": { - "MIXS": { - "prefix_prefix": "MIXS", - "prefix_reference": "https://w3id.org/mixs/", - }, - "UO": { - "prefix_prefix": "UO", - "prefix_reference": "http://purl.obolibrary.org/obo/UO_", - }, - "WGS84": { - "prefix_prefix": "WGS84", - "prefix_reference": "http://www.w3.org/2003/01/geo/wgs84_pos#", - }, - "bertron": { - "prefix_prefix": "bertron", - "prefix_reference": "https://w3id.org/ber-data/bertron-schema/", - }, - "linkml": { - "prefix_prefix": "linkml", - "prefix_reference": "https://w3id.org/linkml/", - }, - "schema": { - "prefix_prefix": "schema", - "prefix_reference": "http://schema.org/", - }, - }, - "see_also": ["https://ber-data.github.io/bertron-schema"], - "source_file": "src/schema/linkml/bertron_schema.yaml", - "title": "BERtron schema", - } -) - - -class BERSourceType(str, Enum): - """ - The BER data source from whence the entity originated. - """ - - EMSL = "EMSL" - ESS_DIVE = "ESS-DIVE" - JGI = "JGI" - MONET = "MONET" - NMDC = "NMDC" - - -class EntityType(str, Enum): - """ - Tags used to describe an entity. - """ - - biodata = "biodata" - jgi_biosample = "jgi_biosample" - sample = "sample" - sequence = "sequence" - taxon = "taxon" - unspecified = "unspecified" - - -class NameType(str, Enum): - """ - The relationship between a name and a synonym of that name. - """ - - broad_synonym = "broad_synonym" - """ - The synonym refers to a broader group of entities than the name. - """ - exact_synonym = "exact_synonym" - """ - String with exactly the same meaning and connotations as the original name. - """ - narrow_synonym = "narrow_synonym" - """ - The synonym refers to a narrower group of entities than the name. - """ - related_synonym = "related_synonym" - """ - The synonym has overlap with the name but the precise relationship is not defined. - """ - acronym = "acronym" - """ - An acronym or abbreviation for the name. - """ - - -class AttributeValue(ConfiguredBaseModel): - """ - The value for any value of a attribute for a sample. This object can hold both the un-normalized atomic value and the structured value - """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "abstract": True, - "class_uri": "nmdc:AttributeValue", - "from_schema": "https://w3id.org/ber-data/bertron_types", - } - ) - - has_raw_value: Optional[str] = Field( - default=None, - description="""The value that was specified for an annotation in raw form, i.e. a string. E.g. \"2 cm\" or \"2-4 cm\"""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_raw_value", - "domain_of": ["AttributeValue", "QuantityValue"], - "mappings": ["nmdc:has_raw_value"], - } - }, - ) - - -class QuantityValue(AttributeValue): - """ - A simple quantity, e.g. 2cm - """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "class_uri": "nmdc:QuantityValue", - "from_schema": "https://w3id.org/ber-data/bertron_types", - "mappings": ["schema:QuantityValue"], - "slot_usage": { - "has_numeric_value": { - "description": "The number part of the quantity", - "name": "has_numeric_value", - }, - "has_raw_value": { - "description": "Unnormalized atomic string " - "representation, should in " - "syntax {number} {unit}", - "name": "has_raw_value", - }, - "has_unit": { - "description": "The unit of the quantity", - "name": "has_unit", - }, - }, - } - ) - - has_maximum_numeric_value: Optional[float] = Field( - default=None, - description="""The maximum value part, expressed as number, of the quantity value when the value covers a range.""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_maximum_numeric_value", - "domain_of": ["QuantityValue"], - "is_a": "has_numeric_value", - "mappings": ["nmdc:has_maximum_numeric_value"], - } - }, - ) - has_minimum_numeric_value: Optional[float] = Field( - default=None, - description="""The minimum value part, expressed as number, of the quantity value when the value covers a range.""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_minimum_numeric_value", - "domain_of": ["QuantityValue"], - "is_a": "has_numeric_value", - "mappings": ["nmdc:has_minimum_numeric_value"], - } - }, - ) - has_numeric_value: Optional[float] = Field( - default=None, - description="""The number part of the quantity""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_numeric_value", - "domain_of": ["QuantityValue"], - "mappings": [ - "nmdc:has_numeric_value", - "qud:quantityValue", - "schema:value", - ], - } - }, - ) - has_raw_value: Optional[str] = Field( - default=None, - description="""Unnormalized atomic string representation, should in syntax {number} {unit}""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_raw_value", - "domain_of": ["AttributeValue", "QuantityValue"], - "mappings": ["nmdc:has_raw_value"], - } - }, - ) - has_unit: Optional[str] = Field( - default=None, - description="""The unit of the quantity""", - json_schema_extra={ - "linkml_meta": { - "alias": "has_unit", - "aliases": ["scale"], - "domain_of": ["QuantityValue"], - "mappings": ["nmdc:has_unit", "qud:unit", "schema:unitCode"], - } - }, - ) - - -class Entity(ConfiguredBaseModel): - """ - An object retrieved by BERtron from a BER data API. - - """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "class_uri": "schema:Thing", - "from_schema": "https://w3id.org/ber-data/bertron-schema", - } - ) - - ber_data_source: BERSourceType = Field( - default=..., - description="""The BER member from whence the entity originated.""", - json_schema_extra={ - "linkml_meta": {"alias": "ber_data_source", "domain_of": ["Entity"]} - }, - ) - coordinates: Coordinates = Field( - default=..., - description="""The geographic coordinates associated with an entity. For entities with a bounding box, the centroid is used as the geographic reference.""", - json_schema_extra={ - "linkml_meta": {"alias": "coordinates", "domain_of": ["Entity"]} - }, - ) - entity_type: list[EntityType] = Field( - default=..., - description="""What kind of entity is this -- e.g. sequence data; a soil core; a well; field site; sample; etc.""", - json_schema_extra={ - "linkml_meta": {"alias": "entity_type", "domain_of": ["Entity"]} - }, - ) - description: Optional[str] = Field( - default=None, - description="""Textual description of the entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "description", - "domain_of": ["Entity", "DataCollection"], - "examples": [ - {"value": "River water sample taken by AquaTROLL 9000."}, - {"value": "Genome sequence of P. aeruginosa strain IDDQD"}, - ], - "slot_uri": "schema:description", - } - }, - ) - id: Optional[str] = Field( - default=None, - description="""The unique ID used for the entity within the BER resource. It may not necessarily be resolvable outside the resource.""", - json_schema_extra={ - "linkml_meta": { - "alias": "id", - "aliases": ["BER data source internal identifier", "CURIE"], - "comments": [ - "If the data source does not use CURIEs, we cannot guarantee " - "that IDs will be unique between all the BER sources." - ], - "domain_of": ["Entity", "DataCollection"], - "slot_uri": "schema:identifier", - } - }, - ) - name: Optional[str] = Field( - default=None, - description="""Human-readable string representing an entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "name", - "domain_of": ["Entity", "Name"], - "examples": [ - {"value": "Pseudomonas aeruginosa strain IDDQD"}, - {"value": "Soil core FW-106"}, - ], - "slot_uri": "schema:name", - } - }, - ) - alt_ids: Optional[list[str]] = Field( - default=None, - description="""Fully-qualified URI or CURIE used as an identifier for an entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_ids", - "aliases": [ - "CURIEs", - "database cross-references", - "dbxrefs", - "IDs", - "alternative identifiers", - "alternative IDs", - "alternative PIDs", - "PIDs", - ], - "comments": ["The entity `id` should not appear in this list."], - "domain_of": ["Entity", "DataCollection"], - "examples": [ - {"value": "NCBItaxon:172684329"}, - {"value": "ISGN:1986497"}, - ], - } - }, - ) - alt_names: Optional[list[Name]] = Field( - default=None, - description="""Textual identifiers for an entity.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_names", - "aliases": ["alternative names", "synonyms"], - "comments": ["The entity `name` should not appear in this list."], - "domain_of": ["Entity"], - } - }, - ) - part_of_collection: Optional[list[DataCollection]] = Field( - default=None, - description="""Administrative collection (e.g. project, campaign, whatever) that the entity was generated as part of. May also be called a project.""", - json_schema_extra={ - "linkml_meta": {"alias": "part_of_collection", "domain_of": ["Entity"]} - }, - ) - uri: str = Field( - default=..., - description="""Permanent resolvable URI for the entity at the data source.""", - json_schema_extra={ - "linkml_meta": {"alias": "uri", "aliases": ["url"], "domain_of": ["Entity"]} - }, - ) - - -class Coordinates(ConfiguredBaseModel): - """ - The coordinates defining the position associated with the entity. - """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - {"from_schema": "https://w3id.org/ber-data/bertron-schema"} - ) - - altitude: Optional[QuantityValue] = Field( - default=None, - title="altitude", - description="""Altitude is a term used to identify heights of objects such as airplanes, space shuttles, rockets, atmospheric balloons and heights of places such as atmospheric layers and clouds. It is used to measure the height of an object which is above the earth's surface. In this context, the altitude measurement is the vertical distance between the earth's surface above sea level and the sampled position in the air""", - json_schema_extra={ - "linkml_meta": { - "alias": "altitude", - "annotations": { - "expected_value": { - "tag": "expected_value", - "value": "measurement value", - } - }, - "domain_of": ["Coordinates"], - "examples": [{"value": "100 meter"}], - "slot_uri": "MIXS:0000094", - } - }, - ) - depth: Optional[QuantityValue] = Field( - default=None, - title="depth", - description="""The vertical distance below local surface, e.g. for sediment or soil samples depth is measured from sediment or soil surface, respectively. Depth can be reported as an interval for subsurface samples.""", - json_schema_extra={ - "linkml_meta": { - "alias": "depth", - "aliases": ["depth"], - "annotations": { - "expected_value": { - "tag": "expected_value", - "value": "measurement value", - } - }, - "domain_of": ["Coordinates"], - "examples": [{"value": "10 meter"}], - "slot_uri": "MIXS:0000018", - } - }, - ) - elevation: Optional[QuantityValue] = Field( - default=None, - title="elevation", - description="""Elevation of the sampling site is its height above a fixed reference point, most commonly the mean sea level. Elevation is mainly used when referring to points on the earth's surface, while altitude is used for points above the surface, such as an aircraft in flight or a spacecraft in orbit.""", - json_schema_extra={ - "linkml_meta": { - "alias": "elevation", - "aliases": ["elevation"], - "annotations": { - "expected_value": { - "tag": "expected_value", - "value": "measurement value", - } - }, - "domain_of": ["Coordinates"], - "examples": [{"value": "100 meter"}], - "slot_uri": "MIXS:0000093", - } - }, - ) - latitude: float = Field( - default=..., - description="""latitude""", - json_schema_extra={ - "linkml_meta": { - "alias": "latitude", - "broad_mappings": ["MIXS:0000009"], - "domain_of": ["Coordinates"], - "examples": [{"value": "-33.460524"}], - "mappings": ["schema:latitude"], - "slot_uri": "WGS84:lat", - } - }, - ) - longitude: float = Field( - default=..., - description="""longitude""", - json_schema_extra={ - "linkml_meta": { - "alias": "longitude", - "broad_mappings": ["MIXS:0000009"], - "domain_of": ["Coordinates"], - "examples": [{"value": "150.168149"}], - "mappings": ["schema:longitude"], - "slot_uri": "WGS84:long", - } - }, - ) - - -class Name(ConfiguredBaseModel): - """ - The name or label for an entity. This may be a primary name, alternative name, synonym, acronym, or any other label used to refer to an entity. - """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - {"from_schema": "https://w3id.org/ber-data/bertron-schema"} - ) - - name_type: Optional[NameType] = Field( - default=None, - description="""Brief description of the name and/or its relationship to the entity.""", - json_schema_extra={ - "linkml_meta": {"alias": "name_type", "domain_of": ["Name"]} - }, - ) - name: str = Field( - default=..., - description="""The string used as a name.""", - json_schema_extra={ - "linkml_meta": { - "alias": "name", - "domain_of": ["Entity", "Name"], - "examples": [ - {"value": "Heat-inducible transcription repressor HrcA"}, - {"value": "FW106 groundwater metagenome"}, - ], - "slot_uri": "schema:name", - } - }, - ) - - -class DataCollection(ConfiguredBaseModel): - """ - Administrative unit (e.g. project, proposal, etc.) in which one or more entities is collected. - """ - - linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta( - { - "comments": [ - "May be equivalent to FundingReference in the CreditMetadata schema." - ], - "from_schema": "https://w3id.org/ber-data/bertron-schema", - } - ) - - id: Optional[str] = Field( - default=None, - description="""The unique ID used for the project within the BER resource. It may not necessarily be resolvable outside the resource.""", - json_schema_extra={ - "linkml_meta": { - "alias": "id", - "aliases": ["proposal ID", "project ID"], - "comments": [ - "If the data source does not use CURIEs, we cannot guarantee " - "that IDs will be unique between all the BER sources." - ], - "domain_of": ["Entity", "DataCollection"], - "slot_uri": "schema:identifier", - } - }, - ) - title: Optional[str] = Field( - default=None, - description="""Human-readable string representing the project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "title", - "aliases": ["name"], - "domain_of": ["DataCollection"], - "slot_uri": "schema:name", - } - }, - ) - description: Optional[str] = Field( - default=None, - description="""Textual description of the project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "description", - "domain_of": ["Entity", "DataCollection"], - "slot_uri": "schema:description", - } - }, - ) - alt_ids: Optional[list[str]] = Field( - default=None, - description="""Fully-qualified URI or CURIE used as an identifier for a project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_ids", - "aliases": [ - "CURIEs", - "database cross-references", - "dbxrefs", - "IDs", - "alternative identifiers", - "alternative IDs", - "alternative PIDs", - "PIDs", - ], - "comments": ["The project `id` should not appear in this list."], - "domain_of": ["Entity", "DataCollection"], - } - }, - ) - alt_titles: Optional[list[Name]] = Field( - default=None, - description="""Alternative versions of the title/name of a project.""", - json_schema_extra={ - "linkml_meta": { - "alias": "alt_titles", - "aliases": ["alternative titles"], - "comments": ["The project `title` should not appear in this list."], - "domain_of": ["DataCollection"], - } - }, - ) - url: str = Field( - default=..., - description="""Permanent resolvable URI for the collection at the data source.""", - json_schema_extra={ - "linkml_meta": {"alias": "url", "domain_of": ["DataCollection"]} - }, - ) - - -# Model rebuild -# see https://pydantic-docs.helpmanual.io/usage/models/#rebuilding-a-model -AttributeValue.model_rebuild() -QuantityValue.model_rebuild() -Entity.model_rebuild() -Coordinates.model_rebuild() -Name.model_rebuild() -DataCollection.model_rebuild() diff --git a/src/server.py b/src/server.py index 8be07a7..9efb2a1 100644 --- a/src/server.py +++ b/src/server.py @@ -4,7 +4,7 @@ from pymongo import MongoClient from typing import Optional, Dict, Any from pydantic import BaseModel, Field -import bertron_schema_pydantic +from schema.datamodel import bertron_schema_pydantic import logging # Set up logging diff --git a/uv.lock b/uv.lock index 42a2cde..444534e 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,15 @@ version = 1 revision = 2 -requires-python = ">=3.12, <3.14" +requires-python = ">=3.12.9, <3.14" + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] [[package]] name = "annotated-types" @@ -11,6 +20,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } + [[package]] name = "anyio" version = "4.9.0" @@ -25,6 +40,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, +] + [[package]] name = "attrs" version = "25.3.0" @@ -34,11 +62,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backrefs" +version = "5.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994, upload-time = "2025-02-25T18:15:32.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337, upload-time = "2025-02-25T16:53:14.607Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142, upload-time = "2025-02-25T16:53:17.266Z" }, + { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021, upload-time = "2025-02-25T16:53:26.378Z" }, + { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915, upload-time = "2025-02-25T16:53:28.167Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336, upload-time = "2025-02-25T16:53:29.858Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, +] + [[package]] name = "bertron" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "bertron-schema" }, { name = "dtspy" }, { name = "fastapi", extra = ["standard"] }, { name = "jsonschema" }, @@ -58,6 +122,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "bertron-schema", git = "https://github.com/ber-data/bertron-schema.git" }, { name = "dtspy", url = "https://github.com/kbase/dtspy/archive/730828cff3924fc4b2215fe5c1b67bc04aad377f.tar.gz" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" }, { name = "jsonschema", specifier = ">=4.0.0" }, @@ -75,6 +140,17 @@ dev = [ { name = "ruff", specifier = ">=0.9.9" }, ] +[[package]] +name = "bertron-schema" +version = "0.1.0" +source = { git = "https://github.com/ber-data/bertron-schema.git#d9ea00ef9167cf78fa6a7ba195eadf7b0f1b81a8" } +dependencies = [ + { name = "linkml" }, + { name = "linkml-runtime" }, + { name = "mkdocs-material" }, + { name = "mkdocs-mermaid2-plugin" }, +] + [[package]] name = "certifi" version = "2025.4.26" @@ -84,6 +160,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, ] +[[package]] +name = "cfgraph" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rdflib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/51/3e7e021920cfe2f7d18b672642e13f7dc4f53545d530b52ee6533b6681ca/CFGraph-0.2.1.tar.gz", hash = "sha256:b57fe7044a10b8ff65aa3a8a8ddc7d4cd77bf511b42e57289cd52cbc29f8fe74", size = 2630, upload-time = "2018-11-20T15:27:28.69Z" } + [[package]] name = "cfgv" version = "3.4.0" @@ -199,6 +284,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, ] +[[package]] +name = "curies" +version = "0.10.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "pytrie" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/d9/911e88f1a78229fe85b2ebf2efd06312e7d012eca53ee75b611086851c6a/curies-0.10.19.tar.gz", hash = "sha256:aeae5e7cbb7aee6c5144376fcb69e15a0d3c0557a12f9edff809bd0ce5004ea2", size = 271414, upload-time = "2025-05-14T09:05:36.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/8d/440d865fbb1b38382f30f031795fc6c47db38744ecf06cb1cd7ce64d507a/curies-0.10.19-py3-none-any.whl", hash = "sha256:8f34e945c5101f6ba0916bc73c1e1e244da5412adf35eabcf0e596acb47460d6", size = 60459, upload-time = "2025-05-14T09:05:34.659Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -208,6 +307,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, +] + [[package]] name = "distlib" version = "0.3.9" @@ -226,6 +337,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, ] +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + [[package]] name = "dtspy" version = "0.1.0" @@ -242,6 +362,15 @@ requires-dist = [ { name = "requests", specifier = ">=2.32.3,<3" }, ] +[[package]] +name = "editorconfig" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3a/a61d9a1f319a186b05d14df17daea42fcddea63c213bcd61a929fb3a6796/editorconfig-0.17.1.tar.gz", hash = "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745", size = 14695, upload-time = "2025-06-09T08:21:37.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/fd/a40c621ff207f3ce8e484aa0fc8ba4eb6e3ecf52e15b42ba764b457a9550/editorconfig-0.17.1-py3-none-any.whl", hash = "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", size = 16360, upload-time = "2025-06-09T08:21:35.654Z" }, +] + [[package]] name = "email-validator" version = "2.2.0" @@ -255,6 +384,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, ] +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + [[package]] name = "fastapi" version = "0.115.12" @@ -332,6 +470,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/e5/c1cb8ebabb80be76d4d28995da9416816653f8f572920ab5e3d2e3ac8285/fonttools-4.58.2-py3-none-any.whl", hash = "sha256:84f4b0bcfa046254a65ee7117094b4907e22dc98097a220ef108030eb3c15596", size = 1114597, upload-time = "2025-06-06T14:50:56.619Z" }, ] +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, +] + [[package]] name = "frictionless" version = "5.18.1" @@ -362,6 +509,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/7a/dac76d31584bb4f874ae860490c9465f5b59bd8c110f68fbbb07aba48845/frictionless-5.18.1-py3-none-any.whl", hash = "sha256:3f4c87469a89bdb88e9cc318088553a26f3d14839098f95c183ea01fc89628dd", size = 531615, upload-time = "2025-03-25T21:32:45.534Z" }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "graphviz" +version = "0.20.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/83/5a40d19b8347f017e417710907f824915fba411a9befd092e52746b63e9f/graphviz-0.20.3.zip", hash = "sha256:09d6bc81e6a9fa392e7ba52135a9d49f1ed62526f96499325930e87ca1b5925d", size = 256455, upload-time = "2024-03-21T07:50:45.772Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/be/d59db2d1d52697c6adc9eacaf50e8965b6345cc143f671e1ed068818d5cf/graphviz-0.20.3-py3-none-any.whl", hash = "sha256:81f848f2904515d8cd359cc611faba817598d2feaac4027b266aa3eda7b3dde5", size = 47126, upload-time = "2024-03-21T07:50:43.091Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" }, + { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", size = 647701, upload-time = "2025-06-05T16:48:19.604Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" }, + { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", size = 1149055, upload-time = "2025-06-05T16:12:40.457Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", size = 297817, upload-time = "2025-06-05T16:29:49.244Z" }, + { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" }, + { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" }, + { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" }, + { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" }, + { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" }, + { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -371,6 +565,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "hbreader" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/66/3a649ce125e03d1d43727a8b833cd211f0b9fe54a7e5be326f50d6f1d951/hbreader-0.9.1.tar.gz", hash = "sha256:d2c132f8ba6276d794c66224c3297cec25c8079d0a4cf019c061611e0a3b94fa", size = 19016, upload-time = "2021-02-25T19:22:32.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/24/61844afbf38acf419e01ca2639f7bd079584523d34471acbc4152ee991c5/hbreader-0.9.1-py3-none-any.whl", hash = "sha256:9a6e76c9d1afc1b977374a5dc430a1ebb0ea0488205546d4678d6e31cc5f6801", size = 7595, upload-time = "2021-02-25T19:22:31.944Z" }, +] + [[package]] name = "httpcore" version = "1.0.9" @@ -448,6 +651,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -466,6 +678,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -478,6 +702,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jsbeautifier" +version = "1.15.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "editorconfig" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/98/d6cadf4d5a1c03b2136837a435682418c29fdeb66be137128544cecc5b7a/jsbeautifier-1.15.4.tar.gz", hash = "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592", size = 75257, upload-time = "2025-02-27T17:53:53.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/14/1c65fccf8413d5f5c6e8425f84675169654395098000d8bddc4e9d3390e1/jsbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528", size = 94707, upload-time = "2025-02-27T17:53:46.152Z" }, +] + +[[package]] +name = "json-flattener" +version = "0.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/77/b00e46d904818826275661a690532d3a3a43a4ded0264b2d7fcdb5c0feea/json_flattener-0.1.9.tar.gz", hash = "sha256:84cf8523045ffb124301a602602201665fcb003a171ece87e6f46ed02f7f0c15", size = 11479, upload-time = "2022-02-26T01:36:04.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/cc/7fbd75d3362e939eb98bcf9bd22f3f7df8c237a85148899ed3d38e5614e5/json_flattener-0.1.9-py3-none-any.whl", hash = "sha256:6b027746f08bf37a75270f30c6690c7149d5f704d8af1740c346a3a1236bc941", size = 10799, upload-time = "2022-02-26T01:36:03.06Z" }, +] + +[[package]] +name = "jsonasobj" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/ba/13523c1408a23bac4e08ef2312732733c0129c4ff085d351eafaf45fd080/jsonasobj-1.3.1.tar.gz", hash = "sha256:d52e0544a54a08f6ea3f77fa3387271e3648655e0eace2f21e825c26370e44a2", size = 4315, upload-time = "2021-02-08T22:03:20.336Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/57/38c47753c67ad67f76ba04ea673c9b77431a19e7b2601937e6872a99e841/jsonasobj-1.3.1-py3-none-any.whl", hash = "sha256:b9e329dc1ceaae7cf5d5b214684a0b100e0dad0be6d5bbabac281ec35ddeca65", size = 4388, upload-time = "2021-02-08T22:03:19.17Z" }, +] + +[[package]] +name = "jsonasobj2" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hbreader" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/3a/feb245b755f7a47a0df4f30be645e8485d15ff13d0c95e018e4505a8811f/jsonasobj2-1.0.4.tar.gz", hash = "sha256:f50b1668ef478004aa487b2d2d094c304e5cb6b79337809f4a1f2975cc7fbb4e", size = 95522, upload-time = "2021-06-02T17:43:28.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/90/0d93963711f811efe528e3cead2f2bfb78c196df74d8a24fe8d655288e50/jsonasobj2-1.0.4-py3-none-any.whl", hash = "sha256:12e86f86324d54fcf60632db94ea74488d5314e3da554c994fe1e2c6f29acb79", size = 6324, upload-time = "2021-06-02T17:43:27.126Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + [[package]] name = "jsonschema" version = "4.24.0" @@ -493,6 +773,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" }, ] +[package.optional-dependencies] +format = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3987" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + [[package]] name = "jsonschema-specifications" version = "2025.4.1" @@ -556,6 +848,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, ] +[[package]] +name = "linkml" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "click" }, + { name = "graphviz" }, + { name = "hbreader" }, + { name = "isodate" }, + { name = "jinja2" }, + { name = "jsonasobj2" }, + { name = "jsonschema", extra = ["format"] }, + { name = "linkml-runtime" }, + { name = "openpyxl" }, + { name = "parse" }, + { name = "prefixcommons" }, + { name = "prefixmaps" }, + { name = "pydantic" }, + { name = "pyjsg" }, + { name = "pyshex" }, + { name = "pyshexc" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "rdflib" }, + { name = "requests" }, + { name = "sphinx-click" }, + { name = "sqlalchemy" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e2/23136b7e063159dc25cb865ef10f1ceca5606136bbed469efe7a061cf707/linkml-1.9.2.tar.gz", hash = "sha256:2f9141d2bc8a93bfe1d4b86a015ad0acbb94c2af099177f5687a50d3331d2b34", size = 260216, upload-time = "2025-05-15T22:21:52.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/19/430f4907cdad687af4c14e495c7027ed96d1bbb1901609838d55b2b32c4e/linkml-1.9.2-py3-none-any.whl", hash = "sha256:4c9cf217948367df8a20cdf68e8f6da24ba23ab97a551f8ae32e9d4264e702cc", size = 333519, upload-time = "2025-05-15T22:21:50.067Z" }, +] + +[[package]] +name = "linkml-runtime" +version = "1.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "curies" }, + { name = "deprecated" }, + { name = "hbreader" }, + { name = "json-flattener" }, + { name = "jsonasobj2" }, + { name = "jsonschema" }, + { name = "prefixcommons" }, + { name = "prefixmaps" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "rdflib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/3c/d0ec2b9b2472a06fb43784f67c89ebcaa6dfb69f9c9bc19c8ed358c88045/linkml_runtime-1.9.3.tar.gz", hash = "sha256:1b65358bf91868b7607675abb98c26597873bb45f73ab309b7d4c31a84e58e1b", size = 479939, upload-time = "2025-06-02T16:52:42.878Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/64/93e53462fc222d6cbf6094781e55a6b3d129184f89dc533237beb544d62b/linkml_runtime-1.9.3-py3-none-any.whl", hash = "sha256:39a8aa51b40decd58fd04f4c02a213aad06b971df4c042aa7764f4b75cc09aa8", size = 577670, upload-time = "2025-06-02T16:52:40.853Z" }, +] + +[[package]] +name = "markdown" +version = "3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f", size = 360906, upload-time = "2025-04-11T14:42:50.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc", size = 106210, upload-time = "2025-04-11T14:42:49.178Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -661,6 +1021,101 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.6.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fa/0101de32af88f87cf5cc23ad5f2e2030d00995f74e616306513431b8ab4b/mkdocs_material-9.6.14.tar.gz", hash = "sha256:39d795e90dce6b531387c255bd07e866e027828b7346d3eba5ac3de265053754", size = 3951707, upload-time = "2025-05-13T13:27:57.173Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/a1/7fdb959ad592e013c01558822fd3c22931a95a0f08cf0a7c36da13a5b2b5/mkdocs_material-9.6.14-py3-none-any.whl", hash = "sha256:3b9cee6d3688551bf7a8e8f41afda97a3c39a12f0325436d76c86706114b721b", size = 8703767, upload-time = "2025-05-13T13:27:54.089Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocs-mermaid2-plugin" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "jsbeautifier" }, + { name = "mkdocs" }, + { name = "pymdown-extensions" }, + { name = "requests" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/1a/f580733da1924ebc9b4bb04a34ca63ae62a50b0e62eeb016e78d9dee6d69/mkdocs_mermaid2_plugin-1.2.1.tar.gz", hash = "sha256:9c7694c73a65905ac1578f966e5c193325c4d5a5bc1836727e74ac9f99d0e921", size = 16104, upload-time = "2024-11-02T06:27:36.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/ce/c8a41cb0f3044990c8afbdc20c853845a9e940995d4e0cffecafbb5e927b/mkdocs_mermaid2_plugin-1.2.1-py3-none-any.whl", hash = "sha256:22d2cf2c6867d4959a5e0903da2dde78d74581fc0b107b791bc4c7ceb9ce9741", size = 17260, upload-time = "2024-11-02T06:27:34.652Z" }, +] + [[package]] name = "nmdc-api-utilities" version = "0.3.9" @@ -725,6 +1180,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/e8/2c8a1c9e34d6f6d600c83d5ce5b71646c32a13f34ca5c518cc060639841c/numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944", size = 9935345, upload-time = "2025-06-07T14:50:02.311Z" }, ] +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -734,6 +1201,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + [[package]] name = "pandas" version = "2.2.3" @@ -768,6 +1244,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, ] +[[package]] +name = "parse" +version = "1.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + [[package]] name = "petl" version = "1.7.16" @@ -849,6 +1343,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, ] +[[package]] +name = "prefixcommons" +version = "0.1.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "pytest-logging" }, + { name = "pyyaml" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/b5/c5b63a4bf5dedb36567181fdb98dbcc7aaa025faebabaaffa2f5eb4b8feb/prefixcommons-0.1.12.tar.gz", hash = "sha256:22c4e2d37b63487b3ab48f0495b70f14564cb346a15220f23919eb0c1851f69f", size = 24063, upload-time = "2022-07-19T00:06:12.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/e8/715b09df3dab02b07809d812042dc47a46236b5603d9d3a2572dbd1d8a97/prefixcommons-0.1.12-py3-none-any.whl", hash = "sha256:16dbc0a1f775e003c724f19a694fcfa3174608f5c8b0e893d494cf8098ac7f8b", size = 29482, upload-time = "2022-07-19T00:06:08.709Z" }, +] + +[[package]] +name = "prefixmaps" +version = "0.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "curies" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/cf/f588bcdfd2c841839b9d59ce219a46695da56aa2805faff937bbafb9ee2b/prefixmaps-0.2.6.tar.gz", hash = "sha256:7421e1244eea610217fa1ba96c9aebd64e8162a930dc0626207cd8bf62ecf4b9", size = 709899, upload-time = "2024-10-17T16:30:57.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/b2/2b2153173f2819e3d7d1949918612981bc6bd895b75ffa392d63d115f327/prefixmaps-0.2.6-py3-none-any.whl", hash = "sha256:f6cef28a7320fc6337cf411be212948ce570333a0ce958940ef684c7fb192a62", size = 754732, upload-time = "2024-10-17T16:30:55.731Z" }, +] + [[package]] name = "pydantic" version = "2.11.5" @@ -915,6 +1437,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] +[[package]] +name = "pyjsg" +version = "0.11.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "jsonasobj" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/61/e001a4b679a171f84783deb8e215a91c9f614cb498807e24e4f73ea4e5ed/PyJSG-0.11.10.tar.gz", hash = "sha256:4bd6e3ff2833fa2b395bbe803a2d72a5f0bab5b7285bccd0da1a1bc0aee88bfa", size = 130742, upload-time = "2022-04-14T17:18:24.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/ee/370c3b1908327dac967841ff723db391a02f3637c95c6898160e5ffe1060/PyJSG-0.11.10-py3-none-any.whl", hash = "sha256:10af60ff42219be7e85bf7f11c19b648715b0b29eb2ddbd269e87069a7c3f26d", size = 80763, upload-time = "2022-04-14T17:18:23.169Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/92/a7296491dbf5585b3a987f3f3fc87af0e632121ff3e490c14b5f2d2b4eb5/pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7", size = 852320, upload-time = "2025-04-27T23:48:29.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/d1/c54e608505776ce4e7966d03358ae635cfd51dff1da6ee421c090dbc797b/pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f", size = 265845, upload-time = "2025-04-27T23:48:27.359Z" }, +] + [[package]] name = "pymongo" version = "4.13.1" @@ -975,6 +1523,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/37/1a1c62d955e82adae588be8e374c7f77b165b6cb4203f7d581269959abbc/pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982", size = 5624004, upload-time = "2025-06-11T08:48:33.998Z" }, ] +[[package]] +name = "pyshex" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgraph" }, + { name = "chardet" }, + { name = "pyshexc" }, + { name = "rdflib-shim" }, + { name = "requests" }, + { name = "shexjsg" }, + { name = "sparqlslurper" }, + { name = "sparqlwrapper" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/d7/420ce2df4e8688e06fa8e1fc353fdf3875eb70f6fc2e17493d0526d778ff/PyShEx-0.8.1.tar.gz", hash = "sha256:3c5c4d45fe27faaadae803cb008c41acf8ee784da7868b04fd84967e75be70d0", size = 475611, upload-time = "2022-04-14T21:14:58.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/48/efb1b1d3f3aee8cfc9f256738ca6e79ec362edbfc3a3abecbaf84db04643/PyShEx-0.8.1-py3-none-any.whl", hash = "sha256:6da1b10123e191abf8dcb6bf3e54aa3e1fcf771df5d1a0ed453217c8900c8e6a", size = 51861, upload-time = "2022-04-14T21:14:57.254Z" }, +] + +[[package]] +name = "pyshexc" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "chardet" }, + { name = "jsonasobj" }, + { name = "pyjsg" }, + { name = "rdflib-shim" }, + { name = "shexjsg" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/31/95c590e8ed6e8cff141b6dd2a3de93b540f9dc3fba54621a20fd1cdb11e4/PyShExC-0.9.1.tar.gz", hash = "sha256:35a9975d4b9afeb20ef710fb6680871756381d0c39fbb5470b3b506581a304d3", size = 96070, upload-time = "2022-04-14T18:51:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/7d/ff5000e0882f2b3995bef20b667945d3faa9289b556295e4cc5d2e91f104/PyShExC-0.9.1-py2.py3-none-any.whl", hash = "sha256:efc55ed5cb2453e9df569b03e282505e96bb06597934288f3b23dd980ef10028", size = 69792, upload-time = "2022-04-14T18:51:44.148Z" }, +] + [[package]] name = "pytest" version = "8.4.0" @@ -991,6 +1576,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/de/afa024cbe022b1b318a3d224125aa24939e99b4ff6f22e0ba639a2eaee47/pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e", size = 363797, upload-time = "2025-06-02T17:36:27.859Z" }, ] +[[package]] +name = "pytest-logging" +version = "2015.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/1e/fb11174c9eaebcec27d36e9e994b90ffa168bc3226925900b9dbbf16c9da/pytest-logging-2015.11.4.tar.gz", hash = "sha256:cec5c85ecf18aab7b2ead5498a31b9f758680ef5a902b9054ab3f2bdbb77c896", size = 3916, upload-time = "2015-11-04T12:15:54.122Z" } + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1033,6 +1627,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] +[[package]] +name = "pytrie" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/19/15ec77ab9c85f7c36eb590d6ab7dd529f8c8516c0e2219f1a77a99d7ee77/PyTrie-0.4.0.tar.gz", hash = "sha256:8f4488f402d3465993fb6b6efa09866849ed8cda7903b50647b7d0342b805379", size = 95139, upload-time = "2020-10-21T15:39:30.334Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fd/499b261a34e9c6e39b9f5711c4b3093bca980b8db4b49de3009d808f41c9/PyTrie-0.4.0-py3-none-any.whl", hash = "sha256:f687c224ee8c66cda8e8628a903011b692635ffbb08d4b39c5f92b18eb78c950", size = 6061, upload-time = "2024-03-09T16:59:46.768Z" }, +] + [[package]] name = "pytz" version = "2025.2" @@ -1068,6 +1674,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "rdflib" +version = "7.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/7e/cb2d74466bd8495051ebe2d241b1cb1d4acf9740d481126aef19ef2697f5/rdflib-7.1.4.tar.gz", hash = "sha256:fed46e24f26a788e2ab8e445f7077f00edcf95abb73bcef4b86cefa8b62dd174", size = 4692745, upload-time = "2025-03-29T02:23:02.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/31/e9b6f04288dcd3fa60cb3179260d6dad81b92aef3063d679ac7d80a827ea/rdflib-7.1.4-py3-none-any.whl", hash = "sha256:72f4adb1990fa5241abd22ddaf36d7cafa5d91d9ff2ba13f3086d339b213d997", size = 565051, upload-time = "2025-03-29T02:22:44.987Z" }, +] + +[[package]] +name = "rdflib-jsonld" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rdflib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/48/9eaecac5f5ba6b31dd932fbbe67206afcbd24a7a696c03c6c920ac7ddc39/rdflib-jsonld-0.6.1.tar.gz", hash = "sha256:eda5a42a2e09f80d4da78e32b5c684bccdf275368f1541e6b7bcddfb1382a0e0", size = 130465, upload-time = "2021-09-14T12:22:20.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/d2/760527679057a7dad67f4e41f3e0c463b247f0bdbffc594e0add7c9077d6/rdflib_jsonld-0.6.1-py2.py3-none-any.whl", hash = "sha256:bcf84317e947a661bae0a3f2aee1eced697075fc4ac4db6065a3340ea0f10fc2", size = 16381, upload-time = "2021-09-14T12:22:17.805Z" }, +] + +[[package]] +name = "rdflib-shim" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rdflib" }, + { name = "rdflib-jsonld" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/c8/1014ec6b5f4428c630deffba1f9851043ae378eb1d6ef52a03bd492cea99/rdflib_shim-1.0.3.tar.gz", hash = "sha256:d955d11e2986aab42b6830ca56ac6bc9c893abd1d049a161c6de2f1b99d4fc0d", size = 7783, upload-time = "2021-12-21T16:31:06.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/97/d8a785d2c7131c731c90cb0e65af9400081af4380bea4ec04868dc21aa92/rdflib_shim-1.0.3-py3-none-any.whl", hash = "sha256:7a853e7750ef1e9bf4e35dea27d54e02d4ed087de5a9e0c329c4a6d82d647081", size = 5190, upload-time = "2021-12-21T16:31:05.719Z" }, +] + [[package]] name = "referencing" version = "0.36.2" @@ -1097,6 +1752,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + [[package]] name = "rfc3986" version = "2.0.0" @@ -1106,6 +1773,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, ] +[[package]] +name = "rfc3987" +version = "1.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/bb/f1395c4b62f251a1cb503ff884500ebd248eed593f41b469f89caa3547bd/rfc3987-1.3.8.tar.gz", hash = "sha256:d3c4d257a560d544e9826b38bc81db676890c79ab9d7ac92b39c7a253d5ca733", size = 20700, upload-time = "2018-07-29T17:23:47.954Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/d4/f7407c3d15d5ac779c3dd34fbbc6ea2090f77bd7dd12f207ccf881551208/rfc3987-1.3.8-py2.py3-none-any.whl", hash = "sha256:10702b1e51e5658843460b189b185c0366d2cf4cff716f13111b0ea9fd2dce53", size = 13377, upload-time = "2018-07-29T17:23:45.313Z" }, +] + [[package]] name = "rich" version = "14.0.0" @@ -1133,6 +1809,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/2e/95fde5b818dac9a37683ea064096323f593442d0f6358923c5f635974393/rich_toolkit-0.14.7-py3-none-any.whl", hash = "sha256:def05cc6e0f1176d6263b6a26648f16a62c4563b277ca2f8538683acdba1e0da", size = 24870, upload-time = "2025-05-27T15:48:07.942Z" }, ] +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + [[package]] name = "rpds-py" version = "0.25.1" @@ -1207,6 +1892,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928, upload-time = "2025-06-05T21:00:13.758Z" }, ] +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -1216,6 +1910,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] +[[package]] +name = "shexjsg" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyjsg" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/c9/34224e3c8fd9d466535626e3c2f6e01f6adae3e82acaed353d42add509ec/ShExJSG-0.8.2.tar.gz", hash = "sha256:f17a629fc577fa344382bdee143cd9ff86588537f9f811f66cea6f63cdbcd0b6", size = 33550, upload-time = "2022-04-14T20:23:13.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6e/d23bcde21d4ef0250a74e7505d2990d429f862be65810a3b650a69def7f0/ShExJSG-0.8.2-py2.py3-none-any.whl", hash = "sha256:3b0d8432dd313bee9e1343382c5e02e9908dd941a7dd7342bf8c0200fe523766", size = 14381, upload-time = "2022-04-14T20:23:12.515Z" }, +] + [[package]] name = "simpleeval" version = "1.0.3" @@ -1243,6 +1949,184 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[package]] +name = "sparqlslurper" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rdflib" }, + { name = "rdflib-shim" }, + { name = "sparqlwrapper" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/22/6c375a48851f96b334e147db62ebee615283b87f30398ba94b3551d60984/sparqlslurper-0.5.1.tar.gz", hash = "sha256:9282ebb064fc6152a58269d194cb1e7b275b0f095425a578d75b96dcc851f546", size = 640336, upload-time = "2021-12-21T21:28:04.095Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/77/48ce09fce2836856588beb84f434c1f8812d1428326efd993b619d49d949/sparqlslurper-0.5.1-py3-none-any.whl", hash = "sha256:ae49b2d8ce3dd38df7a40465b228ad5d33fb7e11b3f248d195f9cadfc9cfff87", size = 6555, upload-time = "2021-12-21T21:28:01.95Z" }, +] + +[[package]] +name = "sparqlwrapper" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rdflib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/cc/453752fffa759ef41a3ceadb3f167e13dae1a74c1db057d9f6a7affa9240/SPARQLWrapper-2.0.0.tar.gz", hash = "sha256:3fed3ebcc77617a4a74d2644b86fd88e0f32e7f7003ac7b2b334c026201731f1", size = 98429, upload-time = "2022-03-13T23:14:00.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/89/176e3db96e31e795d7dfd91dd67749d3d1f0316bb30c6931a6140e1a0477/SPARQLWrapper-2.0.0-py3-none-any.whl", hash = "sha256:c99a7204fff676ee28e6acef327dc1ff8451c6f7217dcd8d49e8872f324a8a20", size = 28620, upload-time = "2022-03-13T23:13:58.969Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-click" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "docutils" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/0a/5b1e8d0579dbb4ca8114e456ca4a68020bfe8e15c7001f3856be4929ab83/sphinx_click-6.0.0.tar.gz", hash = "sha256:f5d664321dc0c6622ff019f1e1c84e58ce0cecfddeb510e004cf60c2a3ab465b", size = 29574, upload-time = "2024-05-15T14:49:17.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/d7/8621c4726ad3f788a1db4c0c409044b16edc563f5c9542807b3724037555/sphinx_click-6.0.0-py3-none-any.whl", hash = "sha256:1e0a3c83bcb7c55497751b19d07ebe56b5d7b85eb76dd399cf9061b497adc317", size = 9922, upload-time = "2024-05-15T14:49:15.768Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.41" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/2a/f1f4e068b371154740dd10fb81afb5240d5af4aa0087b88d8b308b5429c2/sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", size = 2119645, upload-time = "2025-05-14T17:55:24.854Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/c664a7e73d36fbfc4730f8cf2bf930444ea87270f2825efbe17bf808b998/sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", size = 2107399, upload-time = "2025-05-14T17:55:28.097Z" }, + { url = "https://files.pythonhosted.org/packages/5c/78/8a9cf6c5e7135540cb682128d091d6afa1b9e48bd049b0d691bf54114f70/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", size = 3293269, upload-time = "2025-05-14T17:50:38.227Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/f74add3978c20de6323fb11cb5162702670cc7a9420033befb43d8d5b7a4/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e", size = 3303364, upload-time = "2025-05-14T17:51:49.829Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d4/c990f37f52c3f7748ebe98883e2a0f7d038108c2c5a82468d1ff3eec50b7/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078", size = 3229072, upload-time = "2025-05-14T17:50:39.774Z" }, + { url = "https://files.pythonhosted.org/packages/15/69/cab11fecc7eb64bc561011be2bd03d065b762d87add52a4ca0aca2e12904/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae", size = 3268074, upload-time = "2025-05-14T17:51:51.736Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0c19ec16858585d37767b167fc9602593f98998a68a798450558239fb04a/sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6", size = 2084514, upload-time = "2025-05-14T17:55:49.915Z" }, + { url = "https://files.pythonhosted.org/packages/7f/23/4c2833d78ff3010a4e17f984c734f52b531a8c9060a50429c9d4b0211be6/sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0", size = 2111557, upload-time = "2025-05-14T17:55:51.349Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, +] + [[package]] name = "starlette" version = "0.46.2" @@ -1288,6 +2172,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, ] +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/88/d65ed807393285204ab6e2801e5d11fbbea811adcaa979a2ed3b67a5ef41/types_python_dateutil-2.9.0.20250516.tar.gz", hash = "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5", size = 13943, upload-time = "2025-05-16T03:06:58.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/3f/b0e8db149896005adc938a1e7f371d6d7e9eca4053a29b108978ed15e0c2/types_python_dateutil-2.9.0.20250516-py3-none-any.whl", hash = "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93", size = 14356, upload-time = "2025-05-16T03:06:57.249Z" }, +] + [[package]] name = "typing-extensions" version = "4.14.0" @@ -1318,6 +2211,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, +] + [[package]] name = "urllib3" version = "2.4.0" @@ -1394,6 +2296,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + [[package]] name = "watchfiles" version = "1.0.5" @@ -1430,6 +2356,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087, upload-time = "2025-04-08T10:35:52.458Z" }, ] +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064, upload-time = "2024-11-11T07:43:24.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934, upload-time = "2024-11-11T07:43:22.529Z" }, +] + [[package]] name = "websockets" version = "15.0.1" @@ -1460,3 +2395,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, + { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" }, + { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload-time = "2025-01-14T10:34:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload-time = "2025-01-14T10:34:29.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload-time = "2025-01-14T10:34:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload-time = "2025-01-14T10:34:32.91Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload-time = "2025-01-14T10:34:34.903Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload-time = "2025-01-14T10:34:36.13Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload-time = "2025-01-14T10:34:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload-time = "2025-01-14T10:34:39.13Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload-time = "2025-01-14T10:34:40.604Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload-time = "2025-01-14T10:34:45.011Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload-time = "2025-01-14T10:34:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload-time = "2025-01-14T10:34:50.934Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload-time = "2025-01-14T10:34:52.297Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload-time = "2025-01-14T10:34:53.489Z" }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload-time = "2025-01-14T10:34:55.327Z" }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload-time = "2025-01-14T10:34:58.055Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload-time = "2025-01-14T10:34:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload-time = "2025-01-14T10:35:00.498Z" }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, +] From db2c84ad297e0370d481e331c5839ad549c2fb80 Mon Sep 17 00:00:00 2001 From: shreddd Date: Fri, 13 Jun 2025 16:30:16 -0700 Subject: [PATCH 20/20] remove redundant entity conversion --- src/server.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/server.py b/src/server.py index 9efb2a1..3545577 100644 --- a/src/server.py +++ b/src/server.py @@ -266,11 +266,9 @@ def get_entity_by_id(id: str): status_code=404, detail=f"Entity with id '{id}' not found" ) - entity = convert_document_to_entity(document) - # Validate and create Entity instance try: - entity = bertron_schema_pydantic.Entity(**document) + entity = convert_document_to_entity(document) return entity except Exception as validation_error: logger.error(f"Entity validation failed for id '{id}': {validation_error}") @@ -290,10 +288,9 @@ def convert_document_to_entity( document: Dict[str, Any], ) -> Optional[bertron_schema_pydantic.Entity]: """Convert a MongoDB document to an Entity object.""" - # Remove MongoDB _id and metadata + # Remove MongoDB _id, metadata, geojson document.pop("_id", None) document.pop("_metadata", None) - document.pop("geojson", None) return bertron_schema_pydantic.Entity(**document)