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

Refactor for initial public release #5

Merged
merged 25 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
[MAIN]
# Specify a score threshold to be exceeded before program exits with error.
fail-under=9.5

# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
logging-fstring-interpolation,
logging-not-lazy,
unspecified-encoding,
consider-using-from-import,
consider-using-with,
invalid-name

[FORMAT]

# Maximum number of characters on a single line.
Expand All @@ -34,8 +19,8 @@ disable=
too-many-instance-attributes,
global-statement,
too-many-arguments,
unused-argument,
too-few-public-methods
too-few-public-methods,
fixme

[STRING]

Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# UC Integration API Python wrapper Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

_Changes in the next release_

### Added
- Type information
- Simple example and initial developer documentation

### Fixed
- mDNS service publishing announces local hostname.
- ENV var handling: `UC_INTEGRATION_INTERFACE` and `UC_INTEGRATION_HTTP_PORT` are optional (#2, #3)
- config_dir_path is always set

### Changed
- driver setup process
- entity command handler
- don't expose AsyncIOEventEmitter for event callbacks
- invalid names in public classes
- logging configuration, configuration must be done in client code


---
72 changes: 72 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Contributing

First off, thanks for taking the time to contribute!

Found a bug, typo, missing feature or a description that doesn't make sense or needs clarification?
Great, please let us know!

### Bug Reports :bug:

If you find a bug, please search for it first in the [Issues](https://github.com/unfoldedcircle/integration-python-library/issues),
and if it isn't already tracked, [create a new issue](https://github.com/unfoldedcircle/integration-python-library/issues/new).

### New Features :bulb:

If you'd like to see or add new functionality to the library, describe the problem you want to solve in a
[new Issue](https://github.com/unfoldedcircle/integration-python-library/issues/new).

### Pull Requests

**Any pull request needs to be reviewed and approved by the Unfolded Circle development team.**

We love contributions from everyone.

⚠️ If you plan to make substantial changes, we kindly ask you, that you please reach out to us first.
Either by opening a feature request describing your proposed changes before submitting code, or by contacting us on
one of the other [feedback channels](#feedback-speech_balloon).

Since this library is being used in integration drivers running on the embedded Remote Two device,
we have to make sure it remains compatible with the embedded runtime environment and runs smoothly.

Submitting pull requests for typos, formatting issues etc. are happily accepted and usually approved relatively quick.

With that out of the way, here's the process of creating a pull request and making sure it passes the automated tests:

### Contributing Code :bulb:

1. Fork the repo.

2. Make your changes or enhancements (preferably on a feature-branch).

Contributed code must be licensed under the [Mozilla Public License 2.0](https://choosealicense.com/licenses/mpl-2.0/),
or a compatible license, if existing parts of other projects are reused (e.g. MIT licensed code).
It is required to add a boilerplate copyright notice to the top of each file:

```
"""
{fileheader}

:copyright: (c) {year} {person OR org} <{email}>
:license: MPL-2.0, see LICENSE for more details.
"""
```

3. Make sure your changes follow the project's code style and the lints pass described in [Code Style](docs/code_guidelines.md).

4. Push to your fork.

5. Submit a pull request.

At this point we will review the PR and give constructive feedback.
This is a time for discussion and improvements, and making the necessary changes will be required before we can
merge the contribution.

### Feedback :speech_balloon:

There are a few different ways to provide feedback:

- [Create a new issue](https://github.com/unfoldedcircle/integration-python-library/issues/new)
- [Reach out to us on Twitter](https://twitter.com/unfoldedcircle)
- [Visit our community forum](http://unfolded.community/)
- [Chat with us in our Discord channel](http://unfolded.chat/)
- [Send us a message on our website](https://unfoldedcircle.com/contact)
60 changes: 49 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
# Python API wrapper for the UC Integration API

This is a Python library that can be used for Python based integrations. It wraps the UC Integration API.
This library simplifies writing Python based integrations for the [Unfolded Circle Remote Two](https://www.unfoldedcircle.com/)
by wrapping the [WebSocket Integration API](https://github.com/unfoldedcircle/core-api/tree/main/integration-api).

It's a pre-alpha release. Missing features will be added continuously. Based on the NodeJS implementation.
It's a pre-alpha release (in our eyes). Missing features will be added continuously.
Based on our [Node.js integration library](https://github.com/unfoldedcircle/integration-node-library).

Not supported:
❗️**Attention:**
> This is our first Python project, and we don't see ourselves as Python professionals.
> Therefore, the library is most likely not yet that Pythonic!
> We are still learning and value your feedback on how to improve it :-)

- secure WebSocket
Not yet supported:

Requires Python 3.10 or newer
- Secure WebSocket
- Token based authentication

---
Requirements:
- Python 3.10 or newer

### Local testing:
```console
python3 setup.py bdist_wheel
pip3 install dist/ucapi-$VERSION-py3-none-any.whl
```
## Usage

See [examples directory](examples) for a minimal integration driver example.

More examples will be published.

### Environment Variables

Certain features can be configured by environment variables:

| Variable | Values | Description |
|--------------------------|------------------|----------------------------------------------------------------------------------------------------------------------|
| UC_CONFIG_HOME | _directory path_ | Configuration directory to save the user configuration from the driver setup.<br>Default: $HOME or current directory |
| UC_INTEGRATION_INTERFACE | _address_ | Listening interface for WebSocket server.<br>Default: `0.0.0.0` |
| UC_INTEGRATION_HTTP_PORT | _number_ | WebSocket listening port.<br>Default: `port` field in driver metadata json file, if not specified: `9090` |
| UC_MDNS_LOCAL_HOSTNAME | _hostname_ | Published local hostname in mDNS service announcement.<br>Default: _short hostname_ with `.local` domain. |
| UC_DISABLE_MDNS_PUBLISH | `true` / `false` | Disables mDNS service advertisement.<br>Default: `false` |

## Versioning

We use [SemVer](http://semver.org/) for versioning. For the versions available, see the
[tags and releases on this repository](https://github.com/unfoldedcircle/integration-python-library/releases).

## Changelog

The major changes found in each new release are listed in the [changelog](CHANGELOG.md) and
under the GitHub [releases](https://github.com/unfoldedcircle/integration-python-library/releases).

## Contributions

Please read our [contribution guidelines](./CONTRIBUTING.md) before opening a pull request.

## License

This project is licensed under the [**Mozilla Public License 2.0**](https://choosealicense.com/licenses/mpl-2.0/).
See the [LICENSE](LICENSE) file for details.
63 changes: 63 additions & 0 deletions docs/code_guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Code Style

This project uses the [PEP 8 – Style Guide for Python Code](https://peps.python.org/pep-0008/) as coding convention, with the
following customization:

- Code line length: 120
- Use double quotes as default (don't mix and match for simple quoting, checked with pylint).

## Tooling

Install all code linting tools:

```shell
pip3 install -r test-requirements.txt
```

### Linting

```shell
python -m pylint ucapi
```

- The tool is configured in `.pylintrc`.

Linting integration in PyCharm/IntelliJ IDEA:
1. Install plugin [Pylint](https://plugins.jetbrains.com/plugin/11084-pylint)
2. Open Pylint window and run a scan: `Check Module` or `Check Current File`

### Sort Imports

Import statements must be sorted with [isort](https://pycqa.github.io/isort/):

```shell
python -m isort ucapi/.
```

- The tool is configured in `pyproject.toml` to use the `black` code-formatting profile.

### Format Code

Source code is formatted with the [Black code formatting tool](https://github.com/psf/black):

```shell
python -m black ucapi --line-length 120
```

PyCharm/IntelliJ IDEA integration:
1. Go to `Preferences or Settings -> Tools -> Black`
2. Configure:
- Python interpreter
- Use Black formatter: `On code reformat` & optionally `On save`
- Arguments: `--line-length 120`

## Verify

The following tests are run as GitHub action for each push on the main branch and for pull requests.
They can also be run anytime on a local developer machine:
```shell
python -m pylint ucapi
python -m flake8 ucapi --count --show-source --statistics
python -m isort ucapi/. --check --verbose
python -m black ucapi --check --verbose --line-length 120
```
11 changes: 11 additions & 0 deletions docs/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Development Setup

This library requires Python 3.10 or newer.

## Local installation

```shell
python3 setup.py bdist_wheel
pip3 install --force-reinstall dist/ucapi-$VERSION-py3-none-any.whl
```

18 changes: 18 additions & 0 deletions examples/hello_integration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"driver_id": "hello_integration",
"version": "0.0.1",
"min_core_api": "0.20.0",
"name": { "en": "Hello Python integration" },
"icon": "uc:integration",
"description": {
"en": "Minimal Python integration driver example."
},
"port": 9080,
"developer": {
"name": "Unfolded Circle ApS",
"email": "[email protected]",
"url": "https://www.unfoldedcircle.com"
},
"home_page": "https://www.unfoldedcircle.com",
"release_date": "2023-10-30"
}
43 changes: 43 additions & 0 deletions examples/hello_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Hello world integration example. Bare minimum of an integration driver."""
import asyncio
import logging
from typing import Any

import ucapi

loop = asyncio.get_event_loop()
api = ucapi.IntegrationAPI(loop)


async def cmd_handler(entity: ucapi.Button, cmd_id: str, _params: dict[str, Any] | None) -> ucapi.StatusCodes:
"""
Push button command handler.

Called by the integration-API if a command is sent to a configured button-entity.

:param entity: button entity
:param cmd_id: command
:param _params: optional command parameters
:return: status of the command
"""
print(f"Got {entity.id} command request: {cmd_id}")

return ucapi.StatusCodes.OK


if __name__ == "__main__":
logging.basicConfig()

button = ucapi.Button(
"button1",
"Push the button",
cmd_handler=cmd_handler,
)
api.available_entities.add(button)

# We are ready all the time! Otherwise, use @api.listens_to(ucapi.Events.CONNECT) & DISCONNECT
api.set_device_state(ucapi.DeviceStates.CONNECTED)

loop.run_until_complete(api.init("hello_integration.json"))
loop.run_forever()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ucapi"
version = "0.0.11"
version = "0.1.0"
authors = [
{name = "Unfolded Circle ApS", email = "[email protected]"}
]
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

PACKAGE_NAME = "ucapi"
HERE = path.abspath(path.dirname(__file__))
VERSION = "0.0.11"
VERSION = "0.1.0"

with open(path.join(HERE, "README.md"), encoding="utf-8") as f:
long_description = f.read()
Expand Down
35 changes: 34 additions & 1 deletion ucapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,38 @@
Integration driver library for Remote Two.

:copyright: (c) 2023 by Unfolded Circle ApS.
:license: MPL 2.0, see LICENSE for more details.
:license: MPL-2.0, see LICENSE for more details.
"""

# Set default logging handler to avoid "No handler found" warnings.
import logging # isort:skip

from .api_definitions import ( # isort:skip # noqa: F401
DeviceStates,
DriverSetupRequest,
Events,
IntegrationSetupError,
RequestUserConfirmation,
RequestUserInput,
SetupAction,
SetupComplete,
SetupDriver,
SetupError,
StatusCodes,
UserConfirmationResponse,
UserDataResponse,
)
from .entity import Entity, EntityTypes # isort:skip # noqa: F401
from .entities import Entities # isort:skip # noqa: F401
from .api import IntegrationAPI # isort:skip # noqa: F401

# Entity types
from .button import Button # noqa: F401
from .climate import Climate # noqa: F401
from .cover import Cover # noqa: F401
from .light import Light # noqa: F401
from .media_player import MediaPlayer # noqa: F401
from .sensor import Sensor # noqa: F401
from .switch import Switch # noqa: F401

logging.getLogger(__name__).addHandler(logging.NullHandler())
Loading