diff --git a/duniterpy/api/ws2p/__init__.py b/duniterpy/api/ws2p/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2fac55f9b786a25b93e614276e335fa5aec68edd 100644 --- a/duniterpy/api/ws2p/__init__.py +++ b/duniterpy/api/ws2p/__init__.py @@ -0,0 +1,3 @@ +from . import network + + diff --git a/duniterpy/api/ws2p/network.py b/duniterpy/api/ws2p/network.py index 14b331c53ced6cd584bd7f873f7502ad6c9c89d1..a5c37808c0d5b9d9961ac4cb1bcddebf3305d3c7 100644 --- a/duniterpy/api/ws2p/network.py +++ b/duniterpy/api/ws2p/network.py @@ -14,55 +14,52 @@ # # Authors: # Caner Candan <caner@candan.fr>, http://caner.candan.fr -# +# vit +import logging -from duniterpy.api.client import API, logging, parse_response +from duniterpy.api.client import Client logger = logging.getLogger("duniter/network") -URL_PATH = 'network' +MODULE = 'network' WS2P_HEADS_SCHEMA = { - "type": "object", - "properties": { - "heads": { - "type": "array", - "items": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "sig": { - "type": "string", - }, - "messageV2": { - "type": "string" - }, - "sigV2": { - "type": "string", - }, - "step": { - "type": "number", - }, - }, - "required": ["message", "sig"] - } - } + "type": "object", + "properties": { + "heads": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "sig": { + "type": "string", + }, + "messageV2": { + "type": "string" + }, + "sigV2": { + "type": "string", }, - "required": ["heads"] - } + "step": { + "type": "number", + }, + }, + "required": ["message", "sig"] + } + } + }, + "required": ["heads"] +} -async def heads(connection): +def heads(client: Client): """ GET Certification data over a member - :param duniterpy.api.bma.ConnectionHandler connection: Connection handler instance + :param client: Client to connect to the api :rtype: dict """ - - client = API(connection, URL_PATH) - - r = await client.requests_get('/ws2p/heads') - return await parse_response(r, WS2P_HEADS_SCHEMA) + return client.connect_ws(MODULE + '/ws2p/heads') diff --git a/examples/request_ws2p.py b/examples/request_ws2p.py new file mode 100644 index 0000000000000000000000000000000000000000..095215d124b3e23222e772fa93134a19d298e298 --- /dev/null +++ b/examples/request_ws2p.py @@ -0,0 +1,73 @@ +import asyncio +from _socket import gaierror + +import aiohttp +import jsonschema + +from duniterpy.api.client import Client, parse_text +from duniterpy.api import ws2p + +# CONFIG ####################################### + +# You can either use a complete defined endpoint : [NAME_OF_THE_API] [DOMAIN] [IPv4] [IPv6] [PORT] +# or the simple definition : [NAME_OF_THE_API] [DOMAIN] [PORT] +# Here we use the secure BASIC_MERKLED_API (BMAS) +WS2P_ENDPOINT = "WS2P id ip port" + +################################################ + + +async def main(): + """ + Main code (synchronous requests) + """ + # Create Client from endpoint string in Duniter format + client = Client(WS2P_ENDPOINT) + + # Get the node summary infos by dedicated method (with json schema validation) + print("\nCall ws2p.heads:") + try: + # Create Web Socket connection on block path + ws_connection = client(ws2p.network.heads) + + # From the documentation ws_connection should be a ClientWebSocketResponse object... + # + # https://docs.aiohttp.org/en/stable/client_quickstart.html#websockets + # + # In reality, aiohttp.session.ws_connect() returns a aiohttp.client._WSRequestContextManager instance. + # It must be used in a with statement to get the ClientWebSocketResponse instance from it (__aenter__). + # At the end of the with statement, aiohttp.client._WSRequestContextManager.__aexit__ is called + # and close the ClientWebSocketResponse in it. + + # Mandatory to get the "for msg in ws" to work ! + async with ws_connection as ws: + print("Connected successfully to web socket block path") + # Iterate on each message received... + async for msg in ws: + # if message type is text... + if msg.type == aiohttp.WSMsgType.TEXT: + print("Received a block") + # Validate jsonschema and return a the json dict + block_data = parse_text(msg.data, ws2p.network.WS2P_HEADS_SCHEMA) + print(block_data) + elif msg.type == aiohttp.WSMsgType.CLOSED: + # Connection is closed + print("Web socket connection closed !") + elif msg.type == aiohttp.WSMsgType.ERROR: + # Connection error + print("Web socket connection error !") + + # Close session + await client.close() + + except (aiohttp.WSServerHandshakeError, ValueError) as e: + print("Websocket block {0} : {1}".format(type(e).__name__, str(e))) + except (aiohttp.ClientError, gaierror, TimeoutError) as e: + print("{0} : {1}".format(str(e), WS2P_ENDPOINT)) + except jsonschema.ValidationError as e: + print("{:}:{:}".format(str(e.__class__.__name__), str(e))) + + +# Latest duniter-python-api is asynchronous and you have to use asyncio, an asyncio loop and a "as" on the data. +# ( https://docs.python.org/3/library/asyncio.html ) +asyncio.get_event_loop().run_until_complete(main()) diff --git a/tests/api/test_bma.py b/tests/api/bma/test_bma.py similarity index 100% rename from tests/api/test_bma.py rename to tests/api/bma/test_bma.py diff --git a/tests/api/bma/test_ws2p.py b/tests/api/bma/test_ws2p.py deleted file mode 100644 index 1e4c9e817ebb3fa9de26d57ec58a0b49ab32d5fa..0000000000000000000000000000000000000000 --- a/tests/api/bma/test_ws2p.py +++ /dev/null @@ -1,54 +0,0 @@ -import unittest -import aiohttp -import jsonschema -import json -from duniterpy.documents import BMAEndpoint -from tests.api.webserver import WebFunctionalSetupMixin, web, asyncio -from duniterpy.api.bma.network import heads, WS2P_HEADS_SCHEMA -from duniterpy.api.bma import parse_text - - -class Test_WS2P_Heads(WebFunctionalSetupMixin, unittest.TestCase): - - def test_block(self): - json_sample = { - "heads": [ - { - "message": "WS2POCAIC:HEAD:1:8iVdpXqFLCxGyPqgVx5YbFSkmWKkceXveRd2yvBKeARL:102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:e66254bf:duniter:1.6.20:1", - "sig": "ZO5gSUMK6IaUEwU4K40nhuHOfnJ6Zfn8VS+4Ko2FM7t+mDsHf+3gDRT9PgV2p0fz81mF6jVYWpq2UYEsnK/gCg==", - "messageV2": "WS2POCAIC:HEAD:2:8iVdpXqFLCxGyPqgVx5YbFSkmWKkceXveRd2yvBKeARL:102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:e66254bf:duniter:1.6.20:1:15:14", - "sigV2": "ReXzbgUya9jo4dL/R4g19Y+RE9BGB0xDkw7mrBWoldlRLkq3KFyRkAf9VthVx1UUb/AINr3nxImZKVQiVH9+DQ==", - "step": 0 - }, - { - "message": "WS2POCAIC:HEAD:1:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:a0a45ed2:duniter:1.6.21:1", - "sig": "pXLMmOpyEMdWihT183g/rnCvMzA2gHki5Cxg7rEl3psQu0RuK0ObCv5YFhmQnRlg+QZ1CWfbYEEbm3G1eGplAQ==", - "messageV2": "WS2POCAIC:HEAD:2:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:a0a45ed2:duniter:1.6.21:1:34:28", - "sigV2": "p5f7/KfQqjTaCYSMUXpjUDH7uF2DafetHNgphGzkOXgxM+Upeii0Fz2RFBwnZvN+Gjp81hAqSuH48PJP6HJSAw==", - "step": 1 - }, - { - "message": "WS2POCA:HEAD:1:GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4:102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:6d0e96f9:duniter:1.6.21:1", - "sig": "h9o1XBEV18gUzbvj1jdQB1M7U8ifZprIyVwLdlSQEfeG9WZLvZAjYzLGA2nD6h/9RkJLOJPzIQJXysHUHJ2dDQ==", - "messageV2": "WS2POCA:HEAD:2:GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4:102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:6d0e96f9:duniter:1.6.21:1:20:20", - "sigV2": "VsyQmXOUYrfHWy0FeS4rJrIJCUBI+3BergbSYQ78icJWV6MQzZSw7Z+Yl7urujCYZriDQM76D6GW+6F0EELpBQ==", - "step": 2 - }, - ] -} - - jsonschema.validate(json_sample, WS2P_HEADS_SCHEMA) - - def test_bma_ws2p_heads_bad(self): - async def handler(request): - await request.read() - return web.Response(body=b'{}', content_type='application/json') - - async def go(): - _, port, url = await self.create_server('GET', '/network/ws2p/heads', handler) - with self.assertRaises(jsonschema.ValidationError): - async with aiohttp.ClientSession() as session: - connection = BMAEndpoint("127.0.0.1", None, None, port).conn_handler(session) - await heads(connection) - - self.loop.run_until_complete(go()) diff --git a/tests/api/ws2p/__init__.py b/tests/api/ws2p/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/api/ws2p/test_ws2p.py b/tests/api/ws2p/test_ws2p.py new file mode 100644 index 0000000000000000000000000000000000000000..7c97c720bf16ff7aba5955057fa3e203cf1e4b93 --- /dev/null +++ b/tests/api/ws2p/test_ws2p.py @@ -0,0 +1,63 @@ +import unittest + +import jsonschema + +from duniterpy.api.client import Client +from duniterpy.api.endpoint import BMAEndpoint +from duniterpy.api.ws2p.network import heads, WS2P_HEADS_SCHEMA +from tests.api.webserver import WebFunctionalSetupMixin, web + + +class TestWs2pHeads(WebFunctionalSetupMixin, unittest.TestCase): + + def test_block(self): + json_sample = { + "heads": [ + { + "message": "WS2POCAIC:HEAD:1:8iVdpXqFLCxGyPqgVx5YbFSkmWKkceXveRd2yvBKeARL:\ + 102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:e66254bf:duniter:1.6.20:1", + "sig": "ZO5gSUMK6IaUEwU4K40nhuHOfnJ6Zfn8VS+4Ko2FM7t+mDsHf+3gDRT9PgV2p0fz81mF6jVYWpq2UYEsnK/gCg==", + "messageV2": "WS2POCAIC:HEAD:2:8iVdpXqFLCxGyPqgVx5YbFSkmWKkceXveRd2yvBKeARL:\ + 102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:e66254bf:\ + duniter:1.6.20:1:15:14", + "sigV2": "ReXzbgUya9jo4dL/R4g19Y+RE9BGB0xDkw7mrBWoldlRLkq3KFyRkAf9VthVx1UUb/AINr3nxImZKVQiVH9+DQ==", + "step": 0 + }, + { + "message": "WS2POCAIC:HEAD:1:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:\ + 102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:a0a45ed2:duniter:1.6.21:1", + "sig": "pXLMmOpyEMdWihT183g/rnCvMzA2gHki5Cxg7rEl3psQu0RuK0ObCv5YFhmQnRlg+QZ1CWfbYEEbm3G1eGplAQ==", + "messageV2": "WS2POCAIC:HEAD:2:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:\ + 102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:a0a45ed2:\ + duniter:1.6.21:1:34:28", + "sigV2": "p5f7/KfQqjTaCYSMUXpjUDH7uF2DafetHNgphGzkOXgxM+Upeii0Fz2RFBwnZvN+Gjp81hAqSuH48PJP6HJSAw==", + "step": 1 + }, + { + "message": "WS2POCA:HEAD:1:GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4:\ + 102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:6d0e96f9:duniter:1.6.21:1", + "sig": "h9o1XBEV18gUzbvj1jdQB1M7U8ifZprIyVwLdlSQEfeG9WZLvZAjYzLGA2nD6h/9RkJLOJPzIQJXysHUHJ2dDQ==", + "messageV2": "WS2POCA:HEAD:2:GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4:\ + 102102-000002C0694C7D373A78B095419C86584B81804CFB9641B7EBC3A18040B6FEE6:6d0e96f9:\ + duniter:1.6.21:1:20:20", + "sigV2": "VsyQmXOUYrfHWy0FeS4rJrIJCUBI+3BergbSYQ78icJWV6MQzZSw7Z+Yl7urujCYZriDQM76D6GW+6F0EELpBQ==", + "step": 2 + }, + ] + } + + jsonschema.validate(json_sample, WS2P_HEADS_SCHEMA) + + def test_ws2p_heads_bad(self): + async def handler(request): + await request.read() + return web.Response(body=b'{}', content_type='application/json') + + async def go(): + _, port, url = await self.create_server('GET', '/network/ws2p/heads', handler) + with self.assertRaises(jsonschema.ValidationError): + client = Client(BMAEndpoint("127.0.0.1", "", "", port)) + await client(heads) + await client.close() + + self.loop.run_until_complete(go())