Skip to content

python: Implement version-agnostic Protocol types - #114

Draft
bact wants to merge 34 commits into
JPEWdev:mainfrom
bact:python-protocol
Draft

python: Implement version-agnostic Protocol types#114
bact wants to merge 34 commits into
JPEWdev:mainfrom
bact:python-protocol

Conversation

@bact

@bact bact commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

This PR adds src/shacl2code/lang/templates/python/protocols.py.j2 template to generates Protocol structure types for classes in the generated bindings.

Given two versions of a model (v1 and v2; v2 is backward compatible with v1) to shacl2code to generate Python bindings (one at a time), it will generate two bindings that look similar but v1.SomeClass and v2.SomeClass are considered distinct. They are not sharing a common parent type.

This makes it difficult for a downstream user who wants to write a function like:

def func(x: SomeClass) -> SomeClass: ...

Because if it defines SomeClass as v1.SomeClass, it will be incompatible with v2.SomeClass (and vice versa).

This PR provides the generation of protocols.SomeClass which is a structural type that accepts any class with the same shape (has similar members), so v1.SomeClass and v2.SomeClass can be both accepted, even they are unrelated.

This function will work across all versions of the model (provided that they are keeping backward compatibility to previous versions -- not removing any members in newer versions):

def func(x: protocols.SomeClass) -> protocols.SomeClass: ...

bact added 3 commits June 27, 2026 22:36
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
@bact bact added the enhancement New feature or request label Jun 28, 2026
@bact
bact marked this pull request as draft June 28, 2026 03:30
@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  src/shacl2code/lang
  python.py
Project Total  

This report was generated by python-coverage-comment-action

bact added 6 commits June 28, 2026 09:47
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Follows --include-main pattern

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
bact added 3 commits July 1, 2026 21:18
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
@bact bact added this to the 1.2.0 milestone Jul 2, 2026
@bact
bact requested review from JPEWdev and removed request for JPEWdev July 3, 2026 18:13
@bact
bact marked this pull request as draft July 3, 2026 18:36
bact added 2 commits July 3, 2026 22:15
@bact

bact commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

This PR may need update after #124 got merged, as the protocols may need to have the new Ontology class too. (Possible to get that from auto-generation, but still need to add a test for confidence)

@bact
bact marked this pull request as ready for review July 4, 2026 22:33
@bact

bact commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

#124 merged. Need to review this PR (may need few revision). Put to draft.

@bact
bact marked this pull request as draft July 6, 2026 16:58
Follow the updated test.ttl

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
bact added 3 commits July 7, 2026 01:07
For cheap check of is_release, without the need to actually load the model

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
@bact
bact marked this pull request as ready for review July 9, 2026 00:00
@bact
bact marked this pull request as draft July 9, 2026 00:00
@bact
bact marked this pull request as ready for review July 9, 2026 11:30

def __getattr__(name: str) -> Any:
# PEP 562 lazy access: each branch imports only what it needs.
if name == "__all__":

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't this need 'main' from cmd (and protocols)?

Also, is it possible to maybe use model.__all__ instead of manually filtering?

@bact bact Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For first question, it is intentional since cmd and protocols will not be usually used (as a library) and we like to avoid loading anything that is not likely to be used.

For cmd and main, same rational as removing main from spdx-python-model binding generation at spdx/spdx-python-model#51

For protocols, its main use case is for type check and it is better to keep it separate because if we allow them in mix in top-level import * all *Protocol classes will be loaded during runtime (no exactly harmful, but unnecessary waste memory - every model class will have its own *Protocol class counterpart).

Users who want them still able to access them by using fully qualified name.

--

For 2nd one, we can use model.__all__ too but since currently model doesn't have __all__, we have to define it there.

Do it in model.py.j2 is more straightforward, we explicitly say what we like to export. It is also cheaper (do it a generation time). The downside is maintenance, you have to maintain the full list of __all__.

Do it in __init__.py.j2 retrospectively is more expensive but lower maintenance, you inspect actual available symbols, then filter it. You maintain only a small blacklist - which in this case tend to be stable. Since the imports tend to be only few times per process, I chose to do it in init.

Each approach has its own risks though. The static explicit list in model can under-export. The dynamic filter in init can over-export.

@bact bact modified the milestones: 1.2.0, 1.3.0 Aug 5, 2026
@bact
bact marked this pull request as draft August 13, 2026 11:07
bact added 6 commits August 14, 2026 00:22
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants