Releases: frequenz-floss/frequenz-channels-python
v0.16.0
Frequenz Channels Release Notes
Summary
The minimum Python supported version was bumped to 3.11 and the Select class replaced by the new select() function.
Upgrading
-
The minimum supported Python version was bumped to 3.11, downstream projects will need to upgrade too to use this version.
-
The
Selectclass was replaced by a newselect()function, with the following improvements:- Type-safe: proper type hinting by using the new helper type guard
selected_from(). - Fixes potential starvation issues.
- Simplifies the interface by providing values one-by-one.
- Guarantees there are no dangling tasks left behind when used as an async context manager.
This new function is an async iterator, and makes sure no dangling tasks are left behind after a select loop is done.
Example:
timer1 = Timer.periodic(datetime.timedelta(seconds=1)) timer2 = Timer.timeout(datetime.timedelta(seconds=0.5)) async for selected in select(timer1, timer2): if selected_from(selected, timer1): # Beware: `selected.value` might raise an exception, you can always # check for exceptions with `selected.exception` first or use # a try-except block. You can also quickly check if the receiver was # stopped and let any other unexpected exceptions bubble up. if selected.was_stopped(): print("timer1 was stopped") continue print(f"timer1: now={datetime.datetime.now()} drift={selected.value}") timer2.stop() elif selected_from(selected, timer2): # Explicitly handling of exceptions match selected.exception: case ReceiverStoppedError(): print("timer2 was stopped") case Exception() as exception: print(f"timer2: exception={exception}") case None: # All good, no exception, we can use `selected.value` safely print( f"timer2: now={datetime.datetime.now()} " f"drift={selected.value}" ) case _ as unhanded: assert_never(unhanded) else: # This is not necessary, as select() will check for exhaustiveness, but # it is good practice to have it in case you forgot to handle a new # receiver added to `select()` at a later point in time. assert False
- Type-safe: proper type hinting by using the new helper type guard
New Features
-
A new
select()function was added, please look at the Upgrading section for details. -
A new
Eventutility receiver was added.This receiver can be made ready manually. It is mainly useful for testing but can also become handy in scenarios where a simple, on-off signal needs to be sent to a select loop for example.
Example:
import asyncio from frequenz.channels import Receiver from frequenz.channels.util import Event, select, selected_from other_receiver: Receiver[int] = ... exit_event = Event() async def exit_after_10_seconds() -> None: asyncio.sleep(10) exit_event.set() asyncio.ensure_future(exit_after_10_seconds()) async for selected in select(exit_event, other_receiver): if selected_from(selected, exit_event): break if selected_from(selected, other_receiver): print(selected.value) else: assert False, "Unknow receiver selected"
-
The
Timerclass now has more descriptive__str__and__repr__methods.
What's Changed
- Clear release notes by @leandro-lucarella-frequenz in #106
- Enable merge queue by @leandro-lucarella-frequenz in #108
- Bump actions/labeler from 4.0.3 to 4.0.4 by @dependabot in #110
- Add possible values to timer missed_tick_policy parameter documentation by @Marenz in #109
- Bump mkdocstrings[python] from 0.21.2 to 0.22.0 by @dependabot in #111
- Bump mkdocs-material from 9.1.14 to 9.1.15 by @dependabot in #112
- Bump the minimum Python supported version to 3.11 by @leandro-lucarella-frequenz in #113
- Bump actions/labeler from 4.0.4 to 4.1.0 by @dependabot in #115
- Bump mkdocs-material from 9.1.15 to 9.1.16 by @dependabot in #117
- Bump mkdocs-material from 9.1.16 to 9.1.17 by @dependabot in #118
- Bump actions/labeler from 4.1.0 to 4.2.0 by @dependabot in #119
- Replace
Selectwithselect(), a type-safe async iterator by @leandro-lucarella-frequenz in #114 - Fix broken cross-reference in docs by @llucax in #120
- Fix typo in release notes example by @llucax in #121
- Run CI for tags too by @llucax in #122
New Contributors
Full Changelog: v0.15.1...v0.16.0
v0.15.1
Frequenz Channels Release Notes
Summary
This is a bugfix release that mainly uses more up to date dependencies and extend the range of supported dependencies. There is technically one breaking change though, but this is hardly used by anyone.
Upgrading
FileWatcherno longer accepts or setsNoneas theevent_typesargument. Instead, all available event types are now set by default while still providing the flexibility to customize the event types as needed.
Bug Fixes
- Many documentation examples were fixed.
What's Changed
- Clear release notes by @leandro-lucarella-frequenz in #90
- Add dependabot config and pin dependencies by @leandro-lucarella-frequenz in #92
- Bump mkdocs-section-index from 0.3.4 to 0.3.5 by @dependabot in #98
- Bump frequenz-floss/setup-git-user from 1 to 2 by @dependabot in #100
- Bump mkdocs-literate-nav from 0.4.0 to 0.6.0 by @dependabot in #94
- Merge various dependency updates by @leandro-lucarella-frequenz in #102
- Bump mkdocs-material from 8.5.7 to 9.1.12 by @dependabot in #97
- Bump mkdocs-material from 9.1.12 to 9.1.13 by @dependabot in #103
- Update event_types parameter in FileWatcher by @daniel-zullo-frequenz in #89
- Bump mkdocs-material from 9.1.13 to 9.1.14 by @dependabot in #104
- Validate docstring examples by @Marenz in #101
- Update release notes for v0.15.1 by @leandro-lucarella-frequenz in #105
New Contributors
- @dependabot made their first contribution in #98
- @Marenz made their first contribution in #101
Full Changelog: v0.15.0...v0.15.1
v0.15.0
Frequenz Channels Release Notes
Summary
This release adds support to pass None values via channels and revamps the Timer class to support custom policies for handling missed ticks and use the loop monotonic clock. There is also a fix for the FileWatcher which includes a change in behavior when reporting changes for deleted files.
Upgrading
-
util.Timerwas replaced by a more generic implementation that allows for customizable policies to handle missed ticks.If you were using
Timerto implement timeouts, these two pieces of code should be almost equivalent:-
Old:
old_timer = Timer(1.0) triggered_datetime = old_timer.receive()
-
New:
new_timer = Timer.timeout(timedelta(seconds=1.0)) drift = new_timer.receive() triggered_datetime = datetime.now(timezone.utc) - drift
They are not exactly the same because the
triggered_datetimein the second case will not be exactly when the timer had triggered, but that shouldn't be relevant, the important part is when your code can actually react to the timer trigger and to know how much drift there was to be able to take corrective actions.Also the new
Timeruses theasyncioloop monotonic clock and the old one used the wall clock (datetime.now()) to track time. This means that when usingasync-solipsismto test, the newTimerwill always trigger immediately regardless of the state of the wall clock. This also means that we don't need to mock the wall clock withtime-machineeither now.With the previous timer one needed to create a separate task to run the timer, because otherwise it would block as it loops until the wall clock was at a specific time. Now the code will run like this:
timer = Timer.timeout(timedelta(seconds=1.0)) asyncio.sleep(0.5) # Advances the loop monotonic clock by 0.5 seconds immediately await drift = timer.receive() # Advances the clock by 0.5 immediately too assert drift == approx(timedelta(0)) # Because it could trigger exactly at the tick time # Simulates a delay in the timer trigger time asyncio.sleep(1.5) # Advances the loop monotonic clock by 1.5 seconds immediately await drift = timer.receive() # The timer should have triggered 0.5 seconds ago, so it doesn't even sleep assert drift == approx(timedelta(seconds=0.5)) # Now we should observe a drift of 0.5 seconds
Note: Before replacing this code blindly in all uses of
Timer.timeout(), please consider using the periodic timer constructorTimer.periodic()if you need a timer that triggers reliable on a periodic fashion, as the oldTimer(andTimer.timeout()) accumulates drift, which might not be what you want. -
-
FileWatchernow will emit events even if the file doesn't exist anymore.Because the underlying library has a considerable delay in triggering filesystem events, it can happen that, for example, a
CREATEevent is received but at the time of receiving the file doesn't exist anymore (because if was removed just after creation and before the event was triggered).Before the
FileWatcherwill only emit events when the file exists, but this doesn't work forDELETEevents (clearly). Given the nature of this mechanism can lead to races easily, it is better to leave it to the user to decide when these situations happen and just report all events.Therefore, you should now check a file receiving an event really exist before trying to operate on it.
-
FileWatcherreports the type of event observed in addition to the file path.Previously, only the file path was reported. With this update, users can now determine if a file change is a creation, modification, or deletion.
Note that this change may require updates to your existing code that relies onFileWatcheras the new interface returns aFileWatcher.Eventinstead of just the file path.
New Features
-
util.Timerwas replaced by a more generic implementation that allows for customizable policies to handle missed ticks. -
Passing
Nonevalues via channels is now supported. -
FileWatcher.Eventwas added to notify events when a file is created, modified, or deleted.
Bug Fixes
-
util.Select/util.Merge/util.MergeNamed: Cancel pending tasks in__del__methods only if possible (the loop is not already closed). -
FileWatcherwill now reportDELETEevents correctly.Due to a bug, before this release
DELETEevents were only reported if the file was re-created before the event was triggered.
What's Changed
- Clear release notes by @leandro-lucarella-frequenz in #81
- Publish to PyPi using trusted publishing by @leandro-lucarella-frequenz in #84
- Add support/tests for None-value channels by @sahas-subramanian-frequenz in #83
- Cancel pending tasks in
__del__methods only if possible by @sahas-subramanian-frequenz in #85 - Make the timer generic and support periodic timers by @leandro-lucarella-frequenz in #79
- Fix
FileWatcherDELETEevent reporting and rearrange tests by @leandro-lucarella-frequenz in #86 - Report the type of change observed in FileWatcher by @daniel-zullo-frequenz in #87
New Contributors
- @sahas-subramanian-frequenz made their first contribution in #83
- @daniel-zullo-frequenz made their first contribution in #87
Full Changelog: v0.14.0...v0.15.0
v0.14.0
Frequenz Channels Release Notes
Summary
The main change in this release is the revamp of exception handling in general. New exceptions were created and send() now raises an exception too when it fails.
Hopefully they are now used much more uniformly across the whole library.
Upgrading
-
The
Sender.send()method nowraises aSenderErrorinstead of returningFalse. TheSenderErrorwill typically have aChannelClosedErrorand the underlying reason as a chained exception. -
The
Receiver.ready()method (and relatedreceive()and__anext__when used as an async iterator) nowraises aReceiverErrorand in particular aReceiverStoppedErrorwhen the receiver has no more messages to receive.Receiver.consume()doesn't raise any exceptions.Receivers raising
EOFErrornow raiseReceiverInvalidatedErrorinstead. -
For channels which senders raise an error when the channel is closed or which receivers stop receiving when the channel is closed, the
SenderErrorandReceiverStoppedErrorare chained with a__cause__that is aChannelClosedErrorwith the channel that was closed. -
ChannelClosedErrornow requires the argumentchannel(before it was optional). -
Now exceptions are not raised in Receiver.ready() but in Receiver.consume() (receive() or the async iterator
anext).
New Features
-
New exceptions were added:
-
Error: A base exception from which all exceptions from this library inherit. -
SendError: Raised for errors when sending messages. -
ReceiverError: Raised for errors when receiving messages. -
ReceiverClosedError: Raised when a receiver don't have more messages to receive. -
ReceiverInvalidatedError: Raised when a receiver was invalidated (for example it was converted into aPeekable).
-
What's Changed
- Clean release notes by @ela-kotulska-frequenz in #69
- Fix build badge by @leandro-lucarella-frequenz in #71
- Improve and add exceptions by @leandro-lucarella-frequenz in #61
- Make assert msg grammatically correct and less ambiguous by @mathias-baumann-frequenz in #74
- Finish release notes for v0.14.0 by @leandro-lucarella-frequenz in #80
New Contributors
- @mathias-baumann-frequenz made their first contribution in #74
Full Changelog: v0.13.0...v0.14.0
v0.13.0
Frequenz Channels Release Notes
New Features
- Add method to stop
MergeandMergeNamed.
What's Changed
- Clean release notes by @ela-kotulska-frequenz in #67
- Add stop method for MergeNamed and Merge channels by @ela-kotulska-frequenz in #68
Full Changelog: v0.12.0...v0.13.0
v0.12.0
Frequenz Channels Release Notes
Summary
Upgrading
New Features
- Add method to stop Select.
Bug Fixes
- Deactivate Broadcast receivers that were transformed to peekable.
What's Changed
- Clear release notes by @leandro-lucarella-frequenz in #58
- Add method to stop Select by @ela-kotulska-frequenz in #63
- Add a private method to deactivate Broadcast receivers by @shsms in #64
- Update release notes by @ela-kotulska-frequenz in #65
New Contributors
- @ela-kotulska-frequenz made their first contribution in #63
Full Changelog: v0.11.0...v0.12.0
v0.11.0
Frequenz Channels Release Notes
Summary
The project has a new home!
https://frequenz-floss.github.io/frequenz-channels-python/
For now the documentation is pretty scarce but we will be improving it with time.
Upgrading (breaking changes)
-
You need to make sure to use timezone-aware
datetimeobjects when using the timestamp returned byTimer, Otherwise you will get an exception. -
Channels methods
get_receiver()andget_sender()have been renamed tonew_receiver()andnew_sender()respectively. This is to make it more clear that new objects are being created. -
The public API surface has been reduced considerably to make it more clear where to import symbols. You should update your imports. The new symbol locations are:
frequenz.channels.Anycastfrequenz.channels.Broadcastfrequenz.channels.Anycastfrequenz.channels.Bidirectionalfrequenz.channels.Broadcastfrequenz.channels.Peekablefrequenz.channels.Receiverfrequenz.channels.Senderfrequenz.channels.util.Mergefrequenz.channels.util.MergeNamedfrequenz.channels.util.FileWatcherfrequenz.channels.util.Selectfrequenz.channels.util.Timer
-
The class
BufferedReceiverwas removed because the interface was really intended for channel implementations. Users are not supposed to enqueue messages to receiver but just receive from them. If you used it you can implement it yourself. -
The class
BidirectionalHandlewas moved toBidirectional.Handle. -
The class
EventTypewas moved toFileWatcher.EventType.
New Features
- Python 3.11 is now supported!
Bug Fixes
-
Broadcastreceivers now get cleaned up once they go out of scope. -
Timernow returns timezone-awaredatetimeobjects using UTC as timezone.
What's Changed
- Fix project URLs by @leandro-lucarella-frequenz in #18
- ci: Test also Python 3.11 pre-releases by @leandro-lucarella-frequenz in #19
- Remove
version:xxxand addpart:selectandpart:receiverslabels by @leandro-lucarella-frequenz in #28 - Use MkDocs to build the documentation by @leandro-lucarella-frequenz in #25
- ci: Add contents permission to publish-docs by @leandro-lucarella-frequenz in #33
- ci: Update aliases when deploying with mike by @leandro-lucarella-frequenz in #38
- Add PyPI and Docs badges to README by @shsms in #34
- ci: Use the final Python 3.11 version by @leandro-lucarella-frequenz in #39
- docs: Replace the Home with the README by @leandro-lucarella-frequenz in #40
- Fix CONTRIBUTING headings by @leandro-lucarella-frequenz in #41
- Use "aware" datetimes by @leandro-lucarella-frequenz in #48
- Ensure deleted receivers get cleaned up by @shsms in #45
- Avoid dropping of messages after breaking from
Selectblocks by @shsms in #42 - Rename
get_{sender,receiver}tonew_{sender,receiver}by @leandro-lucarella-frequenz in #49 - Clean up public API by @leandro-lucarella-frequenz in #55
- FileWatcher: Don't type-check
changesby @leandro-lucarella-frequenz in #56 - Add Python 3.11 support to RELEASE_NOTES by @leandro-lucarella-frequenz in #57
Full Changelog: v0.10.0...v0.11.0
v0.10.1
frequenz-channels Release Notes
Summary
This is the first public open source release based on the internal SDK version v0.9.0. There are no breaking changes in this release, only changes to the project structure, metadata, and automation. Packages are also now uploaded to PyPI as frequenz-channels, so this project now can be installed normally via pip:
python -m pip install frequenz-channelsThe GitHub issues were also improved, adding templates for reporting issues and requesting features. Users are also pointed to the Discussion forums when trying to open an issue if they have questions instead. Also many labels are assigned automatically on issue and pull request creation.
What's Changed
- Backport documentation generation to v0.10.x by @leandro-lucarella-frequenz in #35
Full Changelog: v0.10.0...v0.10.1
v0.10.0
frequenz-channels Release Notes
Summary
This is the first public open source release based on the internal SDK version v0.9.0. There are no breaking changes in this release, only changes to the project structure, metadata, and automation. Packages are also now uploaded to PyPI as frequenz-channels, so this project now can be installed normally via pip:
python -m pip install frequenz-channelsThe GitHub issues were also improved, adding templates for reporting issues and requesting features. Users are also pointed to the Discussion forums when trying to open an issue if they have questions instead. Also many labels are assigned automatically on issue and pull request creation.
What's Changed
- Add CODEOWNERS file by @leandro-lucarella-frequenz in #1
- Add issue templates and configure KeywordLabeler app by @leandro-lucarella-frequenz in #2
- Import channel implementations from Python SDK v0.9.0 by @shsms in #3
- ci: Add automatic pull request labeler by @leandro-lucarella-frequenz in #15
New Contributors
- @leandro-lucarella-frequenz made their first contribution in #1
- @shsms made their first contribution in #3
Full Changelog: https://github.com/frequenz-floss/frequenz-channels-python/commits/v0.10.0
v0.10.0-rc0
frequenz-channels Release Notes
Summary
This is the first public open source release based on the internal SDK version v0.9.0. There are no breaking changes in this release, only changes to the project structure, metadata, and automation. Packages are also now uploaded to PyPI as frequenz-channels, so this project now can be installed normally via pip:
python -m pip install frequenz-channelsThe GitHub issues were also improved, adding templates for reporting issues and requesting features. Users are also pointed to the Discussion forums when trying to open an issue if they have questions instead. Also many labels are assigned automatically on issue and pull request creation.
What's Changed
- Add CODEOWNERS file by @leandro-lucarella-frequenz in #1
- Add issue templates and configure KeywordLabeler app by @leandro-lucarella-frequenz in #2
- Import channel implementations from Python SDK v0.9.0 by @shsms in #3
- ci: Add automatic pull request labeler by @leandro-lucarella-frequenz in #15
New Contributors
- @leandro-lucarella-frequenz made their first contribution in #1
- @shsms made their first contribution in #3
Full Changelog: https://github.com/frequenz-floss/frequenz-channels-python/commits/v0.10.0-rc0