Defined a `platypush.backend.http.ws` package with all the routes, a
base `WSRoute` class that all the websocket routes can extend, and a
logic in the HTTP backend to automatically scan the package to register
exposed websocket routes.
It was just too painful to find a combination of versions of gunicorn,
gevent, eventlet, pyuwsgi etc. that could work on all of my systems.
On the other hand, Tornado works out of the box with no headaches.
Also in this commit:
- Updated a bunch of outdated/required integration dependencies.
- Black'd and LINTed a couple of old plugins.
The eventlet API has way too many dependency issues with gunicorn.
Still TODO: Fix or at least mitigate the WSGI workers timeout issue when
they handle websocket connections.
The websocket service is no longer provided by a different service,
controlled by a different thread running on another port.
Instead, it's now exposed directly over Flask routes, using
WSGI+eventlet+simple_websocket.
Also, the SSL context options have been removed from `backend.http`, for
sake of simplicity. If you want to enable SSL, you can serve Platypush
through a reverse proxy like nginx.
Instead of iterating over each of the entities in a grouping to find out
which groups should be displayed based on the selector's policy, the
selector can directly keep its `selectedGroups` attribute in sync with
the index.
`delete` will actually remove the record from the database (same as
`unset`'s new behaviour), while `unset` will set it to null without
deleting it (same as the `unset`'s previous behaviour).
Added `waitress` dependency. For performance and security reasons, it's
better to always run the Flask application inside of a uWSGI server.
`waitress` also makes things easier by avoiding to ask the user to
manually provide the external executable arguments, as it was the case
with `uwsgi` and `gunicorn`.
It was broken by the previous refactor of the entities panel, which no
longer triggers the `watch` callback on the upstream `entityGroups`.
The new approach listens for entity updates on the frontend bus and
dynamically creates the entity groupings in `selectedGroups` if they are
missing.
Unlike the other entity groupings, which are 4-layered (`grouping ->
group -> entity_id -> entity`), the grouping by ID only needs 3 layers
(`grouping -> entity_id -> entity`).
- Don't recalculate entity groups every time. Instead, keep them in sync
every time an entity is added or removed.
- Removed `computedChildren` from the entity component - no null nodes
are guaranteed to be passed now, so there's no need for another
iteration on the list of children.
- `childrenByParentId` now only looks in the scope of the entity's
children instead of searching all the entities.
The animation has a big impact on page loading performance when the
system includes a high number of entities that all need their loading
animation to be render.
Multiple style improvements for the entity components. Among these:
- A more consistent style for entity values and toggler buttons.
- Fixed overflowing/underflowing entities on smaller/larger screen
sizes.
- Simplified the stylesheets for many entities as many component classes
have now been moved to `common.scss`.
The entity name and value in the component header may be arbitrarily
long and rendered on small screens.
We therefore need to ensure that the text won't overflow the screen
width.
`entities.transform_entities` will pass back an empty list instead of an
empty dict if no entities were found, and the function should be able to
handle it.
Added Alembic environment and `run_db_migrations` logic to the entities
engine so database schema changes can be processed as soon as the
application is started.
- Support for nested attributes on event hook conditions. Things like
these are now possible:
```
from platypush.event.hook import hook
from platypush.message.event.entities import EntityUpdateEvent
@hook(EntityUpdateEvent, entity={"external_id": "system:cpu"})
def on_cpu_update_event(event: EntityUpdateEvent, **_):
print(event.args["entity"]["percent"])
```
- The scoring/regex extraction/partial string match logic in
`_matches_argument` is actually only needed for
`SpeechRecognizedEvent`. Other events don't need these features, and
event hooks may be actually triggered unexpectedly in case of partial
matches. Therefore, the "complex" `_matches_argument` has been moved
as an override only for `SpeechRecognizedEvent`, and all the other
events will perform simple key-value matching.
SQLAlchemy should automatically begin a transaction on
connection/session creation. Plus, `.begin()` messes up things with
SQLAlchemy 2, which has `autobegin` enabled with no easy way of
disabling it.
`Mapped` has been introduced only in SQLAlchemy 1.4, while Debian stable
still ships 1.3.
Removing the type annotation doesn't come with a big cost, but it keeps
Platypush compatible with Debian stable.
- `percent_field` should be declared on `platypush.schemas.dataclasses`
level, since it's not specific to the `system` plugin.
- Added a common `SystemBaseSchema` that takes care of calling
`_asdict()` if the object is passed as a `psutil` object instead of a
dict.
Plus, `platypush.schemas.system` has now been split into multiple
submodules to avoid a single-file mega-module with all the system
schemas definitions.
Also, there is now a single `Cpu` entity being exported, with a nested
hierarchy structured like:
```
cpu
-> cpu_info
-> cpu_times
-> idle
-> user
-> system
-> ...
-> cpu_load
-> ...
```
There are probably more optimal ways of achieving this other than
passing a reference to the full list of entities to each of the
entities, such as running a BFS to recursively expand all the entities
within the child hierarchy of an entity.
This is needed because the entity needs to know which entities aren't
direct children, but are two or more layers down in the hierarchy, so
they should be passed to their own child entities.
Remove `backend.sensor.distance` and `gpio.sensor.distance`. They are
now replaced by the `sensor.hcsr04` integration, which is compatible
with the new `SensorPlugin` API.