Metadata-Version: 2.4
Name: mash-core
Version: 0.9.2
Summary: Standardized data and RDF made practical
Keywords: RDF,DCAT,standards,serialization,deserialization
Author: Natalie Jakobsen
Author-email: Natalie Jakobsen <natalie.jakobsen@elbits.no>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Requires-Dist: ipykernel>=6.29.5
Requires-Dist: pytest>=8.3.5
Requires-Dist: uvicorn>=0.34.0
Requires-Dist: maplib>=0.20.25
Requires-Dist: polars>=0.20.13
Requires-Dist: pyarrow==23.0.1
Requires-Dist: fastapi[standard]>=0.115
Requires-Dist: pyproj>=3.7.1
Requires-Dist: platformdirs>=4.4.0
Requires-Dist: pyoxigraph>=0.5.2
Requires-Dist: colorama>=0.4.6
Requires-Dist: pydantic-settings>=2.12.0
Requires-Dist: cachetools>=6.2.2
Requires-Dist: azure-storage-blob>=12.27.1
Requires-Dist: azure-identity>=1.25.1
Requires-Dist: smart-open[azure]>=7.6.0
Requires-Dist: linkml>=1.11.1
Requires-Dist: jinja2>=3.1.6
Requires-Dist: pathvalidate>=3.3.1
Maintainer: Natalie Jakobsen
Maintainer-email: Natalie Jakobsen <natalie.jakobsen@elbits.no>
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# Mash

Mash makes working with standardized data using RDF easy. It provides ORM-like functionality for mapping your RDF data to classes, and to serialize these classes into RDF. Furthermore it provides concepts like profiles and repositories which allow you to describe, organize and store your data, metadata and information models.

(the docs are undergoing a rewrite, so some things might not make sense right now)


## Mash ORM

Mash features a simple RDF ORM which allows you to extract data from RDF graphs into Pydantic BaseModel classes, and to serialize BaseModel classes into RDF graphs. Using the SemanticModel class and Predicate annotation you can define your own rich metadata classes - either based on existing standards or your own models.

#### A basic example

```Python
from typing import Annotated, ClassVar
from mash import models, serialize, Predicate as P


class Person(models.SemanticModel):
    _type: ClassVar = "foaf:Person"
    name: Annotated[str, P("foaf:name")]
    mail: Annotated[str | None, P("foaf:mbox")] = None
    friends: Annotated[list["Person"], P("mash:friends")] = []


christopher_robin = Person(identifier="cr", name="Christopher Robin")
winnie = Person(identifier="wp", name="Winnie The Pooh", mail="Winnie@hundredacres.com", friends=[christopher_robin])
turtle_text = serialize(winnie)
print(turtle_text)
```

```
<urn:uuid:cr>    a foaf:Person ; 
    foaf:name "Christopher Robin" .

<urn:uuid:wp>    a foaf:Person ; 
    mash:friends <urn:uuid:cr> ;
    foaf:mbox "Winnie@hundredacres.com" ;
    foaf:name "Winnie The Pooh" .
```

#### Making data easier to parse with ReverseRelations

Normally in RDF some relations might go in directions that are not pracitical. If you want everything attached to one class rather than to point in all directions from different classes, you can achieve this with ReverseRelations.

Notice how in the previous example `Substation` has no references to `VoltageLevel` or `SubstationPart` since those actually contain the reference to `Substation`.

```Python
from typing import Annotated, ClassVar

import mash

from mash import Predicate as P, ReverseRelation
from mash.grid import get_latest_nemo
from tests.grid.profiles.nemo import IdentifiedObject, VoltageLevel, SubstationPart, NemoProfile


class SubstationEmbed(IdentifiedObject):
    _type: ClassVar = "cim:Substation"
    substation_kind: Annotated[list[str], P("elb:Substation.substationKind")]
    substation_parts: Annotated[list[SubstationPart], ReverseRelation("elb:SubstationPart.Substation")]
    voltage_levels: Annotated[list[VoltageLevel], ReverseRelation("cim:VoltageLevel.Substation")]


latest_nemo = get_latest_nemo()
m = mash.model_from_dataset(latest_nemo)
with NemoProfile:
    substations = mash.instantiate(m, model=SubstationEmbed)
print(substations[0:2])
```


## Mash profiles

Profiles group a set of models, namespaces and static resources alongside relevant metadata using `prof` - The Profiles Ontology. This makes it very convenient to store and retrieve example data, validation shapes, vocabularies and more in a structured fashion. Mash then allows you to activate everything associated with a profile whenever it is relevant, which avoids collisions between models and namespaces and allows you to think about only what you need.


### Using a profile

```Python
latest_nemo = get_latest_nemo()
m = mash.model_from_dataset(latest_nemo)
with NemoProfile:
    substations = mash.instantiate_all(m, model=SubstationEmbed)
```

### Making a profile

## Mash repositories

Mash provides the ability to store data alongside rich metadata inside of repositories. The rich metadata models are based on widely-implemented standard vocabularies like `dcat`, `prof` and `prov-o`, and mash can both read and write data following these vocabularies.

This brings with it organization, searchability, traceability and many other useful tings. Using functions like `mash.query` you can search the datasets based on pre-configured filters or ones you make yourself. By using the ORM you can make your own metadata models to use alongsiede Mash's preconfigured ones.


#### Define new data using datasets and distributions

```Python
import mash

from mash import models
from mash.core.profiles import MetadataProfile
from mash.core.utilities.organizations import make_organization


dist_id = "http://example.com/example#metadata-plaintext"
dataset = models.Dataset(
    identifier="http://example.com/example#metadata",
    title="Example metadata",
    description="Example dataset containing metadata with little",
    conforms_to=[MetadataProfile],  # This is a lie
    information_owner=make_organization("ElBits AS", "931264079"),
    distributions=[models.Distribution(identifier=dist_id, mime_type="text/plain")],
)

mash.save(dataset)
data = b"this is some text"
mash.write_data(data, dist_id)


print(mash.read_data(dist_id).decode())
```


#### Write a SPARQL query towards a graph using maplib

```Python
import mash

# This dataset would already have been created like above, and would contain a RDF distribution
rdf_dataset = mash.fetch("urn:uuid:5393e12b-96c3-4fab-8b98-d4ba2e940b94")

# model_from_dataset is a utility method that finds RDF-comliant distributions in a dataset
# loading it from a connected repository into a maplib Model.
m = mash.model_from_dataset(rdf_dataset)
res = m.query("""
    PREFIX cim: <http://iec.ch/TC57/CIM100#>
    SELECT (COUNT(?s) as ?substation_count) WHERE {
        ?s a cim:Substation
    }
""")
print(res)
```

## Data catalog

To open a visual catalog of the data mash has access to, simply run `uv run poe catalog` and a page will open in your browser.
