import json
import re
from pathlib import Path
from typing import Optional, Union
from pydantic import BaseModel, ConfigDict, JsonValue, Field
from datetime import datetime
[docs]
class Parsimonius(BaseModel):
"""Change defaule `model_dump` behaviour to not export unset values by default"""
[docs]
model_config = ConfigDict(
extra="allow",
)
[docs]
def model_dump(self, exclude_unset=True, *args, **kwargs):
return super().model_dump(*args, exclude_unset=exclude_unset, **kwargs)
[docs]
class DataSource(Parsimonius):
"""A data source, such as a publication or field measurement.
Very preliminary."""
[docs]
doi: Optional[str] = None
[docs]
class UncertaintyDistribution(Parsimonius):
"""Separate out the fields used in uncertainty distributions"""
[docs]
uncertainty_type: Optional[int] = None
[docs]
loc: Optional[float] = None
[docs]
scale: Optional[float] = None
[docs]
shape: Optional[float] = None
[docs]
minimum: Optional[float] = None
[docs]
maximum: Optional[float] = None
[docs]
negative: Optional[bool] = None
[docs]
class Edge(UncertaintyDistribution):
"""An quantitative edge linking two nodes in the graph."""
[docs]
edge_type: str = Field(alias="type")
# The people want freedom
[docs]
properties: dict[str, Union[float, int]]
[docs]
class Node(Parsimonius):
# Combination of database and code uniquely identifies a node
# Recommended labels for these attributes
[docs]
created: Optional[datetime] = None
[docs]
modified: Optional[datetime] = None
[docs]
name: Optional[str] = None
[docs]
unit: Optional[str] = None
[docs]
location: Optional[str] = None
[docs]
node_type: Optional[str] = Field(alias="type", default=None)
# Comment can be a single string or something more structured.
[docs]
filename: Optional[str] = None
[docs]
references: Optional[list[DataSource]] = None
# Was previously classifications - we want something more generic
# Tags are chosen from defined set of possibilities
[docs]
exchanges: list[Edge] = []
[docs]
class Process(Node):
"""A generic process, possibly multi-functional. Does not have a reference product.
Only difference from `Node` is that some more fields are required."""
# TBD: Time range?
[docs]
class ProcessWithReferenceProduct(Process):
"""Chimaera which serves as both a product and a process in the graph."""
# Use `ProcessWithReferenceProduct.model_dump(by_alias=True)`
# to get a dictionary using the alias field
[docs]
reference_product: str = Field(alias="reference product")
# Optional name for the amount of reference product produced.
# Duplicates information in the exchanges.
# Should be net amount.
[docs]
production_amount: Optional[float] = None
# Properties for reference product
[docs]
properties: Optional[dict[str, Union[float, int]]] = None
[docs]
model_config = ConfigDict(
populate_by_name=False,
)
[docs]
class Product(Node):
# Some products are the same globally, other have specific local properties
[docs]
location: Optional[str] = None
# Properties are quantitative; use tags to choose from a set of possible values
[docs]
properties: dict[str, Union[float, int]]
[docs]
class ElementaryFlow(Node):
# Previously called categories in Brightway
[docs]
context: list[str]
if __name__ == "__main__":
[docs]
hiss = lambda name: re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()
dirpath = Path(__file__).parent / "json_schema"
for cls in (ElementaryFlow, Product, ProcessWithReferenceProduct, Process, Node, Edge):
with open(dirpath / (hiss(cls.__name__) + ".json"), "w") as f:
json.dump(cls.model_json_schema(), f, indent=2, ensure_ascii=False)