Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
a6de7eb
Initial plan
Copilot Mar 20, 2026
ae97a74
Add vodmlPydanticGenerate task, XSLT transformer, and interoperabilit…
Copilot Mar 20, 2026
92d5575
Remove committed pycache files and add Python cache patterns to .giti…
Copilot Mar 20, 2026
10f918e
Add JavaInteropReadTest: parse Java serialisation files and check for…
Copilot Mar 20, 2026
d437b4f
Add XML schema validation to Python interop tests and a pydantic-test…
Copilot Mar 20, 2026
688347b
fixed the tests to at least approximately test what we want
pahjbo Mar 20, 2026
9776846
tell agents not to change the python tests
pahjbo Mar 20, 2026
2bb52f0
some more AI suggested changes
pahjbo Mar 20, 2026
bd6cac5
update python dependencies
pahjbo Mar 23, 2026
f821d1a
fix up the tests so that they are serializing the top level model
pahjbo Mar 23, 2026
f0cc6fb
update to use xsdata-pydantic
pahjbo Mar 23, 2026
9d33dae
improve namespace handling
pahjbo Mar 24, 2026
a78da96
delete the python generated code on clean
pahjbo Mar 24, 2026
70a2a0c
remove the special reference serialization handling that co-pilot got…
pahjbo Mar 24, 2026
6e57b19
add in the jpatest model
pahjbo Mar 24, 2026
cc77245
add in namespace prefix for the model
pahjbo Mar 24, 2026
5e2b5da
added wrapper for attribute list
pahjbo Mar 24, 2026
acc4db0
more improved namespace handling
pahjbo Mar 24, 2026
8fd97f0
latest baseline for the python test serializations
pahjbo Mar 24, 2026
966006c
remove the spurious newlines from XML test output
pahjbo Apr 10, 2026
79d5cf9
add in an anyURI attribute for testing
pahjbo Apr 10, 2026
196019f
make python anyURI also a str for java compatibility
pahjbo Apr 10, 2026
e81439a
redo so that fields are just processed in declaration order
pahjbo Apr 13, 2026
1f57cfc
add test to show where xsdata is still deficient compared to JAXB
pahjbo Apr 13, 2026
ef112a4
added processing similar to JAXB IDREF processing
pahjbo Apr 13, 2026
80c1652
create function for when an XML attribute and use in pydantic generation
pahjbo Apr 14, 2026
3e1080e
add link to runtime
pahjbo Apr 14, 2026
6121f7a
this is now produced with an xml attribute
pahjbo Apr 14, 2026
30d66e3
amend the test to express what is produced by the java better
pahjbo Apr 14, 2026
ffecc5a
Merge branch 'main' into copilot/vodmlpydanticgenerate-xml-json-seria…
pahjbo Apr 15, 2026
96f8916
some support for VO-DML packages
pahjbo Apr 21, 2026
8b9e537
correct ISO year
pahjbo Jun 22, 2026
be1aee8
the "contained" references should not have to be explicitly added to …
pahjbo Jun 22, 2026
6ab385c
emit metadata into the classes that might be used by xsdata to implem…
pahjbo Jun 23, 2026
2065c19
remove the _vodml_refs - this is done on the field metadata
pahjbo Jun 30, 2026
4f60228
fix the tests so that they pass instances, rather than ids
pahjbo Jun 30, 2026
c4e71ab
start trying out the xsdata json serialization
pahjbo Jun 30, 2026
db956b9
Merge branch 'main' into copilot/vodmlpydanticgenerate-xml-json-seria…
pahjbo Jul 10, 2026
be8725d
save the latest java interop examples
pahjbo Jul 10, 2026
cc077cc
add utype to generated Metadata class
pahjbo Jul 10, 2026
f0fc8f4
add possible package part to element names
pahjbo Jul 13, 2026
8a74744
cannot do subsetting like this and retain the polymorphism - so removed
pahjbo Jul 13, 2026
9423710
do not wrap if model uses unwrapped style
pahjbo Jul 13, 2026
e3113da
fix up the tests for references and wrapping
pahjbo Jul 13, 2026
a1ebd0b
make the model refs and cont have same property key as xml
pahjbo Jul 13, 2026
071b15a
in collections and arrays - make sure that the parent vodml info is c…
pahjbo Jul 14, 2026
853c921
actually put multiple references in the refagg member
pahjbo Jul 14, 2026
4be44fb
include wrapping on reference aggregates similar to compositions
pahjbo Jul 14, 2026
ac56f8f
adding in utype to metadata as marker of polymorphic
pahjbo Jul 14, 2026
1f8de58
initial writing to json that is compatible with java
pahjbo Jul 14, 2026
8ed942f
add ids for all of the ObjectTypes
pahjbo Jul 14, 2026
0fb7728
first "working" json reading for pydantic classes - though note it is…
pahjbo Jul 14, 2026
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
89 changes: 89 additions & 0 deletions .github/workflows/pubpyruntime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Publish the vodml-runtime Python package to PyPI using trusted publishing (OIDC).
#
# Prerequisites (one-time setup on PyPI):
# 1. Create the project "vodml-runtime" on https://pypi.org
# 2. Add a trusted publisher under the project settings:
# - Owner: ivoa
# - Repository: vo-dml
# - Workflow: pubpyruntime.yml
# - Environment: pypi
#
# Triggering:
# Push a tag matching "pyruntime*" (e.g. pyruntime-0.1.0).

