Skip to content

Conversation

@dr-carlos
Copy link
Contributor

@dr-carlos dr-carlos commented Dec 5, 2025

  • Clarified in the glossary that an annotate function can be any callable (as is already the case).
  • Improved error messages if call_annotate_function() tries to run with fake globals but can't find the annotate function's __code__ attribute.
  • Added defaults if other non-required attributes cannot be found for fake globals, e.g. if the annotate function doesn't provide __defaults__, that's fine, just assume it's None.
    If required, I can remove some (or all) of these defaults, particularly __globals__ which adds an extra parameter to _build_closure(), or __builtins__ which is semantically correct but a little more complicated than the rest (it would be much simpler to just fallback to builtins.__dict__).
  • Added tests for the current support, added support, and error messages associated with non-function callables as annotate functions..
  • Added a section to the annotationlib documentation providing a simple recipe for a 'minimum viable implementation' of a custom callable class as annotate function. Happy to remove if this is unnecessary/too niche - I just thought it was the clearest way of demonstrating how to implement one of these, without just spelling out which dunders they need to implement.

📚 Documentation preview 📚: https://cpython-previews--142327.org.readthedocs.build/

Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

I am not quite convinced that this should be done. __annotate__ is already complex enough.

In the issue itself there were no real use-cases that can support the idea.

In my opinion, it is fine to have __annotate__ magic function as function only.

Copy link
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

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

I really prefer this version where we leave most of the work to the user because it's an advanced feature that they need to respect. But I also don't know whether it's worth it. It kinda slows down annotate functions for everyone. Maybe just documenting what needs to be present is sufficient but we maybe just say "Your callable should behave like a function, with the same dunders".

Comment on lines +743 to +750
try:
annotate_code = annotate.__code__
except AttributeError:
raise AttributeError(
"annotate function requires __code__ attribute",
name="__code__",
obj=annotate
)
Copy link
Member

Choose a reason for hiding this comment

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

I think we can just propagate the AttributeError without this wrapping.

annotate, owner, is_class, globals, allow_evaluation=True
annotate, owner, is_class, globals, annotate_globals, allow_evaluation=True
)
try:
Copy link
Member

Choose a reason for hiding this comment

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

Ditto

)
closure, cell_dict = _build_closure(
annotate, owner, is_class, globals, allow_evaluation=False
annotate, owner, is_class, globals, annotate_globals, allow_evaluation=False
Copy link
Member

@picnixz picnixz Dec 6, 2025

Choose a reason for hiding this comment

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

Wrap this under 80 chars (I think we can split after globals)

Comment on lines 511 to +513
Copy link
Member

Choose a reason for hiding this comment

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

Only use 2 blank lines to separate sections.

This can then be called with:

.. code-block:: python
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.. code-block:: python
.. code-block:: pycon

Comment on lines +577 to +578
{'x': 'MyType'}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
{'x': 'MyType'}
{'x': 'MyType'}


.. code-block:: python
class Annotate:
Copy link
Member

Choose a reason for hiding this comment

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

Indent the entire code block by 3 spaces (and then 4 spaces)

Comment on lines +548 to +558
@property
def __defaults__(self):
return (None,)
@property
def __kwdefaults__(self):
return {"_self": self}
@property
def __code__(self):
return self.__call__.__code__
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
@property
def __defaults__(self):
return (None,)
@property
def __kwdefaults__(self):
return {"_self": self}
@property
def __code__(self):
return self.__call__.__code__
__defaults__ = (None,)
__kwdefaults__ = property(lambda self: dict(_self=self))
__code__ = property(lambda self: self.__call__.__code__)

class Annotate:
called_formats = []
def __call__(self, format=None, *, _self=None):
Copy link
Member

Choose a reason for hiding this comment

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

Is this the signature we want to force?


.. code-block:: python
class Annotate:
Copy link
Member

Choose a reason for hiding this comment

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

I would rather have a protocol being explained, e.g.:

The annotate protocol is an object with the following proeprties:

- A callable ``__call__`` attribute with the following signature: ...
- Properties `__defaults__`, `__kwdefaults__` and `__code__` such that ...

It's not clear what is required and what's not and what we can put inside. And then you can have an example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants