Skip to content

Generate Python type stub - allowing more code completion in IDE#74

Draft
bact wants to merge 17 commits intoJPEWdev:mainfrom
bact:typevar-propvalue
Draft

Generate Python type stub - allowing more code completion in IDE#74
bact wants to merge 17 commits intoJPEWdev:mainfrom
bact:typevar-propvalue

Conversation

@bact
Copy link
Collaborator

@bact bact commented Feb 22, 2026

This PR improves type annotations and adds Python type stub (.pyi) generation. This will allow more code completion in IDE.

Before this PR, only few class members are shown in autocompletion list:

Screenshot 2569-02-24 at 05 04 20

With this PR, more class members, including ones from its superclasses, are shown:

Screenshot 2569-02-24 at 04 56 26

(example from spdx-python-model -- need spdx/spdx-python-model#18 to copy the type stub to bindings path)

--

Improve type annotations in the generated Python binding:

  • Add Generic[T_PropValue] to Property and ListProxy classes to allow them to define specific types, reduce the use of Any.
  • Add type annotations to the generated class definitions (use DATATYPE_PYTHON_ALIASES mapping)
  • Replace native set with typing.Set to maintain Python 3.8 compatibility

Fix lint warnings in the generated code:

  • Remove unnecessary global/nonlocal where the variable is not being modified locally, to fix F824 Flake8 warning in the generated Python code.
  • Remove unused obj_data variable in SHACLExtensibleObject.__setitem__ to fix F841 warning in Ruff
  • Fix black formatting

--

They generated type stub will look like below.

Generated "link-class" from the test model:

# A class to test links
@register("http://example.org/link-class", abstract=False)
class http_example_org_link_class(SHACLObject):
    NODE_KIND: NodeKind = NodeKind.BlankNodeOrIRI
    IS_DEPRECATED: bool = False
    NAMED_INDIVIDUALS: Dict[str, str] = {}

The stub for that class:

class http_example_org_link_class(SHACLObject):
    NODE_KIND: NodeKind = ...
    ID_ALIAS: Optional[str] = ...
    IS_DEPRECATED: bool = ...
    NAMED_INDIVIDUALS: Dict[str, str] = ...
    derived_prop: Optional[Union[str, "http_example_org_link_derived_class"]]
    extensible: Optional[Union[str, "http_example_org_extensible_class"]]
    link_list_prop: ListProxy[Union[str, "http_example_org_link_class"]]
    link_prop: Optional[Union[str, "http_example_org_link_class"]]
    link_prop_no_class: Optional[Union[str, "http_example_org_link_class"]]
    tag: Optional[str]

bact added 4 commits February 23, 2026 04:19
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Fix ruff, flake8, and black warning in the generated Python code (output.py in test)

Fix Flake8 warning on "F824: name is never assigned in scope" by removing unnecessary global/nonlocal declarations.

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 Feb 22, 2026
@bact bact marked this pull request as draft February 22, 2026 22:46
@github-actions
Copy link

github-actions bot commented Feb 22, 2026

Coverage report

Click to see where and how coverage changed

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

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

bact added 6 commits February 23, 2026 06:21
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
With improved type hints some ignores are no longer needed

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
With the improved type hints and cast, the test now passed pyrefly check

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
To allow Jupyter/IPython notebook to see types, we need to keep annotations out of the block

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
@bact
Copy link
Collaborator Author

bact commented Feb 23, 2026

For code autocompletion, may need to implements two things:

  1. stub (.pyi), for IDE to read
  2. __dir___ overriding to export __annotations__ list, for interactive shell like Jupyter to read

Can't do default assignment to class variables because it will interfere with the existing __setattr__ and __getattr__ overrides.

return (iri for iri, _ in self.context)

def encode(self, encoder: Encoder, value: T_PropValue, state: EncodeState) -> None:
encoder.write_iri(str(value))
Copy link
Owner

Choose a reason for hiding this comment

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

This class (IRIProp) is intended to be abstract, which is why it didn't implement these; are they needed for typechecking for some reason?

def __eq__(self, other: Any) -> bool:
if isinstance(other, ListProxy):
return self.__data == other.__data
return bool(self.__data == other.__data)
Copy link
Owner

Choose a reason for hiding this comment

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

The bool() suprises me; I would have though a comparison operator would implicitly be a bool

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I though so as well. I'm actually trying to get rid of this because it's quite ugly....

Looks like mypy is being very defensive, as == can be override with __eq__ function and that new function can return anything.

Btw, now successfully removed few # type: ignore and one remaining pytest xfail. The code is sort of working now, but there are few places that look very clumsy. I will trying to clean that up.

Copy link
Collaborator Author

@bact bact Feb 24, 2026

Choose a reason for hiding this comment

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

Have update the code to try to convert Collection to a list, and None to an empty list, and compare. -- Not sure if it's a good idea.

What I'm trying to see here is if we provide enough isinstance checks, will it give enough information for the type checker to let us go.

I will tinkering around this for a bit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe not a good idea to convert Collection to list, as list suggests order.
Will revert that.

return bool(self.__data == other.__data)

return self.__data == other
return bool(self.__data == other)
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

v = self.prop.decode(val_d, state)
self.prop.validate(v)
data.append(v)
if v is not None:
Copy link
Owner

Choose a reason for hiding this comment

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

hmm, is this because decode() returns Optional[...]?

v = prop.decode(prop_d, state)
prop.validate(v)
self.__dict__["_obj_data"][iri] = v
if prop_d is not None:
Copy link
Owner

Choose a reason for hiding this comment

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

This check seems unnecessary

return (obj, "", "", "")
return (
obj._id or "",
getattr(obj, "_id", None) or "",
Copy link
Owner

Choose a reason for hiding this comment

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

Why this change?

# This covers custom extensions
reg_type(obj.TYPE, obj.COMPACT_TYPE, obj, True)
reg_type(
str(obj.TYPE),
Copy link
Owner

Choose a reason for hiding this comment

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

Why this cast?

reg_type(obj.TYPE, obj.COMPACT_TYPE, obj, True)
reg_type(
str(obj.TYPE),
str(obj.COMPACT_TYPE) if obj.COMPACT_TYPE else None,
Copy link
Owner

Choose a reason for hiding this comment

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

This also seems unnecessary

Generate stub (.pyi) for type annotations

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
@bact bact changed the title Typing strictness for Property and ListProxy, using Generic TypeVar Generate Python type stub - allowing code completion in IDE Feb 23, 2026
@bact bact changed the title Generate Python type stub - allowing code completion in IDE Generate Python type stub - allowing more code completion in IDE Feb 23, 2026
Convert different types to list and compare

Signed-off-by: Arthit Suriyawongkul <arthit@gmail.com>
@bact
Copy link
Collaborator Author

bact commented Feb 25, 2026

This PR is probably getting too big, maybe it's better to break it down.

  1. Fix Python 3.8 compatibility issue (native type)
  2. Add more/fix few type hints
  3. Generate stub

Will wait for #76 to get merged (it will changed structure of how we maintain class properties) and see where to work first.

@JPEWdev
Copy link
Owner

JPEWdev commented Feb 25, 2026

This PR is probably getting too big, maybe it's better to break it down.

1. Fix Python 3.8 compatibility issue (native type)

2. Add more/fix few type hints

3. Generate stub

Will wait for #76 to get merged (it will changed structure of how we maintain class properties) and see where to work first.

Sounds good. #76 is the last big change I have planned; there will be a few smaller ones after but they shouldn't interfere too much

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