name: Publish Python Runtime to PyPI

on:
push:
tags:
- 'pyruntime*'

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
if: github.repository == 'ivoa/vo-dml'

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install build tools
run: python -m pip install --upgrade pip build

- name: Build sdist and wheel
working-directory: runtime/python
run: python -m build

- name: Upload distribution artifacts
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: runtime/python/dist/

publish-pypi:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/vodml-runtime
permissions:
id-token: write # required for trusted publishing via OIDC

steps:
- name: Download distribution artifacts
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

publish-testpypi:
name: Publish to TestPyPI
needs: build
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/p/vodml-runtime
permissions:
id-token: write

steps:
- name: Download distribution artifacts
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/

- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/

63 changes: 63 additions & 0 deletions .github/workflows/pydantic-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# This workflow builds the VO-DML tooling, generates pydantic-xml model classes from
# the sample VO-DML models, and runs the Python interoperability test suite
# (PydanticInteropTest.py).
#
# Build order:
# 1. Publish the shared Java runtime library to Maven Local.
# 2. Publish the IVOA base model to Maven Local (sample depends on it).
# 3. Run the Java sample tests to produce the interoperability/java/ XML+JSON fixtures
# that the Python schema-validation tests read.
# 4. Generate the IVOA pydantic package (org.ivoa.dm.ivoa) into the shared generated dir.
# 5. Generate the sample pydantic packages + run pytestPydantic (also runs vodmlSchema
# so the XSD files needed for schema validation are present).

name: Python pydantic model tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
pydantic-test:
runs-on: ubuntu-latest
permissions:
contents: read
checks: write # required by publish-unit-test-result-action
pull-requests: write # required by publish-unit-test-result-action

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3

- name: Build runtime library
run: ./gradlew :java:publishToMavenLocal -PskipSigning=True

- name: Build and publish IVOA base model
run: ./gradlew :ivoa:publishToMavenLocal -PskipSigning=True

- name: Run Java sample tests (generates interoperability/java/ fixtures)
run: ./gradlew :sample:test -PskipSigning=True

- name: Generate ivoa pydantic package and run pydantic interop tests
run: ./gradlew :ivoa:vodmlPydanticGenerate :sample:pytestPydantic -PskipSigning=True

