Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0

[Full changelog](https://github.com/linkml/linkml-project-copier/compare/v0.5.0...main)

### Added

- New opt-in `use_sssom` prompt (default: no) adding
[SSSOM](https://mapping-commons.github.io/sssom/spec/) support to generated
projects: the `sssom` (sssom-py) and `ruamel.yaml` dev dependencies, a
`src/<slug>/mappings/` directory for curated `*.sssom.tsv` mapping files
(example TSVs when `add_example` is chosen), and `scripts/overlay_sssom.py`
tool that merges the TSV mappings into the schema's LinkML mapping slots
(`exact_mappings`, `close_mappings`, etc.) idempotently, preserving YAML
comments and formatting. TSV parsing is delegated to sssom-py.
- New `just` recipes for `use_sssom` projects, shipped in an `sssom.justfile`
imported optionally by main justfile: `gen-sssom` (export element mappings
to SSSOM/TSV), `validate-sssom` (CURIE validation of the export),
`overlay-sssom` (apply/preview/check the mappings overlay), and
`test-overlay-sssom` (bundled overlay unit tests; only with `add_example`).
- CI drift gate for `use_sssom` projects: the generated `main.yaml` workflow
runs `just overlay-sssom --check` and fails on schema vs SSSOM TSV drift.
`just setup` applies the overlay so freshly projects start in sync.
- Curated mapping files (`src/<slug>/mappings/*`) are protected from being
overwritten on template updates via `_skip_if_exists`.
- Integration tests for the `use_sssom` template option (recipes, drift-gate
lifecycle, and setup sync) alongside `use_sssom`/`add_example` combinations.

## Release [0.5.0] - 2026-05-27

[Full changelog](https://github.com/linkml/linkml-project-copier/compare/v0.4.2...v0.5.0)
Expand Down
17 changes: 17 additions & 0 deletions copier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ gh_action_docs_preview:
type: bool
default: true

use_sssom:
help: |
Add support for SSSOM (Simple Standard for Sharing Ontology Mappings)?
This adds the "sssom" (sssom-py) and "ruamel.yaml" dev dependencies, a
src/<slug>/mappings/ directory for curated *.sssom.tsv files, the
scripts/overlay_sssom.py tool to project those mappings into the schema's
LinkML mapping slots, and `just` recipes: gen-sssom, validate-sssom,
overlay-sssom, and test-overlay-sssom.
type: bool
default: false

# === copier configuration options ===
# https://copier.readthedocs.io/en/stable/configuring/#available-settings

Expand All @@ -129,6 +140,7 @@ _skip_if_exists:
- examples/README.md
- project/*
- src/{{project_slug}}/datamodel/*
- src/{{project_slug}}/mappings/*
- src/{{project_slug}}/schema/*
- tests/test_data.py
- tests/data/*
Expand All @@ -139,6 +151,11 @@ _message_after_copy: |

Next step (if you have not previously initialized your project)
run "just setup" in the root of your project directory.
{%- if use_sssom %}

Note: "just setup" also runs "just overlay-sssom" to sync the SSSOM
mappings into the schema; CI checks this stays in sync (drift gate).
{%- endif %}

_migrations:
# Migrations are only run on update (not on copy) and only if the update goes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v8.2.0
with:
python-version: ${{ matrix.python-version }}
{% raw %}python-version: ${{ matrix.python-version }}{% endraw %}
enable-cache: true
cache-dependency-glob: "uv.lock"

# https://github.com/actions/setup-python
- name: Set up Python ${{ matrix.python-version }}
- name: {% raw %}Set up Python ${{ matrix.python-version }}{% endraw %}
uses: actions/setup-python@v6.3.0
with:
python-version: ${{ matrix.python-version }}
{% raw %}python-version: ${{ matrix.python-version }}{% endraw %}

- name: Install just
run: |
Expand All @@ -53,3 +53,10 @@ jobs:

- name: Run test suite
run: just test
{%- if use_sssom %}

# Fails when src/<slug>/mappings/*.sssom.tsv and the schema have
# drifted apart; run "just overlay-sssom" locally to re-sync.
- name: Check SSSOM mappings are in sync with the schema
run: just overlay-sssom --check
{%- endif %}
28 changes: 27 additions & 1 deletion template/README.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
(edit this)
* [datamodel/](src/{{project_slug}}/datamodel) -- generated
Python datamodel
{%- if use_sssom %}
* [mappings/](src/{{project_slug}}/mappings) -- SSSOM/TSV
mapping files (`*.sssom.tsv`)
{%- endif %}
* [tests/](tests/) - Python tests
* [data/](tests/data) - Example data

Expand All @@ -28,7 +32,29 @@
There are several pre-defined command-recipes available.
They are written for the command runner [just](https://github.com/casey/just/).
To list all pre-defined commands, run `just` or `just --list`.

{% if use_sssom %}
## SSSOM Mappings

Cross-references between schema elements and external vocabularies are
maintained as [SSSOM/TSV](https://mapping-commons.github.io/sssom/spec/)
files under [src/{{project_slug}}/mappings/](src/{{project_slug}}/mappings).
[`scripts/overlay_sssom.py`](scripts/overlay_sssom.py) merges every
`*.sssom.tsv` into the matching LinkML mapping slot on the matching
schema element, and is idempotent. Multi-module schemas under
[src/{{project_slug}}/schema/](src/{{project_slug}}/schema) are
discovered recursively.

```sh
just overlay-sssom # apply
just overlay-sssom --dry-run # preview without writing
just overlay-sssom --check # CI gate: exit 1 if schema is out of sync
{% if add_example %}just test-overlay-sssom # run the bundled unit test
{% endif %}```

Run `python scripts/overlay_sssom.py --help` for the full set of flags,
overrides, and a description of how predicates, subject prefixes, and
permissible-value `meaning` promotion are handled.
{% endif %}
## Credits

This project uses the template [linkml-project-copier](https://github.com/linkml/linkml-project-copier).
7 changes: 7 additions & 0 deletions template/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ setup: _wsl2_compat_check _check-config _git-init install _git-add && _setup_par
git commit -m "Initialise git with minimal project" -a || true

_setup_part2: gen-project gen-doc
@if [ -f sssom.justfile ]; then \
echo 'Applying SSSOM mappings to the schema ...'; \
just overlay-sssom; \
fi
@echo
@echo '=== Setup completed! ==='
@echo 'Various model representations have been created under directory "project". By default'
Expand Down Expand Up @@ -285,6 +289,9 @@ _ensure_examples_output: # Ensure a clean examples/output directory exists

import "project.justfile"

# SSSOM mapping recipes (present only if this project enabled `use_sssom`)
import? "sssom.justfile"

# ====== Override recipes from above with custom versions =======

# Uncomment the following line to allow duplicate recipe names
Expand Down
4 changes: 4 additions & 0 deletions template/pyproject.toml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ dev = [
"mkdocs-pymdownx-material-extras>=2.5.6",
"jupyter>=1.0.0",
"mknotebooks>= 0.8.0",
{%- if use_sssom %}
"sssom>=0.4.0",
"ruamel.yaml>=0.18",
{%- endif %}
]

# See https://hatch.pypa.io/latest/config/build/#file-selection for how to
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Mappings Directory

This directory stores cross-references from `{{project_slug}}` schema to external
vocabularies as [SSSOM/TSV](https://mapping-commons.github.io/sssom/spec/) files.

The TSV files are the source of truth, and `scripts/overlay_sssom.py` is the
compiler that replays those mappings into LinkML schema mapping slots.

## How The Overlay Script Works

When you run `just overlay-sssom`, the script:

1. Recursively reads every `*.sssom.tsv` under this directory.
2. For each row, maps `predicate_id` to the correct LinkML slot.
3. Finds the matching schema element by `subject_id` local name.
4. Appends new CURIEs without duplicating existing ones.
5. Adds missing object-side prefixes to schema `prefixes:` from `#curie_map:` metadata.

It is idempotent: running it again without TSV changes does nothing.

## What A Row Targets

Use these subject formats:

- `{{project_slug}}:<Name>` for class, slot, enum, type, or attribute.
- `{{project_slug}}:<Enum>/<PermissibleValue>` for a permissible value.

Subject prefixes are discovered from schema `default_prefix` and `name`.
Use `--subject-prefix` only for aliases.

## Predicate Mapping Rules

- `skos:exactMatch` -> `exact_mappings`
- `skos:closeMatch` -> `close_mappings`
- `skos:broadMatch` -> `broad_mappings`
- `skos:narrowMatch` -> `narrow_mappings`
- `skos:relatedMatch` -> `related_mappings`
- `owl:equivalentClass` -> `exact_mappings`
- `owl:equivalentProperty` -> `exact_mappings`

Ignored: `skos:broader`, `skos:narrower`, `rdf:type`, and unsupported predicates.

## Permissible Value Rule

For permissible values, the first `skos:exactMatch` is promoted to `meaning` if
`meaning` is not already set. Additional exact matches are in `exact_mappings`.

## Required TSV Fields

Required columns are:

- `subject_id`
- `predicate_id`
- `object_id`

Include `#curie_map:` in metadata for every non-builtin prefix used in the file
(project prefix and external prefixes like `schema`, `NCIT`).

## Recommended Workflow

```sh
just overlay-sssom --dry-run
just overlay-sssom
just overlay-sssom --check
```

Use `--check` in CI to fail when TSV and schema are out of sync.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#mapping_set_id: https://w3id.org/{{github_org}}/{{project_name}}/mappings/{{project_slug}}-personstatus.sssom.tsv
#mapping_set_description: Example permissible-value mappings for PersonStatus to NCIT terms.
#curie_map:
# {{project_slug}}: https://w3id.org/{{github_org}}/{{project_name}}/
# NCIT: http://purl.obolibrary.org/obo/NCIT_
#license: https://creativecommons.org/publicdomain/zero/1.0/
subject_id predicate_id object_id mapping_justification comment
{{project_slug}}:PersonStatus/ALIVE skos:exactMatch NCIT:C37987 semapv:ManualMappingCuration ALIVE already has meaning PATO:0001421; this lands in exact_mappings instead.
{{project_slug}}:PersonStatus/DEAD skos:closeMatch NCIT:C28554 semapv:LLMBasedMatching Close mapping is independent of the existing meaning slot.
{{project_slug}}:PersonStatus/UNKNOWN skos:exactMatch NCIT:C17998 semapv:TransformerBasedMatching UNKNOWN has no meaning yet; the first exact mapping is promoted to meaning.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#mapping_set_id: https://w3id.org/{{github_org}}/{{project_name}}/mappings/{{project_slug}}-schemaorg.sssom.tsv
#mapping_set_description: Example mappings of demo schema entities to schema.org terms (classes and slots).
#curie_map:
# {{project_slug}}: https://w3id.org/{{github_org}}/{{project_name}}/
# schema: http://schema.org/
#license: https://creativecommons.org/publicdomain/zero/1.0/
#mapping_provider: http://schema.org/
subject_id predicate_id object_id mapping_justification
{{project_slug}}:NamedThing skos:exactMatch schema:Thing semapv:ManualMappingCuration
{{project_slug}}:Person skos:exactMatch schema:Person semapv:MachineLearningBasedMatching
{{project_slug}}:PersonCollection skos:closeMatch schema:ItemList semapv:EmbeddingBasedMatching
{{project_slug}}:primary_email skos:exactMatch schema:email semapv:ManualMappingCuration
{{project_slug}}:birth_date skos:exactMatch schema:birthDate semapv:GraphRepresentationLearningBasedMatching
{{project_slug}}:age_in_years skos:relatedMatch schema:Number semapv:ManualMappingCuration
Loading