Skip to content
Merged
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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ and this project follows [Semantic Versioning](https://semver.org/).

### Changed

- Editors and type checkers now accept the documented `snake_case`
constructor arguments, such as `InternalElement(name="Motor")`. The CAEX
aliases moved into `Annotated[...]` field metadata, which pyright and
Pylance ignore when naming `__init__` parameters. Previously pyright
reported every such argument as unknown and the alias as missing. Runtime
behavior, field metadata, AML JSON/XML output, and the generated JSON Schema
are unchanged.
- The README is now a beginner-oriented introduction: what AutomationML is, the
core CAEX concepts, installation including extras, and a step-by-step
tutorial whose every snippet runs on the base install. The OPC UA usage,
Expand Down Expand Up @@ -58,6 +65,25 @@ and this project follows [Semantic Versioning](https://semver.org/).

### Added

- `automationml.__version__`, defined once in `automationml/_version.py`,
which the build also reads. `source_document()` and `caex_file()` now record
it as `OriginVersion` instead of a hard-coded `0.1.0`.
- `MissingDependencyError`, a `ModuleNotFoundError` subclass raised when OPC
UA or RDF conversion is used without its extra. Its message names the
`pip install "automationml[...]"` command, replacing a bare
`No module named 'lxml'`.
- `AMLParseError` for AML XML that is not a readable CAEX 2.15 or 3.0
document: malformed XML, a non-`CAEXFile` root, or an unsupported schema
version. It subclasses `LegacyConversionError`, which was raised before, so
existing handlers keep working.
- `AutomationMLError` and `CAEXValidationError` are now importable from the
top-level package.
- Builders for all four class-library kinds: `system_unit_class`,
`system_unit_class_lib`, `role_class`, `role_class_lib`, `interface_class`,
`interface_class_lib`, `attribute_type`, and `attribute_type_lib`. They
return the hierarchical `*Family` models that libraries hold, so authors no
longer need to know that a library rejects a plain `SystemUnitClass` or
`RoleClass`.
- Extension-safe, namespace-aware mixed XML content in `AdditionalInformation`
with a documented AML JSON `$xml` representation and generated schema.
- Immutable `CAEXQuery` snapshots with ID/path lookup, reverse references,
Expand Down
28 changes: 17 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ brackets, for example `python -m pip install -e ".[opcua,rdf]"`:
| `learning` | Jupyter, for the [learning notebooks](learning/) |
| `dev` | Everything needed to run the test suite |

If you use a feature without its extra, the SDK raises a
`MissingDependencyError` that names the command to install it.

## Tutorial: your first plant model

This walkthrough uses only the base installation. Run the snippets in order in
Expand Down Expand Up @@ -148,7 +151,7 @@ The printed AML JSON uses the official AutomationML names:
{
"OriginName": "AutomationML Python SDK",
"OriginID": "automationml-python",
"OriginVersion": "0.1.0",
"OriginVersion": "0.1.0a1",
"LastWritingDateTime": "2026-10-17T12:00:00Z"
}
],
Expand Down Expand Up @@ -216,19 +219,17 @@ error unresolved-system-unit-class-reference

### 4. Fix it with a class library

Add the missing library with a `Motor` template. Libraries hold
`SystemUnitFamily` entries, which are SystemUnitClasses that may contain
sub-classes:
Add the missing library with a `Motor` template:

```python
from automationml import SystemUnitClassLib, SystemUnitFamily
from automationml import system_unit_class, system_unit_class_lib

doc.system_unit_class_libs.append(
SystemUnitClassLib(
name="MyComponents",
system_unit_classes=[
SystemUnitFamily(
name="Motor",
system_unit_class_lib(
"MyComponents",
classes=[
system_unit_class(
"Motor",
attributes=[attribute("speed", unit="rpm", data_type="xs:double")],
)
],
Expand All @@ -238,6 +239,9 @@ doc.system_unit_class_libs.append(
assert doc.caex_validation_issues() == [] # the reference resolves now
```

`role_class_lib`, `interface_class_lib`, and `attribute_type_lib` build the
other three kinds of library in the same way.

### 5. Create objects from the template

```python
Expand Down Expand Up @@ -294,7 +298,9 @@ official CAEX names:

All models are [Pydantic](https://docs.pydantic.dev/) models, so you can also
build them from dictionaries that use either kind of name, and they check your
values as you assign them.
values as you assign them. Editors and type checkers such as VS Code (Pylance),
pyright, and mypy understand the `snake_case` names, so you get autocompletion
and warnings for typos or missing required fields.

## What else can it do?

Expand Down
16 changes: 16 additions & 0 deletions docs/sdk-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ and the SDK serializes:
}
```

The same holds for type checkers. Model fields declare their CAEX alias inside
`Annotated` and keep the default on the right-hand side:

```python
name: Annotated[str, Field(alias="Name")]
attributes: Annotated[list[Attribute], Field(alias="Attribute")] = Field(
default_factory=list
)
```

Type checkers name a Pydantic model's `__init__` parameters after the alias of
the assigned `Field`, but ignore `Field` metadata inside `Annotated`. Written
this way, editors accept `InternalElement(name=...)`, while Pydantic still
validates and serializes the `Name` alias. A plain `= Field(alias="Name")`
would make pyright and Pylance reject the `snake_case` name.

## XML contract

XML is an adapter, not the primary model. The XML layer:
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "automationml"
version = "0.1.0a1"
dynamic = ["version"]
description = "JSON-first Python SDK for AutomationML CAEX 3.0 documents."
readme = "README.md"
requires-python = ">=3.11"
Expand Down Expand Up @@ -75,6 +75,9 @@ Documentation = "https://github.com/AutomationML/automationml-python/tree/main/d
Issues = "https://github.com/AutomationML/automationml-python/issues"
Changelog = "https://github.com/AutomationML/automationml-python/blob/main/CHANGELOG.md"

[tool.hatch.version]
path = "src/automationml/_version.py"

[tool.hatch.build.targets.wheel]
packages = ["src/automationml"]

Expand Down
26 changes: 26 additions & 0 deletions src/automationml/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
"""AutomationML CAEX 3.0 JSON-first SDK."""

from ._version import __version__
from .builders import (
attribute,
attribute_type,
attribute_type_lib,
caex_file,
external_interface,
instance_hierarchy,
interface_class,
interface_class_lib,
internal_element,
role_class,
role_class_lib,
role_requirement,
source_document,
system_unit_class,
system_unit_class_lib,
)
from .io import dump_json, dump_xml, load_json, load_xml
from .models import (
Expand Down Expand Up @@ -51,10 +60,14 @@
)
from .changes import ChangeOperation, ChangeSet, DocumentEdit, EditResult, diff_documents
from .exceptions import (
AMLParseError,
AmbiguousReferenceError,
AutomationMLError,
CAEXValidationError,
ChangeSetError,
LegacyConversionError,
LegacyConversionWarning,
MissingDependencyError,
ReferenceNotFoundError,
)
from .legacy import AMLImportResult, MigrationIssue
Expand All @@ -78,19 +91,23 @@
from .validation import CAEXIssue, ReferenceIndex, ReferenceTarget, validate_caex_document

__all__ = [
"__version__",
"AdditionalInformation",
"AMLImportResult",
"AMLParseError",
"AmbiguousReferenceError",
"Attribute",
"AttributeNameMapping",
"AttributeType",
"AttributeTypeLib",
"AttributeValueRequirement",
"AutomationMLError",
"CAEXBasicObject",
"CAEXFile",
"CAEXIssue",
"CAEXObject",
"CAEXQuery",
"CAEXValidationError",
"ChangeOperation",
"ChangeSet",
"ChangeSetError",
Expand All @@ -112,6 +129,7 @@
"MergePolicy",
"MergeResult",
"MigrationIssue",
"MissingDependencyError",
"NominalScaledType",
"OrdinalScaledType",
"ReferenceIndex",
Expand All @@ -135,18 +153,26 @@
"XmlExtensionNode",
"XmlExtensionPayload",
"attribute",
"attribute_type",
"attribute_type_lib",
"caex_file",
"dump_json",
"dump_xml",
"diff_documents",
"external_interface",
"instance_hierarchy",
"interface_class",
"interface_class_lib",
"internal_element",
"load_json",
"load_xml",
"merge_documents",
"role_class",
"role_class_lib",
"role_requirement",
"source_document",
"system_unit_class",
"system_unit_class_lib",
"resolve_and_merge",
"resolve_external_references",
"validate_caex_document",
Expand Down
7 changes: 7 additions & 0 deletions src/automationml/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Version of the AutomationML Python SDK.

This is the single source of the package version: hatchling reads it at build
time (see ``[tool.hatch.version]`` in ``pyproject.toml``).
"""

__version__ = "0.1.0a1"
Loading
Loading