Skip to content

Commit

Permalink
Merge pull request #60 from automata-tech/feature/zeroconf
Browse files Browse the repository at this point in the history
Add a way to discover Evas on your network programmatically
  • Loading branch information
LouisBrunner authored Jul 7, 2020
2 parents f4b73d7 + 6b5dfa4 commit eafbfa0
Show file tree
Hide file tree
Showing 13 changed files with 298 additions and 22 deletions.
22 changes: 9 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,26 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [3.5, 3.6, 3.7]
python-version: [3.6, 3.7, 3.8]

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install pipenv
uses: dschep/install-pipenv-action@v1
- name: Install dependencies
uses: "VaultVulp/action-pipenv@master"
with:
command: install --dev
run: pipenv install --dev --python ${{ matrix.python-version }}
- name: Run linter
uses: "VaultVulp/action-pipenv@master"
with:
command: run flake8
run: pipenv run lint
- name: Run type
run: pipenv run type
- name: Run tests
uses: "VaultVulp/action-pipenv@master"
with:
command: run test --cov=evasdk --cov-branch --cov-report=xml
run: pipenv run test --cov=evasdk --cov-branch --cov-report=xml
- name: Upload to codecov
uses: codecov/codecov-action@v1.0.3
uses: codecov/codecov-action@v1.0.7
with:
token: ${{secrets.CODECOV_TOKEN}}
file: ./coverage.xml
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
Expand All @@ -27,7 +27,7 @@ jobs:
sed -i 's/%VERSION%/'$VERSION'/' evasdk/version.py
python setup.py sdist bdist_wheel
- name: Publish a Python distribution to PyPI
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@v1.3.0
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build/
dist/
.pytest_cache
*coverage*
.mypy_cache
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2018 Automata Technologies Ltd
Copyright 2015-2020 Automata Technologies Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
9 changes: 8 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@ name = "pypi"
[packages]
requests = "*"
websockets = "*"
zeroconf = "==0.27.1"
dataclasses = "*"

[dev-packages]
flake8 = "*"
requests-mock = "*"
pytest = "*"
"pytest-cov" = "*"
"pytest-flake8" = "*"
"pytest-mypy" = "*"
mypy = "*"

[requires]

[scripts]
test = "python -m pytest tests/"
test = "pipenv run testd tests/"
testd = "python -m pytest --mypy --flake8"
lint = "flake8"
type = "mypy evasdk examples tests"
101 changes: 100 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ __* This SDK is currently in beta__

__Requires Python 3, not compatible with Python 2__

We support Python `3.6` and later.

### Pip

Make sure you have Python3 and pip installed, then run the following command:
Expand Down Expand Up @@ -162,7 +164,7 @@ $ pipenv run test

# or to run a single test file:
$ pipenv shell
$ python -m pytest tests/<test-name>_test.py
$ pipenv run testd tests/<test-name>_test.py

# some test require supplying ip and token via the `--ip` and `--token` arguements:
$ pipenv run test --ip 172.16.16.2 --token abc-123-def-456
Expand Down
121 changes: 121 additions & 0 deletions evasdk/EvaDiscoverer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from typing import Callable
from dataclasses import dataclass
from threading import Condition
from .Eva import Eva
from zeroconf import ServiceBrowser, Zeroconf


CHOREO_SERVICE = "_automata-eva._tcp.local."


@dataclass
class DiscoveredEva:
name: str
host: str

def connect(self, token) -> Eva:
return Eva(self.host, token)


DiscoverCallback = Callable[[str, DiscoveredEva], None]


class EvaDiscoverer:


def __init__(self, callback: DiscoverCallback, name: str = None):
self.name = name
self.callback = callback
self.zeroconf = None

def __enter__(self):
self.zeroconf = Zeroconf()
self.browser = ServiceBrowser(self.zeroconf, CHOREO_SERVICE, self)

def __exit__(self, exc_type, exc_val, exc_tb):
self.zeroconf.close()

def __get_eva(self, zeroconf: Zeroconf, service_type: str, service_name: str):
info = zeroconf.get_service_info(service_type, service_name)
if info is None:
return None
return DiscoveredEva(host=info.server, name=info.properties[b'name'].decode("utf-8"))

def __filter_name(self, eva):
return self.name is not None and self.name != eva.name

def add_service(self, zeroconf: Zeroconf, service_type: str, service_name: str):
eva = self.__get_eva(zeroconf, service_type, service_name)
if eva is None or self.__filter_name(eva):
return
self.callback('added', eva)

def remove_service(self, zeroconf: Zeroconf, service_type: str, service_name: str):
eva = self.__get_eva(zeroconf, service_type, service_name)
if eva is None or self.__filter_name(eva):
return
self.callback('removed', eva)


def __find_evas(callback: DiscoverCallback, timeout: float, name: str = None, condition: Condition = None):
if condition is None:
condition = Condition()
with EvaDiscoverer(name=name, callback=callback):
with condition:
condition.wait(timeout=timeout)


def find_evas(timeout: float = 5):
"""Blocks for `timeout` seconds and returns a dictionary of DiscoveredEva (with their names as key) discovered in that time"""
evas = {}

def __callback(event: str, eva: DiscoveredEva):
if event == 'added':
evas[eva.name] = eva
elif event == 'deleted':
del evas[eva.name]

__find_evas(callback=__callback, timeout=timeout)
return evas


def find_eva(name: str, timeout: float = 5):
"""Blocks for a maximum of `timeout` seconds and returns a DiscoveredEva if a robot named `name` was found, or `None`"""
eva = None
cv = Condition()

def __callback(event: str, eva_found: DiscoveredEva):
nonlocal eva
if event == 'added':
eva = eva_found
with cv:
cv.notify()

__find_evas(name=name, callback=__callback, timeout=timeout, condition=cv)
return eva


def find_first_eva(timeout: float = 5):
"""Blocks for a maximum of `timeout` seconds and returns a DiscoveredEva if one was found, or `None`"""
eva = None
cv = Condition()

def __callback(event: str, eva_found: DiscoveredEva):
nonlocal eva
if event == 'added' and eva is None:
eva = eva_found
with cv:
cv.notify()

__find_evas(callback=__callback, timeout=timeout, condition=cv)
return eva


def discover_evas(callback: DiscoverCallback):
"""Returns a context that will discovers robots until exited
It will call `callback` with 2 arguments: the event (either `added` or `removed`) and a Discovered Eva object
Note that `callback` will be called from another thread so you will need to ensure any data accessed there is done in a thread-safe manner
"""
return EvaDiscoverer(callback=callback)
4 changes: 4 additions & 0 deletions evasdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@
EvaValidationError, EvaAuthError, EvaAutoRenewError,
EvaAdminError, EvaServerError)
from .version import __version__
from .EvaDiscoverer import (
DiscoverCallback, DiscoveredEva,
find_evas, find_eva, find_first_eva, discover_evas,
)
Loading

0 comments on commit eafbfa0

Please sign in to comment.