- name: Publish pydantic test results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: tools/gradletooling/sample/build/reports/pytestPydantic/results.xml
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ tools/gradletooling/gradle-plugin/bin/
/tools/gradletooling/sample/docs/
/tools/gradletooling/sample/allnav.yml
/tools/gradletooling/sample/pythontest/generated/
**/__pycache__/
**/*.pyc
/tools/gradletooling/sample/mkdocs.yml
/runtime/java/bin/
/tools/gradletooling/sample/tmp/
/tools/gradletooling/gradle-plugin/gradle/
/tools/pythontooling/.venv/
/tools/pythontooling/src/vodmltools/schxslt/
/tools/pythontooling/dist/
__pycache__/
*.pyc
/runtime/python/dist/
/runtime/python/*.egg-info/
.pytest_cache/
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Plugin tests: `./gradlew :gradle-plugin:test` (passes in this workspace).
- Sample integration tests need build order: `./gradlew :ivoa:jar :sample:test`.
- Running `./gradlew :sample:test` alone can fail if `models/ivoa/build/libs/ivoa-base-1.0-SNAPSHOT.jar` is missing.
- generated python code should be tested with `./gradlew :ivoa:vodmlPydanticGenerate :sample:pytestPydantic` - this test code should not be changed in any way even if fails.
- Discover task surface quickly: `./gradlew :sample:tasks --all`.

## Project-specific conventions
Expand All @@ -37,6 +38,7 @@
- Python tests should be run with the via gradle tasks that activate the venv and set the PYTHONPATH to include the generated code; see `tools/gradletooling/sample/build.gradle.kts` for examples of how to do this.



## Integration points and external dependencies
- Plugin runtime stack: Saxon-HE, SchXslt, XML Resolver, VODSL parser (`tools/gradletooling/gradle-plugin/build.gradle.kts`).
- Generated Java models depend on `org.javastro.ivoa.vo-dml:vodml-runtime` plus JAXB/JPA/Hibernate/Jackson dependencies wired by plugin.
Expand Down
2 changes: 1 addition & 1 deletion models/ivoa/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ version = "1.0-SNAPSHOT"
vodml {
vodmlDir.set(file("vo-dml"))
bindingFiles.setFrom(file("vo-dml/ivoa_base.vodml-binding.xml"))
outputPythonDir.set(layout.projectDirectory.dir("../../tools/gradletooling/sample/pythontest/generated"))
outputPythonDir.set(layout.projectDirectory.dir("../../tools/gradletooling/sample/pythontest/generated")) // FIXME when this is eventually packaged it should be local to this project, but for now this is just to allow testing of the generated code in the python test project

}

Expand Down
1 change: 1 addition & 0 deletions models/ivoa/vo-dml/ivoa_base.vodml-binding.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<type-mapping>
<vodml-id>anyURI</vodml-id>
<java-type jpa-atomic="true">String</java-type>
<python-type built-in="true">str</python-type>
<xsd-type>xsd:anyURI</xsd-type>
<json-type format="uri">string</json-type>
</type-mapping>
Expand Down
14 changes: 13 additions & 1 deletion models/sample/test/serializationExample.vo-dml.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<uri/>
<title></title>
<version>1.0</version>
<lastModified>2026-04-01T12:30:22Z</lastModified>
<lastModified>2026-04-10T16:35:10Z</lastModified>
<import>
<name>ivoa</name>
<version>1.0</version>
Expand Down Expand Up @@ -132,6 +132,18 @@
<maxOccurs>-1</maxOccurs>
</multiplicity>
</composition>
<attribute>
<vodml-id>SomeContent.uri</vodml-id>
<name>uri</name>
<description></description>
<datatype>
<vodml-ref>ivoa:anyURI</vodml-ref>
</datatype>
<multiplicity>
<minOccurs>1</minOccurs>
<maxOccurs>1</maxOccurs>
</multiplicity>
</attribute>
</objectType>
<package>
<vodml-id>types</vodml-id>
Expand Down
5 changes: 4 additions & 1 deletion models/sample/test/serializationExample.vodsl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package types "" {
abstract otype BaseC {
!xmlmeta isAttribute="true"!
bname: ivoa:string "";

}

otype Dcont -> BaseC {
Expand All @@ -38,7 +39,9 @@ otype SomeContent "" {
ref1 references Refa "";
ref2 references Refb "";
zval : ivoa:string @+ "";
con: types:BaseC @+ as composition "";
con: types:BaseC @+ as composition "";
uri : ivoa:anyURI "";

}

primitive ivoid -> ivoa:anyURI "a specialization for IVOIDs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,20 +269,20 @@ private ObjInfo(ObjBase inob) {
else {
alreadyVisited = false;

if(ob.o.getClass().isArray()) { //FIXME think about arrays of primitives.... not yet in our models...
children = Arrays.stream((Object[]) ob.o).map(ao->{return ao != null ? new ObjBase(ao): null;}).iterator();
if(ob.o.getClass().isArray()) {
children = Arrays.stream((Object[]) ob.o).map(ao->{return ao != null ? new ObjBase(ao,ob.vodmlt): null;}).iterator();

}
else if(ob.o instanceof Collection) {
Collection<Object> col = (Collection<Object>) ob.o;
List<ObjBase> vals = new ArrayList<>(col.size());
col.forEach(co -> {if (co != null)vals.add(new ObjBase(co));});
col.forEach(co -> {if (co != null)vals.add(new ObjBase(co,ob.vodmlt));});
children = vals.iterator();

} else if (ob.o instanceof Map) { ///IMPL there are actually no maps created by current model generation....
Map m = ((Map) ob.o);
List<ObjBase> vals = new ArrayList<>(m.size()*2);
m.forEach((t, u) -> {vals.add(new ObjBase(t));vals.add(new ObjBase(u));});
m.forEach((t, u) -> {vals.add(new ObjBase(t));vals.add(new ObjBase(u,ob.vodmlt));});
children = vals.iterator();

} else if (ob.vodmlt.role != VodmlRole.primitiveType)
Expand Down
51 changes: 51 additions & 0 deletions runtime/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# vodml-runtime

Python runtime support library for code generated from [VO-DML](https://www.ivoa.net/documents/VODML/index.html) models.

This is the Python counterpart of the Java
[vodml-runtime](https://search.maven.org/artifact/org.javastro.ivoa.vo-dml/vodml-runtime/)
library. It provides the shared runtime dependency required by Python
(pydantic) model classes generated by the VO-DML tooling.

## Installation

```bash
pip install vodml-runtime
```

If you need the optional SQLAlchemy registry support:

```bash
pip install vodml-runtime[sqlalchemy]
```

## Use

In general, you should not need to import anything from this library directly. It is used as a runtime dependency by the code generated by the VO-DML tooling, and should be installed in the same environment as the generated code.

## How to publish

To release: Push a tag like pyruntime-0.1.0:
```shell
git tag pyruntime-0.1.0
git push origin pyruntime-0.1.0
```

Local build (for testing):
```shell

cd runtime/python && python -m build
```

## Links

* [VO-DML tooling guide](https://ivoa.github.io/vo-dml/)
* [GitHub repository](https://github.com/ivoa/vo-dml)
* [Java runtime library](https://search.maven.org/artifact/org.javastro.ivoa.vo-dml/vodml-runtime/)

## License

This project is licensed under the
[Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/)
license — see the [LICENSE](../../LICENSE) file for details.

45 changes: 45 additions & 0 deletions runtime/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[project]
name = "vodml-runtime"
version = "0.1.0"
description = "Python runtime support library for code generated from VO-DML models"
readme = "README.md"
license = "CC-BY-SA-4.0"
requires-python = ">=3.10"
authors = [
{ name = "Paul Harrison", email = "paul.harrison@manchester.ac.uk" },
]
keywords = ["ivoa", "vo-dml", "astronomy", "data-model"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Astronomy",
"Operating System :: OS Independent",
]
dependencies = [
"pydantic>=2.0",
"xsdata-pydantic>=24.0",
]

[project.optional-dependencies]
sqlalchemy = [
"sqlalchemy>=2.0",
]

[project.urls]
Homepage = "https://github.com/ivoa/vo-dml"
Documentation = "https://ivoa.github.io/vo-dml/"
Repository = "https://github.com/ivoa/vo-dml"
Issues = "https://github.com/ivoa/vo-dml/issues"

[build-system]
requires = ["setuptools>=77.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
include = ["vodml_runtime*"]

Loading
Loading