Michael Pilosov
2 years ago
commit
2e0bf0cb67
9 changed files with 228 additions and 0 deletions
@ -0,0 +1,8 @@ |
|||
__pycache__/ |
|||
*.pyc |
|||
*.pyo |
|||
*.egg-info/ |
|||
*.eggs/ |
|||
dist/ |
|||
build/ |
|||
Pipfile* |
@ -0,0 +1,39 @@ |
|||
[metadata] |
|||
name = announce_server |
|||
author = Mathematical Michael |
|||
author_email = mm@clfx.cc |
|||
url = https://git.mlden.com/mm/announce-server.git |
|||
license = MIT |
|||
description = Announces a server to a host |
|||
long_description = file: README.md |
|||
long_description_content_type = text/markdown |
|||
classifiers = |
|||
Development Status :: 3 - Alpha |
|||
Intended Audience :: Developers |
|||
License :: OSI Approved :: MIT License |
|||
Programming Language :: Python :: 3 |
|||
Programming Language :: Python :: 3.6 |
|||
Programming Language :: Python :: 3.7 |
|||
Programming Language :: Python :: 3.8 |
|||
Programming Language :: Python :: 3.9 |
|||
Programming Language :: Python :: 3.10 |
|||
|
|||
|
|||
[options] |
|||
package_dir = |
|||
= src |
|||
packages = find: |
|||
install_requires = |
|||
python-socketio[asyncio_client] |
|||
|
|||
[options.packages.find] |
|||
where = src |
|||
|
|||
[options.extras_require] |
|||
dev = |
|||
build |
|||
setuptools_scm |
|||
pytest |
|||
pytest-mock |
|||
pytest-asyncio |
|||
asynctest; python_version<'3.8' |
@ -0,0 +1,11 @@ |
|||
from setuptools import find_packages, setup |
|||
|
|||
setup( |
|||
name="announce-server", |
|||
packages=find_packages(), |
|||
setup_requires=[ |
|||
"setuptools_scm", |
|||
], |
|||
use_scm_version=True, |
|||
python_requires=">=3.6", |
|||
) |
@ -0,0 +1,2 @@ |
|||
from .decorator import _announce_server, announce_server |
|||
from .get_ip import get_ip_address |
@ -0,0 +1,69 @@ |
|||
import asyncio |
|||
from functools import wraps |
|||
|
|||
import socketio |
|||
|
|||
sio = socketio.AsyncClient() |
|||
|
|||
|
|||
async def _announce_server(**kwargs): |
|||
SERVER_NAME = kwargs.get("name", "server_1") |
|||
SERVER_IP = kwargs.get("ip", "localhost") |
|||
SERVER_PORT = kwargs.get("port", 8000) |
|||
HOST_SERVER_IP = kwargs.get("host_ip", "0.0.0.0") |
|||
HOST_SERVER_PORT = kwargs.get("host_port", 5000) |
|||
|
|||
@sio.event |
|||
async def connect(): |
|||
await sio.emit( |
|||
"register", {"name": SERVER_NAME, "ip": SERVER_IP, "port": SERVER_PORT} |
|||
) |
|||
print("Announced server to host") |
|||
|
|||
async def main(): |
|||
# retry until we connect to the host |
|||
while True: |
|||
try: |
|||
await sio.connect(f"http://{HOST_SERVER_IP}:{HOST_SERVER_PORT}") |
|||
break |
|||
except Exception as e: |
|||
print(e) |
|||
print("Failed to connect to host, retrying in 5 seconds") |
|||
await asyncio.sleep(5) |
|||
# await sio.connect(f'http://{HOST_SERVER_IP}:{HOST_SERVER_PORT}') |
|||
print("Connected to host") |
|||
|
|||
@sio.on("heartbeat") |
|||
async def on_heartbeat(): |
|||
print("Received heartbeat from host") |
|||
|
|||
@sio.event |
|||
async def disconnect(): |
|||
print("Disconnected from host") |
|||
|
|||
await main() |
|||
|
|||
|
|||
def announce_server(task=None, loop=None, **outer_kwargs): |
|||
if task is None: |
|||
return lambda f: announce_server(f, loop=loop, **outer_kwargs) |
|||
|
|||
@wraps(task) |
|||
def wrapper(*args, **kwargs): |
|||
async def main(*args, **kwargs): |
|||
if loop is not None: |
|||
host_block_thread = loop.run_in_executor(None, task) |
|||
else: |
|||
host_block_thread = asyncio.to_thread(task) |
|||
|
|||
# Announce the server to the host |
|||
await _announce_server(**outer_kwargs) |
|||
|
|||
# Wait for host_block to finish |
|||
await host_block_thread |
|||
|
|||
if loop is not None: |
|||
return loop.create_task(main(*args, **kwargs)) |
|||
else: |
|||
return asyncio.run(main(*args, **kwargs)) |
|||
return wrapper |
@ -0,0 +1,14 @@ |
|||
import socket |
|||
|
|||
|
|||
def get_ip_address(): |
|||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|||
try: |
|||
# This IP address doesn't need to be reachable, as we're only using it to find the local IP address |
|||
s.connect(("10.255.255.255", 1)) |
|||
ip = s.getsockname()[0] |
|||
except Exception: |
|||
ip = "127.0.0.1" |
|||
finally: |
|||
s.close() |
|||
return ip |
@ -0,0 +1,51 @@ |
|||
import asyncio |
|||
import sys |
|||
from unittest.mock import patch |
|||
|
|||
if sys.version_info >= (3, 8): |
|||
from unittest.mock import AsyncMock |
|||
else: |
|||
from asynctest import CoroutineMock as AsyncMock |
|||
|
|||
import pytest |
|||
|
|||
from announce_server import _announce_server, announce_server |
|||
|
|||
|
|||
@pytest.mark.asyncio |
|||
async def test_announce_server_decorator(event_loop, mocker): |
|||
# Mock the _announce_server function to prevent actual connections |
|||
mocker.patch("announce_server._announce_server") |
|||
|
|||
# Sample function to be decorated |
|||
async def sample_async_function(): |
|||
await asyncio.sleep(1) |
|||
return "Hello, world!" |
|||
|
|||
# Decorate the sample function with announce_server |
|||
decorated_function = announce_server( |
|||
name="test_server", |
|||
ip="127.0.0.1", |
|||
port=8000, |
|||
host_ip="127.0.0.1", |
|||
host_port=5000, |
|||
loop=event_loop, # Pass the current event loop |
|||
)(sample_async_function) |
|||
|
|||
# Run the decorated function |
|||
task = await decorated_function() |
|||
await asyncio.sleep(1.1) # Sleep slightly longer than sample_async_function |
|||
task.cancel() # Cancel the task |
|||
|
|||
# Check if the _announce_server function was called with the correct arguments |
|||
announce_server._announce_server.assert_called_once_with( |
|||
name="test_server", |
|||
ip="127.0.0.1", |
|||
port=8000, |
|||
host_ip="127.0.0.1", |
|||
host_port=5000, |
|||
) |
|||
|
|||
# Check if the decorated function returns the expected result |
|||
result = await sample_async_function() |
|||
assert result == "Hello, world!" |
@ -0,0 +1,34 @@ |
|||
import socket |
|||
from unittest.mock import MagicMock, patch |
|||
|
|||
import pytest |
|||
|
|||
from announce_server import get_ip_address |
|||
|
|||
|
|||
def test_get_ip_address(): |
|||
with patch("socket.socket") as mock_socket: |
|||
# Create a MagicMock object for the socket object |
|||
mock_socket_instance = MagicMock() |
|||
mock_socket.return_value = mock_socket_instance |
|||
|
|||
# Define the expected IP address |
|||
expected_ip = "192.168.1.100" |
|||
|
|||
# Configure the mock socket instance to return the expected IP address |
|||
mock_socket_instance.getsockname.return_value = (expected_ip, 0) |
|||
|
|||
# Test the get_ip_address function |
|||
result_ip = get_ip_address() |
|||
|
|||
# Check if the result matches the expected IP address |
|||
assert result_ip == expected_ip |
|||
|
|||
# Check if the socket object was created with the correct arguments |
|||
mock_socket.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM) |
|||
|
|||
# Check if the socket.connect method was called with the correct arguments |
|||
mock_socket_instance.connect.assert_called_once_with(("10.255.255.255", 1)) |
|||
|
|||
# Check if the socket.close method was called |
|||
mock_socket_instance.close.assert_called_once() |
Loading…
Reference in new issue