You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
What should happen if we start to read without starting the reader? #18
As far as convenience goes, it would be nice to be able to have the reader turn things whenever someone starts to read.
But obviously, there's something really ugly about that.
It's best practice to use context managers, not to manually do a .open() and then a .close().
Here' we'd be encouraging even worse. We'd do the open for the user, and expect them to remember to close?
It seems like it would be setting things up to make nice nooses...
But this convenience is useful as far as easy to use, concern-separated, code.
For example, when using SlabIter, doing something like:
app=SlabIter(
audio_read=AudioStream(...).read,
keyboard_read=KeyboardStream(...).read# further callables that do stuff with the data
)
SlabIter takes care of turning on anything that has an on switch, and turn everything off when it's over (through exceptions or more natural causes).
The problem here is that .read is a method. It doesn't have an on switch.
Callable Stream. Have a Stream instance be a callable that points to .read. Why does this scratch the current tradeoff itch? Well, because a Stream instance is a context manage, so can be turned on and off.
Contextualizer (decorator): We could (could we?) make .read return a context manager. That is, still be (sorta) the same callable method, but have an __enter__ and __exit__ that would forward this functionality to the instance it's bound to. Or even better: We could not do that, but rather have contextualize function that would wrap the read to do that. I said "not do that" to be funny. Really, we would solve the "make a (bound) method into a context manager forwarding the context managing to it's instance", but do the right thing and make it a separate, reusable, function. Then we'd have the choice: Do we make all stream readers have thus-decorated read methods, or do we oblige the user to do that explicitly, if and when needed.
I vote for explicit (the latter).
The SlabIter example looks becomes this:
app=SlabIter(
audio_read=contextualize(AudioStream(...).read),
keyboard_read=contextualize(KeyboardStream(...).read),
# further callables that do stuff with the data
)
Here's some code that implements my wishes above, with tests included.
# ------------------------------------------------------------------------------------------------------------# The general solution objectsfrominspectimportsignature, ParameterfromabcimportabstractstaticmethodfromfunctoolsimportwrapsclassTypeValidationError(TypeError):
"""Raised if an object is not valid"""@abstractstaticmethoddefis_valid(obj) ->bool:
"""Returns True if and only if obj is considered valid"""@classmethoddefvalidate(cls, obj, *args, **kwargs):
ifnotcls.is_valid(obj):
raisecls(*args, **kwargs)
defnull_enter_func(obj):
returnobjdefnull_exit_func(obj, *exception_info):
returnNonedef_has_min_num_of_arguments(func, mininum_n_args):
returnlen(signature(func).parameters) >=mininum_n_argsdef_callable_has_a_variadic_pos(func):
"""True if and only if the function has a variadic positional argument (i.e. *args)"""returnany(x.kind==Parameter.VAR_POSITIONALforxinsignature(func).parameters.values())
classEnterFunctionValidation(TypeValidationError):
"""Raised if an function isn't a valid (context manager) __enter__ function"""@staticmethoddefis_valid(obj):
returncallable(obj) and_has_min_num_of_arguments(obj, 1)
classExitFunctionValidation(TypeValidationError):
"""Raised if an function isn't a valid (context manager) __exit__ function"""@staticmethoddefis_valid(obj):
returncallable(obj) and (_callable_has_a_variadic_posor_has_min_num_of_arguments(obj, 4))
# TODO: If needed: # A more general Contextualizer would proxy/delegate all methods (including special ones) to the wrappedclassContextualizeCall:
def__init__(self, func, enter_func=null_enter_func, exit_func=null_exit_func):
# validationifnotcallable(func):
raiseTypeError(f"First argument should be a callable, was: {func}")
EnterFunctionValidation.validate(
enter_func,
f"Not a valid enter function (should be a callable with at least one arg): {enter_func}"
)
ExitFunctionValidation.validate(
exit_func,
f"Not a valid exit function (should be a callable with at least one arg or varadic args): {exit_func}",
)
# assignmentself.func=funcself.enter_func=enter_funcself.exit_func=exit_func# wrapping self with attributes of func (signature, name, etc.)wraps(func)(self)
def__call__(self, *args, **kwargs):
returnself.func(*args, **kwargs)
def__enter__(self):
# print(f'{type(self).__name__}.__enter__')returnself.enter_func(self.func)
def__exit__(self, *exception_info):
# print(f'{type(self).__name__}.__exit__')returnself.exit_func(self.func, *exception_info)
# ------------------------------------------------------------------------------------------------------------# Using these general objects for the particular case of having bound methods forward their context# management to the instances they're bound tofromfunctoolsimportpartialdefforward_to_instance_enter(obj):
returnobj.__self__.__enter__()
defforward_to_instance_exit(obj, *exception_info):
returnobj.__self__.__exit__(obj, *exception_info)
contextualize_with_instance=partial(
ContextualizeCall,
enter_func=forward_to_instance_enter,
exit_func=forward_to_instance_exit
)
contextualize_with_instance.__doc__= (
"To be applied to a bound method. ""Returns a callable that forwards context enters/exits to the bound instance"
)
# ------------------------------------------------------------------------------------------------------------# Testing this # Test objectsclassStreamHasNotBeenStarted(RuntimeError):
"""Raised when an action requires the stream to be 'on'"""classStreamer:
def__init__(self, iterable):
self.iterable=iterableself.is_running=Falseself._read=Nonedefenter(self):
# print(f'{type(self).__name__}.enter')self._read=iter(self.iterable).__next__self.is_running=Truedefexit(self, *exc):
# print(f'{type(self).__name__}.exit')self._read=Noneself.is_running=False__enter__, __exit__=enter, exitdefread(self):
ifnotself.is_running:
raiseStreamHasNotBeenStarted(
"The stream needs to be on/started (in a context) for that!"
)
returnself._read()
# Testing the test objectss=Streamer('stream')
# demowiths:
asserts.read() =='s'asserts.read() =='t'# a reader test functiondeftest_reader(reader):
assert''.join(reader() for_inrange(6)) =='stream'# Normal case: With a contextreader=s.readwiths:
test_reader(reader)
# Normal case: Manual entering/exitings.enter()
reader=s.readtest_reader(reader)
s.exit()
# But if we don't turn things on...reader=s.readtry:
# oops, forgot the enter s contexttest_reader(reader)
it_worked=TrueexceptStreamHasNotBeenStartedase:
it_worked=Falseassertisinstance(e, StreamHasNotBeenStarted)
asserte.args[0] =='The stream needs to be on/started (in a context) for that!'assertnotit_worked# But we can't turn the read method on -- it's not a context (it's instance is!)reader=s.readtry:
withreader: # can we actually do this (answer: no! We can enter s, not s.read)test_reader(reader)
it_worked=TrueexceptExceptionase:
it_worked=Falseassertisinstance(e, AttributeError)
asserte.args[0] =='__enter__'# well yeah, reader doesn't have an __enter__!assertnotit_worked# But with contextualize_with_instance...reader=contextualize_with_instance(s.read)
try:
withreader: # now we can enter the reader!test_reader(reader)
it_worked=TrueexceptExceptionase:
it_worked=Falseassertit_worked# Hurray!
@thorwhalen I'm a little surprised you'd vote for explicit instead of always doing it in the background since you're an advocate for reduced overhead.
I'm in favor of always decorating because it won't change other use cases and were gravitating toward using SlabIter more.
I now implemented and pushed my solution in stream2py.
Some doctests and tests included.
It's completely independent (neither dependent on nor dependent... under (?) anything in stream2py), so nice and safe.
I'll leave the issue open until you and @SRHerzog vet the solution, and then we can close the issue.
Again, this is where the "problem" arose first, but I depend on nothing in stream2py, nor does stream2py depend on it.
We might find that this util is more widely applicable, and can then move where it makes sense (i2? creek?).
On Monday, let's discuss also removing what ever we did to have those automatic context entering.
Done: StreamBuffer.mk_reader() (stream2py/stream_buffer.py:193) checks isinstance(self.source_buffer, _SourceBuffer) and raises StreamNotStartedError with an actionable message if the buffer hasn't been started. Landed in commit cc0b18c (merged via PR #21) but never closed on GitHub. Closing now.
Activity
thorwhalen commentedon Dec 17, 2021
As far as convenience goes, it would be nice to be able to have the reader turn things whenever someone starts to read.
But obviously, there's something really ugly about that.
It's best practice to use context managers, not to manually do a
.open()and then a.close().Here' we'd be encouraging even worse. We'd do the open for the user, and expect them to remember to close?
It seems like it would be setting things up to make nice nooses...
But this convenience is useful as far as easy to use, concern-separated, code.
For example, when using
SlabIter, doing something like:SlabItertakes care of turning on anything that has an on switch, and turn everything off when it's over (through exceptions or more natural causes).The problem here is that
.readis a method. It doesn't have an on switch.So can we get the best of both worlds?
thorwhalen commentedon Dec 17, 2021
A few solution ideas.
Callable Stream. Have a
Streaminstance be a callable that points to.read. Why does this scratch the current tradeoff itch? Well, because aStreaminstance is a context manage, so can be turned on and off.Contextualizer (decorator): We could (could we?) make
.readreturn a context manager. That is, still be (sorta) the same callable method, but have an__enter__and__exit__that would forward this functionality to the instance it's bound to. Or even better: We could not do that, but rather havecontextualizefunction that would wrap thereadto do that. I said "not do that" to be funny. Really, we would solve the "make a (bound) method into a context manager forwarding the context managing to it's instance", but do the right thing and make it a separate, reusable, function. Then we'd have the choice: Do we make all stream readers have thus-decoratedreadmethods, or do we oblige the user to do that explicitly, if and when needed.I vote for explicit (the latter).
The
SlabIterexample looks becomes this:which is not to bad if you ask me.
thorwhalen commentedon Dec 18, 2021
Here's some code that implements my wishes above, with tests included.
andeaseme commentedon Dec 18, 2021
@thorwhalen I'm a little surprised you'd vote for explicit instead of always doing it in the background since you're an advocate for reduced overhead.
I'm in favor of always decorating because it won't change other use cases and were gravitating toward using SlabIter more.
thorwhalen commentedon Dec 18, 2021
@andeaseme: I'm an advocate for explicit over implicit by default, and as long as it's not in the way of usage too much.
(The illusion of the opposite opinion might come from the fact that have low tolerance for difficult/unclear usage.)
thorwhalen commentedon Dec 18, 2021
I now implemented and pushed my solution in stream2py.
Some doctests and tests included.
It's completely independent (neither dependent on nor dependent... under (?) anything in stream2py), so nice and safe.
I'll leave the issue open until you and @SRHerzog vet the solution, and then we can close the issue.
See the code here: https://github.com/i2mint/stream2py/blob/d5ba8fdd1718ca084831a6b97a9698c111caa10a/stream2py/util.py
And the tests here:
https://github.com/i2mint/stream2py/blob/d5ba8fdd1718ca084831a6b97a9698c111caa10a/stream2py/tests/test_util.py
thorwhalen commentedon Dec 18, 2021
Again, this is where the "problem" arose first, but I depend on nothing in stream2py, nor does stream2py depend on it.
We might find that this util is more widely applicable, and can then move where it makes sense (
i2?creek?).On Monday, let's discuss also removing what ever we did to have those automatic context entering.
thorwhalen commentedon Dec 18, 2021
And @andeaseme , about
I might be for doing it in the background here if it didn't have the downsides mentioned above.
If you have an idea that in at the intersection of the best of both worlds, let us know!
thorwhalen commentedon Dec 18, 2021
If you haven't read the solution yet, know that the core of the solution is simply:
It was the original code -- I just expanded on that the code to make it more production-ready (validation, etc.)
feat: resolve issues #10, #15, #18 and improve test coverage
thorwhalen commentedon Sep 22, 2026
Done:
StreamBuffer.mk_reader()(stream2py/stream_buffer.py:193) checksisinstance(self.source_buffer, _SourceBuffer)and raisesStreamNotStartedErrorwith an actionable message if the buffer hasn't been started. Landed in commit cc0b18c (merged via PR #21) but never closed on GitHub. Closing now.