Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a proper dynamicscope iterator #571

Merged
merged 1 commit into from
Jan 31, 2025

Conversation

frmdstryr
Copy link
Contributor

It is not very efficient to the use of dir() and a set to track used keys... but makes python 3.12+ provide useful messages for name errors and attribute errors.

I'd like to add more tests after any initial feedback.

Eg:

enamldef Main(Window): window:
    Container:
        PushButton:
            text = "Click me!"
            clicked :: print(windo.size())

Gives NameError: name 'windo' is not defined. Did you mean: 'window'?

From looking at the traceback source code it ignores giving suggestions if there are > 750 items (https://github.com/python/cpython/blob/main/Lib/traceback.py#L1465). Which I can see it easily exceeding.. So it might be good to also skip globals and builtins?

Copy link

codecov bot commented Jan 29, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 70.96%. Comparing base (e00ca76) to head (07fedc2).
Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #571      +/-   ##
==========================================
+ Coverage   70.92%   70.96%   +0.03%     
==========================================
  Files         287      288       +1     
  Lines       25698    25733      +35     
  Branches     3641     3645       +4     
==========================================
+ Hits        18226    18261      +35     
  Misses       6384     6384              
  Partials     1088     1088              

@MatthieuDartiailh
Copy link
Member

Do we agree this supersedes #566 ?

@frmdstryr
Copy link
Contributor Author

Yes, should I close that or push this on top of that branch?

@MatthieuDartiailh
Copy link
Member

You can close it, I just wanted to be sure.

@sccolbert
Copy link
Member

I think most of this should be done in a Python subclass of DynamicScope? It doesn't need to be fast, just correct. It's much easier to do that from Python. For example:

from itertools import chain, repeat
from _dynamicscope import _DynamicScope


"""
_DynamicScope is a C++ class which exposes the following attributes:

_f_writes
_self_str
_change_str
_f_locals
_f_globals
_f_builtins

"""

class DynamicScope(_DynamicScope):

	def __iter__(self):
		fwrites_it = iter(self._f_writes)
		self_it = repeat(self._self_str, 1)
		change_it = repeat(self._change_str, 1)
		flocals_it = iter(self._f_locals)
		fglobals_it = iter(self._f_globals)
		fbuiltins_it = iter(self._f_builtins)
		parent_it = iter(self._parent)

		return chain(
			fwrites_it,
			self_it,
			change_it,
			flocals_it,
			fglobals_it,
			fbuiltins_it,
			parent_it
		)

	def keys(self):
		return iter(self)

	def values(self):
		return (self[key] for key in self)

	def items(self):
		return ((key, self[key]) for key in self)

	def update(self, items):
		for (key, value) in items:
			self[key] = value

@frmdstryr
Copy link
Contributor Author

Is it ok to expose the internal fields as properties? I guess it'd be fine with only getters.

@sccolbert
Copy link
Member

sccolbert commented Jan 30, 2025 via email

enaml/core/dynamicscope.py Outdated Show resolved Hide resolved
fbuiltins_it = iter(self._f_builtins)
fields = set()
parent = self._owner
while parent is not None:
Copy link
Member

Choose a reason for hiding this comment

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

Why traverse the parent tree here ahead-of-time instead of allowing chain to naturally recurse into the parent iterator as needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I follow... the owner is a Declarative, the parent is another Declarative neither are iterable. It could use traverse_ancestors?

Since the traceback module uses list(frame.f_locals), if it doesn't remove duplicates it will likely quickly overflow the 750 item limit and not provide a suggestion at all...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or is there a way to get the parent dynamic scope? I don't see it anywhere from a quick look.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, yeah, I had it in my head that parent was another DynamicScope object. That said, I think we still put all of that logic into a generator function, instead of computing ahead-of-time.

Copy link
Member

Choose a reason for hiding this comment

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

But yeah, traverse_ancestors could part of a generator function that aggregates the keys while only yielding newly-seen keys.

Copy link
Member

Choose a reason for hiding this comment

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

For the 750 item limit, that really should be an issued raised with the core traceback module. Instead of 100% punting at 751 names, they should truncate their search space to the first 750 items, and just ignore the rest.

def items(self):
return ((key, self[key]) for key in self)

def update(self, state):
Copy link
Member

Choose a reason for hiding this comment

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

Do we need an update method? All the rest of the methods are readonly. I know the C++ class implements __setitem__, but IIRC that's in order to handle local assignments. I'm not sure we really need this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically no... I added it to support ipython embedding since it attempts to update the scope when exiting an embedded console.

fglobals_it,
fbuiltins_it,
fields_it,
),
Copy link
Member

Choose a reason for hiding this comment

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

trailing comma?

result: bool
Whether the key should be included.
"""
if key.startswith("__") or key in used:
Copy link
Member

Choose a reason for hiding this comment

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

Should filter all keys with a single leading _ instead of just double? i.e. only yield the "public" keys.

@sccolbert sccolbert merged commit b7d662c into nucleic:main Jan 31, 2025
21 of 23 checks passed
@frmdstryr frmdstryr deleted the dynamicscope-iter branch February 1, 2025 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants