-
Notifications
You must be signed in to change notification settings - Fork 68
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
Lazy load AudioDevice properties, add additional functions #97
base: develop
Are you sure you want to change the base?
Conversation
…ve redundant code into helper functions, make variable names more consistent, add typing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the refactoring, the main idea is good, the refactoring makes sense and the lazy loading is a nice addition.
The pull request is a bit too big for my taste, I like to break down into smaller pull requests when possible so it's easier to keep focus when reviewing and easier to git bisect it when there's a regression. Also small PRs get less comments and get merged faster.
Thanks for bringing some type hints, I like them too, in the future we would need to lint them to make sure they're not lies, but let's keep it for another PR.
# TODO: the current behavior here isn't really getting "all sessions" | ||
# Leaving behavior as-is for backward compatibility |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks I appreciate the comment
flow=EDataFlow.eAll.value, deviceState=DEVICE_STATE.ACTIVE.value | ||
) -> List[AudioDevice]: | ||
""" | ||
Get devices based on filteres for flow direction and device state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/filteres/filters/
with warnings.catch_warnings(record=True) as w: | ||
AudioUtilities.CreateDevice(dev) | ||
device = AudioUtilities.CreateDevice(dev) | ||
# Properties are lazy-loaded and won't throw exceptions | ||
# until accessed | ||
dev = device.properties | ||
assert len(w) == 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating the tests too.
Maybe we should take the CreateDevice() call out of the context manager and only put the property access into it to prove that it's indeed raising only with this specific line.
So test would be:
AudioUtilities.CreateDevice(dev)
device = AudioUtilities.CreateDevice(dev)
with warnings.catch_warnings(record=True) as w:
# Properties are lazy-loaded and won't throw exceptions
# until accessed
device.properties
assert len(w) == 1
@staticmethod | ||
def getProperty(self, key): | ||
store = self._dev.OpenPropertyStore(STGM.STGM_READ.value) | ||
if store is None: | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this is a static method, maybe let's call that self
obj
instead or something to avoid confusion, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't remember why I added this as a static method -- this should probably just be
@property def property(self, key):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes indeed it's really only consumed by the audio device object so that would be the more logical change
for j in range(propCount): | ||
pk = store.GetAt(j) | ||
|
||
value = AudioDevice.getProperty(self, pk) | ||
if value is None: | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that feels like O(n*n/2) given that getProperty will loop all over again. No big deal at performance level, but just got me curious if there's not a more elegant approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If getProperty gets changed to just property & directly accesses by the name, that would help
|
||
@property | ||
def properties(self) -> dict: | ||
if self._properties is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you familiar with guard clauses?
Basically inverting the condition to exit early and deal with the rest of the code one level of indentation lower:
if self._properties is not None:
return self._properties
store = self._dev.OpenPropertyStore(STGM.STGM_READ.value)
...
And then do not else, but rest of the code.
That saves one indentation level and the brain doesn't have to stack level of logic when tracking the code.
This is relatively simple code of course, but it's a good practice to generalize to make things easier to follow
def DisplayName(self, value: str) -> str: | ||
s = self._ctl.GetDisplayName() | ||
if s != value: | ||
self._ctl.SetDisplayName(value, IID_Empty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments (and typehints) are future lies unless tested.
Are we sure this method returns a string?
Agreed that this is definitely a lot -- I started working on a small project locally and started doing some late-night tinkering and just figured I'd upload what I had done so far. IMO, part of the issue is that there's too much in utils right now. Changes might be easier to keep narrower in scope if it were split up something like: audio\device.py
audio\session.py
audio\controllers.py
audio\types.py
utils.py
|
Yes you're right that this should be split like you said. |
Made a few tweaks:
Note that in every case where I could I kept the behavior the same.
Changed behavior from the current release:
Getting session by process id. This now looks at all sessions, regardless of which device they're associated with.
Not changed, but probably should change:
"GetAllSessions" -- it doesn't actually return "all" sessions. It just returns sessions associated with the default playback device.
A few of the areas where that might not be all: