Lazy load AudioDevice properties, add additional functions#97
Lazy load AudioDevice properties, add additional functions#97timtucker wants to merge 3 commits intoAndreMiras:developfrom
Conversation
…ve redundant code into helper functions, make variable names more consistent, add typing
AndreMiras
left a comment
There was a problem hiding this comment.
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.
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. |
| 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.
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.
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.
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.
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.
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.
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.
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.
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: