---
question: When do I run onto2py after changing Turtle?
kind: task
via_web: na
via_cli: available
via_python: available
via_api: na
via_config: na
ref_cli: onto2py --module
ref_python: onto2py_module()
prereq: You need a module ontology under `ontologies/` and the project's Python environment (`naas-abi-core` installed). See [Create an ontology](/user-guide/build/knowledge-graph/creating-an-ontology).
next:
  - /capabilities/build/knowledge-graph/creating-a-pipeline
  - /capabilities/build/knowledge-graph/enable-ontology
---

# Generate Python classes

`onto2py` reads a Turtle file and writes a Python file next to it: one Pydantic model per class, with a field per data property and an `rdf()` method that returns the entity's triples. It ships with `naas-abi-core`.

The engine does not generate these classes at startup. It loads the `.ttl`; your code imports the generated `.py`. Run `onto2py` after each Turtle change and commit the result.

## Web UI

Not applicable. Nexus reads ontology files but does not generate code from them. Use the CLI or Python on this page.

## CLI

### Generate the module

```bash
uv run python -m naas_abi_core.utils.onto2py --module src/my_module
```

The path is the module folder, the one that holds `ontologies/`. In order, it generates each process slice under `ontologies/processes/`, merges the slices into `ontologies/modules/MyModuleOntology.ttl`, then generates every module ontology that declares `a owl:Ontology`. It prints `📦 my_module: 2 generated, 0 unchanged`. `naas-abi-core` also installs the same entry point as an `onto2py` command.

### Run it again after a change

Each file's hash goes into `ontologies/onto2py.lock.json`, so the next run skips files that have not changed (`0 generated, 2 unchanged`). The hash covers the file itself, not the ontologies it imports: after an imported ontology changes, add `--force` to regenerate everything. A `could not resolve owl:imports` warning for the CCO and BFO imports is expected: the Turtle still loads in full.

### Check and commit

The run writes `MyModuleOntology.py` next to the Turtle file, and action-class stubs under `ontologies/classes/`. Commit the generated files, the merged module `.ttl` and `onto2py.lock.json`, and do not edit the generated files by hand. In the ABI repository itself, `make onto2py` regenerates every file under `libs/`.

To generate a single file instead, pass its path. A process file needs `ABI_SKIP_ONTOLOGY_CHECK=1`, because it restricts IRIs declared in the module file and fails the standalone check:

```bash
ABI_SKIP_ONTOLOGY_CHECK=1 uv run python -m naas_abi_core.utils.onto2py \
    src/my_module/ontologies/processes/MyModuleProcess.ttl
```

## Python

### Generate the module

```python title="scripts/generate_ontology.py"
from naas_abi_core.utils.onto2py.module_codegen import onto2py_module

report = onto2py_module("src/my_module")
print(f"{len(report.generated)} generated, {len(report.skipped)} unchanged")
assert report.ok, report.errors
```

`onto2py_module(module_root, force=False)` does what `--module` does and returns a report: `generated`, `skipped`, `errors` (file → message), `consolidated` (the merged module file) and `ok`. `force=True` regenerates unchanged files too.

### Or generate one file

```python
from naas_abi_core.utils.onto2py import onto2py

onto2py("src/my_module/ontologies/modules/MyModuleOntology.ttl")
```

`onto2py(ttl_file, overwrite=False)` checks the file, writes the `.py` next to it and returns the generated code. A check failure raises `ValueError` with every error. For a process file, set `ABI_SKIP_ONTOLOGY_CHECK=1` in the environment first. `overwrite=True` also rewrites existing stubs under `classes/`.

### Use the generated classes

```python
from my_module.ontologies.modules.MyModuleOntology import MyModuleRecord

record = MyModuleRecord(record_id="12345")
graph = record.rdf()   # an rdflib.Graph
```

Import models from `ontologies/modules/` or `ontologies/processes/`. Import from `ontologies/classes/` only for an action-class stub you have extended.

## REST API

Not applicable. Code generation runs in your project, not in the engine. No API route calls `onto2py`. The process list is the [API](/api).

## Config

Not applicable. No `config.yaml` key runs `onto2py`: generation is a step you run and commit.

## What the generated file holds

```python title="ontologies/modules/MyModuleOntology.py"
# Auto-generated - do not edit manually
class MyModuleRecord(..., RDFEntity):  # its parent classes, then the base entity
    """
    An information content entity that documents something about a person.
    """

    record_id: Annotated[str, Field(description="Identifier of the record in the source system.")] | None = None
```

Field names are the ontology's property names, in snake case. The class docstring is the `skos:definition`. Omit `_uri` when you build an entity and it gets a generated IRI. A pipeline that builds entities this way is on [Create a pipeline](/user-guide/build/knowledge-graph/creating-a-pipeline#using-onto2py-generated-classes).
