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

24.01.0 #432

Merged
merged 15 commits into from
Jan 8, 2024
2 changes: 2 additions & 0 deletions .github/workflows/publish-dockerhub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
jobs:
buildx:
runs-on: ubuntu-latest
environment: release

steps:
- uses: actions/checkout@v2
with:
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ jobs:
build-n-publish:
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
runs-on: ubuntu-latest
environment: release
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write

steps:
- uses: actions/checkout@v3
with:
ref: master

- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
Expand All @@ -27,7 +32,4 @@ jobs:
python setup.py sdist bdist_wheel

- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_api_key }}
uses: pypa/gh-action-pypi-publish@release/v1
14 changes: 12 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ Installation
After the installation take a look how to configure HABApp.
A default configuration will be created on the first start.

Upgrading
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Upgrade to the latest version
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#. Stop HABApp

#. Activate the virtual environment
Expand All @@ -87,6 +87,16 @@ Upgrading

#. Observe the logs for errors in case there were changes

Installation of a certain version
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Installing a certain version of HABApp requires the same steps used for installation or upgrading HABApp.
However the final ``python3 -m pip install`` command is now different and contains the version number::

python3 -m pip install HABApp==23.12.0

The complete list of available versions can be found on `pypi <https://pypi.org/project/HABApp/#history>`_.

Autostart after reboot
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Check where habapp is installed::
Expand Down
17 changes: 14 additions & 3 deletions docs/logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ but the format should be pretty straight forward.
| That way even if the HABApp configuration is invalid HABApp can still log the errors that have occurred.
| e.g.: ``/HABApp/logs/habapp.log`` or ``c:\HABApp\logs\habapp.log``

Provided loggers
======================================

The ``HABApp.config.logging`` module provides additional loggers which can be used


.. autoclass:: HABApp.config.logging.MidnightRotatingFileHandler

.. autoclass:: HABApp.config.logging.CompressedMidnightRotatingFileHandler


Example
======================================

Expand All @@ -42,7 +53,7 @@ to the file configuration under ``handlers`` in the ``logging.yml``.
...

MyRuleHandler: # <-- This is the name of the handler
class: HABApp.core.lib.handler.MidnightRotatingFileHandler
class: HABApp.config.logging.MidnightRotatingFileHandler
filename: 'c:\HABApp\Logs\MyRule.log'
maxBytes: 10_000_000
backupCount: 3
Expand Down Expand Up @@ -84,7 +95,7 @@ Full Example configuration
# -----------------------------------------------------------------------------------
handlers:
HABApp_default:
class: HABApp.core.lib.handler.MidnightRotatingFileHandler
class: HABApp.config.logging.MidnightRotatingFileHandler
filename: 'HABApp.log'
maxBytes: 10_000_000
backupCount: 3
Expand All @@ -93,7 +104,7 @@ Full Example configuration
level: DEBUG

MyRuleHandler:
class: HABApp.core.lib.handler.MidnightRotatingFileHandler
class: HABApp.config.logging.MidnightRotatingFileHandler
filename: 'c:\HABApp\Logs\MyRule.log' # absolute filename is recommended
maxBytes: 10_000_000
backupCount: 3
Expand Down
6 changes: 3 additions & 3 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Packages required to build the documentation
sphinx == 7.2.5
sphinx-autodoc-typehints == 1.24.0
sphinx_rtd_theme == 1.3.0
sphinx == 7.2.6
sphinx-autodoc-typehints == 1.25.2
sphinx_rtd_theme == 2.0.0
sphinx-exec-code == 0.10
autodoc_pydantic == 2.0.1
sphinx-copybutton == 0.5.2
134 changes: 134 additions & 0 deletions docs/util.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,140 @@ Converts a hsb value to the rgb color space
.. autofunction:: HABApp.util.functions.hsb_to_rgb


Rate limiter
------------------------------
A simple rate limiter implementation which can be used in rules.
The limiter is not rule bound so the same limiter can be used in multiples files.
It also works as expected across rule reloads.


Defining limits
^^^^^^^^^^^^^^^^^^
Limits can either be explicitly added or through a textual description.
If the limit does already exist it will not be added again.
It's possible to explicitly create the limits or through some small textual description with the following syntax:

.. code-block:: text

[count] [per|in|/] [count (optional)] [s|sec|second|m|min|minute|hour|h|day|month|year] [s (optional)]

Whitespaces are ignored and can be added as desired

Examples:

* ``5 per minute``
* ``20 in 15 mins``
* ``300 / hour``


Fixed window elastic expiry algorithm
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This algorithm implements a fixed window with elastic expiry.
That means if the limit is hit the interval time will be increased by the expiry time.

For example ``3 per minute``:

* First hit comes ``00:00:00``. Two more hits at ``00:00:59``.
All three pass, intervall goes from ``00:00:00`` - ``00:01:00``.
Another hit comes at ``00:01:01`` an passes. The intervall now goes from ``00:01:01`` - ``00:02:01``.

* First hit comes ``00:00:00``. Two more hits at ``00:00:30``. All three pass.
Another hit comes at ``00:00:45``, which gets rejected and the intervall now goes from ``00:00:00`` - ``00:01:45``.
A rejected hit makes the interval time longer by expiry time. If another hit comes at ``00:01:30`` it
will also get rejected and the intervall now goes from ``00:00:00`` - ``00:02:30``.


Leaky bucket algorithm
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The leaky bucket algorithm is based on the analogy of a bucket that leaks at a constant rate.
As long as the bucket is not full the hits will pass. If the bucket overflows the hits will get rejected.
Since the bucket leaks at a constant rate it will gradually get empty again thus allowing hits to pass again.


Example
^^^^^^^^^^^^^^^^^^

.. exec_code::

