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