from HABApp.util import RateLimiter

# Create or get existing, name is case insensitive
limiter = RateLimiter('MyRateLimiterName')

# define limits, duplicate limits of the same algorithm will only be added once
# These lines all define the same limit so it'll result in only one limiter added
limiter.add_limit(5, 60) # add limits explicitly
limiter.parse_limits('5 per minute').parse_limits('5 in 60s', '5/60seconds') # add limits through text

# add additional limit with leaky bucket algorithm
limiter.add_limit(10, 100, algorithm='leaky_bucket')

# add additional limit with fixed window elastic expiry algorithm
limiter.add_limit(10, 100, algorithm='fixed_window_elastic_expiry')

# Test the limit without increasing the hits
for _ in range(100):
assert limiter.test_allow()

# the limiter will allow 5 calls ...
for _ in range(5):
assert limiter.allow()

# and reject the 6th
assert not limiter.allow()

# It's possible to get statistics about the limiter and the corresponding windows
print(limiter.info())

# There is a counter that keeps track of the total skips that can be reset
print('Counter:')
print(limiter.total_skips)
limiter.reset() # Can be reset
print(limiter.total_skips)


Recommendation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Limiting external requests to an external API works well with the leaky bucket algorithm (maybe with some initial hits).
For limiting notifications the best results can be achieved by combining both algorithms.
Fixed window elastic expiry will notify but block until an issue is resolved,
that's why it's more suited for small intervals. Leaky bucket will allow hits even while the issue persists,
that's why it's more suited for larger intervals.

.. exec_code::

from HABApp.util import RateLimiter

limiter = RateLimiter('MyNotifications')
limiter.parse_limits('5 in 1 minute', algorithm='fixed_window_elastic_expiry')
limiter.parse_limits("20 in 1 hour", algorithm='leaky_bucket')


Documentation
^^^^^^^^^^^^^^^^^^
.. autofunction:: HABApp.util.RateLimiter


.. autoclass:: HABApp.util.rate_limiter.limiter.Limiter
:members:
:inherited-members:

.. autoclass:: HABApp.util.rate_limiter.limiter.LimiterInfo
:members:
:inherited-members:

.. autoclass:: HABApp.util.rate_limiter.limiter.FixedWindowElasticExpiryLimitInfo
:members:
:inherited-members:

.. autoclass:: HABApp.util.rate_limiter.limiter.LeakyBucketLimitInfo
:members:
:inherited-members:


Statistics
------------------------------

Expand Down
10 changes: 9 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,15 @@ MyOpenhabRule()
```

# Changelog
#### 23.11.1 (2023-11-23)
#### 24.01.0 (2024-01-08)
- Added HABApp.util.RateLimiter
- Added CompressedMidnightRotatingFileHandler
- Updated dependencies
- Small improvement for RGB and HSB types
- Small improvements for openHAB items
- Added toggle for SwitchItem

#### 23.11.0 (2023-11-23)
- Fix for very small float values (#425)
- Fix for writing to persistence (#424)
- Updated dependencies
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
# -----------------------------------------------------------------------------
# Packages for source formatting
# -----------------------------------------------------------------------------
pre-commit >= 3.5, < 3.6
ruff >= 0.1.6, < 0.2
pre-commit == 3.5.0 # 3.6.0 requires python >= 3.10
ruff == 0.1.11

# -----------------------------------------------------------------------------
# Packages for other developement tasks
Expand Down
12 changes: 6 additions & 6 deletions requirements_setup.txt
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
aiohttp == 3.9.0
pydantic == 2.5.2
msgspec == 0.18.4
pendulum == 2.1.2
aiohttp == 3.9.1
pydantic == 2.5.3
msgspec == 0.18.5
bidict == 0.22.1
watchdog == 3.0.0
ujson == 5.8.0
ujson == 5.9.0
aiomqtt == 1.2.1

immutables == 0.20
eascheduler == 0.1.11
easyconfig == 0.3.1
pendulum == 2.1.2
stack_data == 0.6.3
colorama == 0.4.6

voluptuous == 0.14.1

typing-extensions == 4.8.0
typing-extensions == 4.9.0

aiohttp-sse-client == 0.2.1

Expand Down
4 changes: 2 additions & 2 deletions requirements_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
# Packages to run source tests
# -----------------------------------------------------------------------------
packaging == 23.2
pytest == 7.4.3
pytest-asyncio == 0.21.1
pytest == 7.4.4
pytest-asyncio == 0.23.3
8 changes: 5 additions & 3 deletions run/conf/logging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ handlers:
# There are several Handlers available:
# - logging.handlers.RotatingFileHandler:
# Will rotate when the file reaches a certain size (see python logging documentation for args)
# - HABApp.core.lib.handler.MidnightRotatingFileHandler:
# - HABApp.config.logging.handler.MidnightRotatingFileHandler:
# Will wait until the file reaches a certain size and then will rotate on midnight
# - HABApp.config.logging.handler.CompressedMidnightRotatingFileHandler:
# Same as MidnightRotatingFileHandler but will rotate to a gzipped archive
# - More handlers:
# https://docs.python.org/3/library/logging.handlers.html#rotatingfilehandler

HABApp_default:
class: HABApp.core.lib.handler.MidnightRotatingFileHandler
class: HABApp.config.logging.handler.MidnightRotatingFileHandler
filename: 'HABApp.log'
maxBytes: 1_048_576
backupCount: 3
Expand All @@ -23,7 +25,7 @@ handlers:
level: DEBUG

EventFile:
class: HABApp.core.lib.handler.MidnightRotatingFileHandler
class: HABApp.config.logging.handler.CompressedMidnightRotatingFileHandler
filename: 'events.log'
maxBytes: 1_048_576
backupCount: 3
Expand Down
Loading