diff --git a/docs/generate.sh b/docs/generate.sh old mode 100755 new mode 100644 diff --git a/docs/make.bat b/docs/make.bat old mode 100644 new mode 100755 diff --git a/duniterpy/api/bma/blockchain.py b/duniterpy/api/bma/blockchain.py index 604110e64f238bb6cfa43f6622bfe9afd61563cf..a76e587bec03fe57602d0c0783f1c6742e806db2 100644 --- a/duniterpy/api/bma/blockchain.py +++ b/duniterpy/api/bma/blockchain.py @@ -309,7 +309,7 @@ async def parameters(client: Client) -> dict: GET the blockchain parameters used by this node :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/parameters', schema=PARAMETERS_SCHEMA) @@ -320,7 +320,7 @@ async def memberships(client: Client, search: str) -> dict: :param client: Client to connect to the api :param search: UID/Public key - :rtype: dict + :return: """ return await client.get(MODULE + '/memberships/%s' % search, schema=MEMBERSHIPS_SCHEMA) @@ -331,17 +331,17 @@ async def membership(client: Client, membership_signed_raw: str) -> ClientRespon :param client: Client to connect to the api :param membership_signed_raw: Membership signed raw document - :rtype: aiohttp.ClientResponse + :return: """ return await client.post(MODULE + '/membership', {'membership': membership_signed_raw}, rtype=RESPONSE_AIOHTTP) async def current(client: Client) -> dict: """ - GET, return last accepted block + GET the last accepted block :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/current', schema=BLOCK_SCHEMA) @@ -355,7 +355,7 @@ async def block(client: Client, number: int = 0, block_raw: str = None, signatur :param number: Block number to get :param block_raw: Block document to post :param signature: Signature of the block document issuer - :rtype: dict + :return: """ # POST block if block_raw is not None and signature is not None: @@ -372,7 +372,7 @@ async def blocks(client: Client, count: int, start: int) -> list: :param client: Client to connect to the api :param count: Number of blocks :param start: First block number - :rtype: list + :return: """ assert type(count) is int assert type(start) is int @@ -386,86 +386,86 @@ async def hardship(client: Client, pubkey: str) -> dict: :param client: Client to connect to the api :param pubkey: Public key of the member - :rtype: dict + :return: """ return await client.get(MODULE + '/hardship/%s' % pubkey, schema=HARDSHIP_SCHEMA) async def newcomers(client: Client) -> dict: """ - GET, return block numbers containing newcomers + GET the block numbers containing newcomers :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/newcomers', schema=BLOCK_NUMBERS_SCHEMA) async def certifications(client: Client) -> dict: """ - GET, return block numbers containing certifications + GET the block numbers containing certifications :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/certs', schema=BLOCK_NUMBERS_SCHEMA) async def joiners(client: Client) -> dict: """ - GET, return block numbers containing joiners + GET the block numbers containing joiners :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/joiners', schema=BLOCK_NUMBERS_SCHEMA) async def actives(client: Client) -> dict: """ - GET, return block numbers containing actives + GET the block numbers containing actives :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/actives', schema=BLOCK_NUMBERS_SCHEMA) async def leavers(client: Client) -> dict: """ - GET, return block numbers containing leavers + GET the block numbers containing leavers :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/leavers', schema=BLOCK_NUMBERS_SCHEMA) async def excluded(client: Client) -> dict: """ - GET, return block numbers containing excluded + GET the block numbers containing excluded :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/excluded', schema=BLOCK_NUMBERS_SCHEMA) async def ud(client: Client) -> dict: """ - GET, return block numbers containing universal dividend + GET the block numbers containing universal dividend :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/ud', schema=BLOCK_NUMBERS_SCHEMA) async def tx(client: Client) -> dict: """ - GET, return block numbers containing transactions + GET the block numbers containing transactions :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/with/tx', schema=BLOCK_NUMBERS_SCHEMA) diff --git a/duniterpy/api/bma/network.py b/duniterpy/api/bma/network.py index bd91569b69b954ce2c19d0d7a6d672d5496e0fb1..6f6ad3d7ea52984f4c94b924fc2802598e2c664a 100644 --- a/duniterpy/api/bma/network.py +++ b/duniterpy/api/bma/network.py @@ -109,7 +109,7 @@ async def peering(client: Client) -> dict: GET peering information about a peer :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/peering', schema=PEERING_SCHEMA) @@ -121,7 +121,7 @@ async def peers(client: Client, leaves: bool = False, leaf: str = "") -> dict: :param client: Client to connect to the api :param leaves: True if leaves should be requested :param leaf: True if leaf should be requested - :rtype: dict + :return: """ if leaves is True: return await client.get(MODULE + '/peering/peers', {"leaves": "true"}, schema=PEERS_SCHEMA) @@ -135,6 +135,6 @@ async def peer(client: Client, peer_signed_raw: str) -> ClientResponse: :param client: Client to connect to the api :param peer_signed_raw: Peer signed raw document - :rtype: ClientResponse + :return: """ return await client.post(MODULE + '/peering/peers', {'peer': peer_signed_raw}, rtype=RESPONSE_AIOHTTP) diff --git a/duniterpy/api/bma/node.py b/duniterpy/api/bma/node.py index d42cd0a6f7df4f787b49516f0e72eccbbeca6d99..ba5ce83ca0a9b5271e32ae4276fac3a8ae34339b 100644 --- a/duniterpy/api/bma/node.py +++ b/duniterpy/api/bma/node.py @@ -29,7 +29,7 @@ async def summary(client: Client) -> dict: GET Duniter node version and infos :param client: Client to connect to the api - :rtype: dict + :return: """ schema = { "type": "object", diff --git a/duniterpy/api/bma/tx.py b/duniterpy/api/bma/tx.py index 2a358b4e1ca74ee2dfcdd6a89a4b4859eb0d9f4c..9e17d49b6df5540e8f303ff7c2bb8dcd9a94d5b2 100644 --- a/duniterpy/api/bma/tx.py +++ b/duniterpy/api/bma/tx.py @@ -206,7 +206,7 @@ async def history(client: Client, pubkey: str) -> dict: :param client: Client to connect to the api :param pubkey: Public key - :rtype: dict + :return: """ return await client.get(MODULE + '/history/%s' % pubkey, schema=HISTORY_SCHEMA) @@ -217,7 +217,7 @@ async def process(client: Client, transaction_signed_raw: str) -> ClientResponse :param client: Client to connect to the api :param transaction_signed_raw: Transaction signed raw document - :rtype: ClientResponse + :return: """ return await client.post(MODULE + '/process', {'transaction': transaction_signed_raw}, rtype=RESPONSE_AIOHTTP) @@ -228,7 +228,7 @@ async def sources(client: Client, pubkey: str) -> dict: :param client: Client to connect to the api :param pubkey: Public key - :rtype: dict + :return: """ return await client.get(MODULE + '/sources/%s' % pubkey, schema=SOURCES_SCHEMA) @@ -241,7 +241,7 @@ async def blocks(client: Client, pubkey: str, start: int, end: int) -> dict: :param pubkey: Public key :param start: Start from block number :param end: End to block number - :return: dict + :return: """ return await client.get(MODULE + '/history/%s/blocks/%s/%s' % (pubkey, start, end), schema=HISTORY_SCHEMA) @@ -254,6 +254,6 @@ async def times(client: Client, pubkey: str, start: int, end: int) -> dict: :param pubkey: Public key :param start: Start from timestamp :param end: End to timestamp - :return: dict + :return: """ return await client.get(MODULE + '/history/%s/times/%s/%s' % (pubkey, start, end), schema=HISTORY_SCHEMA) diff --git a/duniterpy/api/bma/ud.py b/duniterpy/api/bma/ud.py index 6522fc5da59138df0592d25640c0882e160778ff..9fed33be5f79ae3e31117abc8c100ff7d6bde443 100644 --- a/duniterpy/api/bma/ud.py +++ b/duniterpy/api/bma/ud.py @@ -72,6 +72,6 @@ async def history(client: Client, pubkey: str) -> dict: :param client: Client to connect to the api :param pubkey: Public key of the member - :rtype: dict + :return: """ return await client.get(MODULE + '/history/%s' % pubkey, schema=UD_SCHEMA) diff --git a/duniterpy/api/bma/wot.py b/duniterpy/api/bma/wot.py index ffa8d8bf48f477e374a8f26d9bcb7efb8d325180..1599900384cae85e740e548c5bf9ad535100d8e2 100644 --- a/duniterpy/api/bma/wot.py +++ b/duniterpy/api/bma/wot.py @@ -305,7 +305,7 @@ async def add(client: Client, identity_signed_raw: str) -> ClientResponse: :param client: Client to connect to the api :param identity_signed_raw: Identity raw document - :rtype: aiohttp.ClientResponse + :return: """ return await client.post(MODULE + '/add', {'identity': identity_signed_raw}, rtype=RESPONSE_AIOHTTP) @@ -316,7 +316,7 @@ async def certify(client: Client, certification_signed_raw: str) -> ClientRespon :param client: Client to connect to the api :param certification_signed_raw: Certification raw document - :rtype: aiohttp.ClientResponse + :return: """ return await client.post(MODULE + '/certify', {'cert': certification_signed_raw}, rtype=RESPONSE_AIOHTTP) @@ -327,7 +327,7 @@ async def revoke(client: Client, revocation_signed_raw: str) -> ClientResponse: :param client: Client to connect to the api :param revocation_signed_raw: Certification raw document - :rtype: aiohttp.ClientResponse + :return: """ return await client.post(MODULE + '/revoke', {'revocation': revocation_signed_raw}, rtype=RESPONSE_AIOHTTP) @@ -338,7 +338,7 @@ async def lookup(client: Client, search: str) -> dict: :param client: Client to connect to the api :param search: UID or public key - :rtype: dict + :return: """ return await client.get(MODULE + '/lookup/%s' % search, schema=LOOKUP_SCHEMA) @@ -349,7 +349,7 @@ async def certifiers_of(client: Client, search: str) -> dict: :param client: Client to connect to the api :param search: UID or public key - :rtype: dict + :return: """ return await client.get(MODULE + '/certifiers-of/%s' % search, schema=CERTIFICATIONS_SCHEMA) @@ -360,7 +360,7 @@ async def certified_by(client: Client, search: str) -> dict: :param client: Client to connect to the api :param search: UID or public key - :rtype: dict + :return: """ return await client.get(MODULE + '/certified-by/%s' % search, schema=CERTIFICATIONS_SCHEMA) @@ -370,7 +370,7 @@ async def members(client: Client) -> dict: GET list of all current members of the Web of Trust :param client: Client to connect to the api - :rtype: dict + :return: """ return await client.get(MODULE + '/members', schema=MEMBERS_SCHEMA) @@ -381,6 +381,6 @@ async def requirements(client: Client, search: str) -> dict: :param client: Client to connect to the api :param search: UID or public key - :rtype: dict + :return: """ return await client.get(MODULE + '/requirements/%s' % search, schema=REQUIREMENTS_SCHEMA) diff --git a/duniterpy/api/bma/ws.py b/duniterpy/api/bma/ws.py index 884bcc3311e493d6ed72ebb111f56ac239ec5e0f..be6c49849e3a068b5245d56f87c3e894c0ff311d 100644 --- a/duniterpy/api/bma/ws.py +++ b/duniterpy/api/bma/ws.py @@ -18,7 +18,7 @@ # vit import logging -from aiohttp import ClientWebSocketResponse +from aiohttp.client import _WSRequestContextManager from duniterpy.api.bma.blockchain import BLOCK_SCHEMA from duniterpy.api.client import Client @@ -55,21 +55,21 @@ WS_PEER_SCHEMA = { } -def block(client: Client) -> ClientWebSocketResponse: +def block(client: Client) -> _WSRequestContextManager: """ Connect to block websocket :param client: Client to connect to the api - :rtype: ClientWebSocketResponse + :return: """ return client.connect_ws(MODULE + '/block') -def peer(client: Client) -> ClientWebSocketResponse: +def peer(client: Client) -> _WSRequestContextManager: """ Connect to peer websocket :param client: Client to connect to the api - :rtype: ClientWebSocketResponse + :return: """ return client.connect_ws(MODULE + '/peer') diff --git a/duniterpy/api/client.py b/duniterpy/api/client.py index 9f534dd0f94ca021e1feb80a14f3698485e53810..cb6a924cfc9ff386206ccc507361289b2be97af5 100644 --- a/duniterpy/api/client.py +++ b/duniterpy/api/client.py @@ -7,8 +7,8 @@ import logging from typing import Callable, Union, Any, Optional import jsonschema -from aiohttp import ClientResponse, ClientWebSocketResponse, ClientSession - +from aiohttp import ClientResponse, ClientSession +from aiohttp.client import _WSRequestContextManager import duniterpy.api.endpoint as endpoint from .errors import DuniterError @@ -106,7 +106,7 @@ class API(object): :param scheme: Scheme of the url :param path: Path of the url - :return: str + :return: """ # remove starting slash in path if present path = path.lstrip('/') @@ -129,7 +129,7 @@ class API(object): Requests GET wrapper in order to use API parameters. :param path: the request path - :rtype: ClientResponse + :return: """ logging.debug("Request : {0}".format(self.reverse_url(self.connection_handler.http_scheme, path))) url = self.reverse_url(self.connection_handler.http_scheme, path) @@ -150,7 +150,7 @@ class API(object): Requests POST wrapper in order to use API parameters. :param path: the request path - :rtype: ClientResponse + :return: """ if 'self_' in kwargs: kwargs['self'] = kwargs.pop('self_') @@ -165,7 +165,7 @@ class API(object): ) return response - def connect_ws(self, path: str) -> ClientWebSocketResponse: + def connect_ws(self, path: str) -> _WSRequestContextManager: """ Connect to a websocket in order to use API parameters @@ -175,7 +175,7 @@ class API(object): and close the ClientWebSocketResponse in it. :param path: the url path - :rtype: ClientWebSocketResponse + :return: """ url = self.reverse_url(self.connection_handler.ws_scheme, path) return self.connection_handler.session.ws_connect(url, proxy=self.connection_handler.proxy) @@ -274,12 +274,12 @@ class Client: elif rtype == RESPONSE_JSON: return await response.json() - def connect_ws(self, path: str) -> ClientWebSocketResponse: + def connect_ws(self, path: str) -> _WSRequestContextManager: """ Connect to a websocket in order to use API parameters :param path: the url path - :rtype: ClientWebSocketResponse + :return: """ client = API(self.endpoint.conn_handler(self.session, self.proxy)) return client.connect_ws(path) diff --git a/duniterpy/api/endpoint.py b/duniterpy/api/endpoint.py index c83abbda261cc0c431892ac70b65ef4743ce0948..aab5f5c7b50845d0662d6b2085d15ab89b5f3abe 100644 --- a/duniterpy/api/endpoint.py +++ b/duniterpy/api/endpoint.py @@ -95,6 +95,13 @@ class UnknownEndpoint(Endpoint): return doc def conn_handler(self, session: ClientSession, proxy: str = None) -> ConnectionHandler: + """ + Return connection handler from session + + :param session: AIOHTTP Session + :param proxy: Proxy server + :return: + """ return ConnectionHandler("", "", "", 0, "", ClientSession()) def __str__(self) -> str: @@ -154,7 +161,7 @@ class BMAEndpoint(Endpoint): """ Return BMAEndpoint instance from endpoint string - :param inline: + :param inline: Endpoint string :return: """ m = BMAEndpoint.re_inline.match(inline) @@ -238,7 +245,7 @@ class SecuredBMAEndpoint(BMAEndpoint): """ Return SecuredBMAEndpoint instance from endpoint string - :param inline: + :param inline: Endpoint string :return: """ m = SecuredBMAEndpoint.re_inline.match(inline) @@ -300,6 +307,12 @@ class WS2PEndpoint(Endpoint): @classmethod def from_inline(cls: Type[WS2PEndpointType], inline: str) -> WS2PEndpointType: + """ + Return WS2PEndpoint instance from endpoint string + + :param inline: Endpoint string + :return: + """ m = WS2PEndpoint.re_inline.match(inline) if m is None: raise MalformedDocumentError(WS2PEndpoint.API) @@ -312,6 +325,11 @@ class WS2PEndpoint(Endpoint): return cls(ws2pid, server, port, path) def inline(self) -> str: + """ + Return endpoint string + + :return: + """ inlined = [str(info) for info in (self.ws2pid, self.server, self.port, self.path) if info] return WS2PEndpoint.API + " " + " ".join(inlined) @@ -319,9 +337,9 @@ class WS2PEndpoint(Endpoint): """ Return connection handler instance for the endpoint - :param aiohttp.ClientSession session: AIOHTTP client session instance - :param str proxy: Proxy url - :rtype: ConnectionHandler + :param session: AIOHTTP client session instance + :param proxy: Proxy url + :return: """ return ConnectionHandler("https", "wss", self.server, self.port, self.path, session, proxy) @@ -355,6 +373,12 @@ class ESCoreEndpoint(Endpoint): @classmethod def from_inline(cls: Type[ESCoreEndpointType], inline: str) -> ESCoreEndpointType: + """ + Return ESCoreEndpoint instance from endpoint string + + :param inline: Endpoint string + :return: + """ m = ESCoreEndpoint.re_inline.match(inline) if m is None: raise MalformedDocumentError(ESCoreEndpoint.API) @@ -363,6 +387,11 @@ class ESCoreEndpoint(Endpoint): return cls(server, port) def inline(self) -> str: + """ + Return endpoint string + + :return: + """ inlined = [str(info) for info in (self.server, self.port) if info] return ESCoreEndpoint.API + " " + " ".join(inlined) @@ -370,9 +399,9 @@ class ESCoreEndpoint(Endpoint): """ Return connection handler instance for the endpoint - :param aiohttp.ClientSession session: AIOHTTP client session instance - :param str proxy: Proxy url - :rtype: ConnectionHandler + :param session: AIOHTTP client session instance + :param proxy: Proxy url + :return: """ return ConnectionHandler("https", "wss", self.server, self.port, "", session, proxy) @@ -405,6 +434,12 @@ class ESUserEndpoint(Endpoint): @classmethod def from_inline(cls: Type[ESUserEndpointType], inline: str) -> ESUserEndpointType: + """ + Return ESUserEndpoint instance from endpoint string + + :param inline: Endpoint string + :return: + """ m = ESUserEndpoint.re_inline.match(inline) if m is None: raise MalformedDocumentError(ESUserEndpoint.API) @@ -413,6 +448,11 @@ class ESUserEndpoint(Endpoint): return cls(server, port) def inline(self) -> str: + """ + Return endpoint string + + :return: + """ inlined = [str(info) for info in (self.server, self.port) if info] return ESUserEndpoint.API + " " + " ".join(inlined) @@ -420,9 +460,9 @@ class ESUserEndpoint(Endpoint): """ Return connection handler instance for the endpoint - :param aiohttp.ClientSession session: AIOHTTP client session instance - :param str proxy: Proxy url - :rtype: ConnectionHandler + :param session: AIOHTTP client session instance + :param proxy: Proxy url + :return: """ return ConnectionHandler("https", "wss", self.server, self.port, "", session, proxy) @@ -455,6 +495,12 @@ class ESSubscribtionEndpoint(Endpoint): @classmethod def from_inline(cls: Type[ESSubscribtionEndpointType], inline: str) -> ESSubscribtionEndpointType: + """ + Return ESSubscribtionEndpoint instance from endpoint string + + :param inline: Endpoint string + :return: + """ m = ESSubscribtionEndpoint.re_inline.match(inline) if m is None: raise MalformedDocumentError(ESSubscribtionEndpoint.API) @@ -463,6 +509,11 @@ class ESSubscribtionEndpoint(Endpoint): return cls(server, port) def inline(self) -> str: + """ + Return endpoint string + + :return: + """ inlined = [str(info) for info in (self.server, self.port) if info] return ESSubscribtionEndpoint.API + " " + " ".join(inlined) @@ -470,9 +521,9 @@ class ESSubscribtionEndpoint(Endpoint): """ Return connection handler instance for the endpoint - :param aiohttp.ClientSession session: AIOHTTP client session instance - :param str proxy: Proxy url - :rtype: ConnectionHandler + :param session: AIOHTTP client session instance + :param proxy: Proxy url + :return: """ return ConnectionHandler("https", "wss", self.server, self.port, "", session, proxy) @@ -500,6 +551,12 @@ MANAGED_API = { def endpoint(value: Any) -> Any: + """ + Convert a endpoint string to the corresponding Endpoint instance type + + :param value: Endpoint string or subclass + :return: + """ if issubclass(type(value), Endpoint): return value elif isinstance(value, str): diff --git a/duniterpy/api/errors.py b/duniterpy/api/errors.py index 6ee8d15c55373b26e68aa8f824762df1bf31beff..9738dd415637a0254fedd737d296f8bb4a6a79fd 100644 --- a/duniterpy/api/errors.py +++ b/duniterpy/api/errors.py @@ -1,46 +1,51 @@ - class DuniterError(Exception): """ - duniter error + Handle duniter error """ - def __init__(self, data): + def __init__(self, data: dict) -> None: + """ + Init instance from Duniter data + + :param data: Error informations + """ super().__init__("Error code {0} - {1}".format(data["ucode"], data["message"])) self.ucode = data["ucode"] self.message = data["message"] -UNKNOWN = 1001 -UNHANDLED = 1002 -SIGNATURE_DOES_NOT_MATCH = 1003 -ALREADY_UP_TO_DATE = 1004 -WRONG_DOCUMENT = 1005 -HTTP_LIMITATION = 1006 -HTTP_PARAM_PUBKEY_REQUIRED = 1101 -HTTP_PARAM_IDENTITY_REQUIRED = 1102 -HTTP_PARAM_PEER_REQUIRED = 1103 -HTTP_PARAM_BLOCK_REQUIRED = 1104 -HTTP_PARAM_MEMBERSHIP_REQUIRED = 1105 -HTTP_PARAM_TX_REQUIRED = 1106 -HTTP_PARAM_SIG_REQUIRED = 1107 -HTTP_PARAM_CERT_REQUIRED = 1108 -HTTP_PARAM_REVOCATION_REQUIRED = 1109 -HTTP_PARAM_CONF_REQUIRED = 1110 +UNKNOWN = 1001 +UNHANDLED = 1002 +SIGNATURE_DOES_NOT_MATCH = 1003 +ALREADY_UP_TO_DATE = 1004 +WRONG_DOCUMENT = 1005 +HTTP_LIMITATION = 1006 + +HTTP_PARAM_PUBKEY_REQUIRED = 1101 +HTTP_PARAM_IDENTITY_REQUIRED = 1102 +HTTP_PARAM_PEER_REQUIRED = 1103 +HTTP_PARAM_BLOCK_REQUIRED = 1104 +HTTP_PARAM_MEMBERSHIP_REQUIRED = 1105 +HTTP_PARAM_TX_REQUIRED = 1106 +HTTP_PARAM_SIG_REQUIRED = 1107 +HTTP_PARAM_CERT_REQUIRED = 1108 +HTTP_PARAM_REVOCATION_REQUIRED = 1109 +HTTP_PARAM_CONF_REQUIRED = 1110 -NO_MATCHING_IDENTITY = 2001 -UID_ALREADY_USED = 2002 -PUBKEY_ALREADY_USED = 2003 -NO_MEMBER_MATCHING_PUB_OR_UID = 2004 -SELF_PEER_NOT_FOUND = 2005 -WRONG_SIGNATURE_MEMBERSHIP = 2006 -ALREADY_RECEIVED_MEMBERSHIP = 2007 +NO_MATCHING_IDENTITY = 2001 +UID_ALREADY_USED = 2002 +PUBKEY_ALREADY_USED = 2003 +NO_MEMBER_MATCHING_PUB_OR_UID = 2004 +SELF_PEER_NOT_FOUND = 2005 +WRONG_SIGNATURE_MEMBERSHIP = 2006 +ALREADY_RECEIVED_MEMBERSHIP = 2007 MEMBERSHIP_A_NON_MEMBER_CANNOT_LEAVE = 2008 -NOT_A_MEMBER = 2009 -NO_CURRENT_BLOCK = 2010 -BLOCK_NOT_FOUND = 2011 -PEER_NOT_FOUND = 2012 -WRONG_UNLOCKER = 2013 -LOCKTIME_PREVENT = 2014 -SOURCE_ALREADY_CONSUMED = 2015 -WRONG_AMOUNTS = 2016 -WRONG_OUTPUT_BASE = 2017 -CANNOT_ROOT_BLOCK_NO_MEMBERS = 2018 \ No newline at end of file +NOT_A_MEMBER = 2009 +NO_CURRENT_BLOCK = 2010 +BLOCK_NOT_FOUND = 2011 +PEER_NOT_FOUND = 2012 +WRONG_UNLOCKER = 2013 +LOCKTIME_PREVENT = 2014 +SOURCE_ALREADY_CONSUMED = 2015 +WRONG_AMOUNTS = 2016 +WRONG_OUTPUT_BASE = 2017 +CANNOT_ROOT_BLOCK_NO_MEMBERS = 2018 diff --git a/duniterpy/api/ws2p/network.py b/duniterpy/api/ws2p/network.py index e8b8b1ff776120636e7c50bd2d5038aee8d56aaf..b7fa58aff2f4d19730b8527633f9d21e1b61d494 100644 --- a/duniterpy/api/ws2p/network.py +++ b/duniterpy/api/ws2p/network.py @@ -54,13 +54,4 @@ WS2P_HEADS_SCHEMA = { "required": ["heads"] } - -# fixme: ws2p heads support must be handled by websocket -def heads(client: Client): - """ - GET Certification data over a member - - :param client: Client to connect to the api - :rtype: dict - """ - return client.get(MODULE + '/ws2p/heads', schema=WS2P_HEADS_SCHEMA) +# todo: support ws2p v1 api diff --git a/duniterpy/documents/block.py b/duniterpy/documents/block.py index 9487c495a1da6363a0bff96cadc6a47d34625fff..229c343a39ed3002f2d21919d98d258a940ff69b 100644 --- a/duniterpy/documents/block.py +++ b/duniterpy/documents/block.py @@ -1,6 +1,7 @@ import base64 import hashlib import re +from typing import Union, TypeVar, Type, Optional, List from .certification import Identity, Certification, Revocation from .document import Document, MalformedDocumentError @@ -8,16 +9,8 @@ from .membership import Membership from .transaction import Transaction from ..constants import PUBKEY_REGEX, BLOCK_ID_REGEX, BLOCK_HASH_REGEX - -def block_uid(value): - if isinstance(value, BlockUID): - return value - elif isinstance(value, str): - return BlockUID.from_str(value) - elif value is None: - return BlockUID.empty() - else: - raise TypeError("Cannot convert {0} to BlockUID".format(type(value))) +# required to type hint cls in classmethod +BlockUIDType = TypeVar('BlockUIDType', bound='BlockUID') class BlockUID: @@ -28,20 +21,20 @@ class BlockUID: block_hash_regex=BLOCK_HASH_REGEX)) re_hash = re.compile("({block_hash_regex})".format(block_hash_regex=BLOCK_HASH_REGEX)) - @classmethod - def empty(cls): - return cls(0, Block.Empty_Hash) - - def __init__(self, number, sha_hash): + def __init__(self, number: int, sha_hash: str) -> None: assert (type(number) is int) assert (BlockUID.re_hash.match(sha_hash) is not None) self.number = number self.sha_hash = sha_hash @classmethod - def from_str(cls, blockid): + def empty(cls: Type[BlockUIDType]) -> BlockUIDType: + return cls(0, Block.Empty_Hash) + + @classmethod + def from_str(cls: Type[BlockUIDType], blockid: str) -> BlockUIDType: """ - :param str blockid: The block id + :param blockid: The block id """ data = BlockUID.re_block_uid.match(blockid) try: @@ -56,31 +49,52 @@ class BlockUID: return cls(number, sha_hash) - def __str__(self): + def __str__(self) -> str: return "{0}-{1}".format(self.number, self.sha_hash) - def __eq__(self, other): + def __eq__(self, other: Type[BlockUIDType]) -> bool: return self.number == other.number and self.sha_hash == other.sha_hash - def __lt__(self, other): + def __lt__(self, other: Type[BlockUIDType]) -> bool: return self.number < other.number - def __gt__(self, other): + def __gt__(self, other: Type[BlockUIDType]) -> bool: return self.number > other.number - def __le__(self, other): + def __le__(self, other: Type[BlockUIDType]) -> bool: return self.number <= other.number - def __ge__(self, other): + def __ge__(self, other: Type[BlockUIDType]) -> bool: return self.number >= other.number - def __hash__(self): + def __hash__(self) -> int: return hash((self.number, self.sha_hash)) - def __bool__(self): + def __bool__(self) -> bool: return self != BlockUID.empty() +def block_uid(value: Union[str, BlockUID, None]) -> BlockUID: + """ + Convert value to BlockUID instance + + :param value: Value to convert + :return: + """ + if isinstance(value, BlockUID): + return value + elif isinstance(value, str): + return BlockUID.from_str(value) + elif value is None: + return BlockUID.empty() + else: + raise TypeError("Cannot convert {0} to BlockUID".format(type(value))) + + +# required to type hint cls in classmethod +BlockType = TypeVar('BlockType', bound='Block') + + class Block(Document): """ The class Block handles Block documents. @@ -183,47 +197,70 @@ The class Block handles Block documents. 'Transactions': re_transactions, 'InnerHash': re_hash, 'Noonce': re_noonce, - } } + } Empty_Hash = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" - def __init__(self, version, currency, number, powmin, time, - mediantime, ud, unit_base, issuer, issuers_frame, issuers_frame_var, - different_issuers_count, prev_hash, prev_issuer, - parameters, members_count, identities, joiners, - actives, leavers, revokations, excluded, certifications, - transactions, inner_hash, noonce, signature): + def __init__(self, + version: int, + currency: str, + number: int, + powmin: int, + time: int, + mediantime: int, + ud: Optional[int], + unit_base: int, + issuer: str, + issuers_frame: int, + issuers_frame_var: int, + different_issuers_count: int, + prev_hash: str, + prev_issuer: str, + parameters: str, + members_count: int, + identities: List[Identity], + joiners: List[Membership], + actives: List[Membership], + leavers: List[Membership], + revokations: List[Revocation], + excluded: List[str], + certifications: List[Certification], + transactions: List[Transaction], + inner_hash: str, + noonce: int, + signature: str + ) -> None: """ Constructor - :param int version: duniter protocol version - :param str currency: the block currency - :param int number: the number of the block - :param int powmin: the powmin value of this block - :param int time: the timestamp of this block - :param int ud: the dividend amount, or None if no dividend present in this block - :param int unit_base: the unit_base of the dividend, or None if no dividend present in this block - :param str issuer: the pubkey of the issuer of the block - :param int issuers_frame: - :param int issuers_frame_var: - :param int different_issuers_count: the count of issuers - :param str prev_hash: the previous block hash - :param str prev_issuer: the previous block issuer - :param Optional[Sequence[str]] parameters: the parameters of the currency. Should only be present in block 0. - :param int members_count: the number of members found in this block - :param list[duniterpy.documents.Identity] identities: the self certifications declared in this block - :param list[duniterpy.documents.Membership] joiners: the joiners memberships via "IN" documents - :param list[duniterpy.documents.Membership] actives: renewed memberships via "IN" documents - :param list[duniterpy.documents.Membership] leavers: the leavers memberships via "OUT" documents - :param list[duniterpy.documents.Revocation] revokations: revokations - :param list[str] excluded: members excluded because of missing certifications - :param list[duniterpy.documents.Membership] actives: renewed memberships via "IN" documents - :param list[duniterpy.documents.Certification] certifications: certifications documents - :param list[duniterpy.documents.Transaction] transactions: transactions documents - :param str inner_hash: the block hah - :param int noonce: the noonce value of the block - :param str signature: the block signature + :param version: duniter protocol version + :param currency: the block currency + :param number: the number of the block + :param powmin: the powmin value of this block + :param time: the timestamp of this block + :param mediantime: the timestamp of the median time of this block + :param ud: the dividend amount, or None if no dividend present in this block + :param unit_base: the unit_base of the dividend, or None if no dividend present in this block + :param issuer: the pubkey of the issuer of the block + :param issuers_frame: + :param issuers_frame_var: + :param different_issuers_count: the count of issuers + :param prev_hash: the previous block hash + :param prev_issuer: the previous block issuer + :param parameters: the parameters of the currency. Should only be present in block 0. + :param members_count: the number of members found in this block + :param identities: the self certifications declared in this block + :param joiners: the joiners memberships via "IN" documents + :param actives: renewed memberships via "IN" documents + :param leavers: the leavers memberships via "OUT" documents + :param revokations: revokations + :param excluded: members excluded because of missing certifications + :param certifications: certifications documents + :param transactions: transactions documents + :param inner_hash: the block hah + :param noonce: the noonce value of the block + :param signature: the block signature """ super().__init__(version, currency, [signature]) documents_versions = max(max([1] + [i.version for i in identities]), @@ -260,11 +297,11 @@ The class Block handles Block documents. self.noonce = noonce @property - def blockUID(self): + def blockUID(self) -> BlockUIDType: return BlockUID(self.number, self.proof_of_work()) @classmethod - def from_signed_raw(cls, signed_raw): + def from_signed_raw(cls: Type[BlockType], signed_raw: str) -> BlockType: lines = signed_raw.splitlines(True) n = 0 @@ -437,7 +474,7 @@ The class Block handles Block documents. actives, leavers, revoked, excluded, certifications, transactions, inner_hash, noonce, signature) - def raw(self): + def raw(self) -> str: doc = """Version: {version} Type: Block Currency: {currency} @@ -512,14 +549,14 @@ PreviousIssuer: {1}\n".format(self.prev_hash, self.prev_issuer) return doc - def proof_of_work(self): + def proof_of_work(self) -> str: doc_str = """InnerHash: {inner_hash} Nonce: {nonce} {signature} """.format(inner_hash=self.inner_hash, nonce=self.noonce, signature=self.signatures[0]) return hashlib.sha256(doc_str.encode('ascii')).hexdigest().upper() - def computed_inner_hash(self): + def computed_inner_hash(self) -> str: doc = self.signed_raw() inner_doc = '\n'.join(doc.split('\n')[:-2]) + '\n' return hashlib.sha256(inner_doc.encode("ascii")).hexdigest().upper() @@ -534,17 +571,17 @@ Nonce: {nonce} signing = base64.b64encode(key.signature(bytes(signed, 'ascii'))) self.signatures = [signing.decode("ascii")] - def __eq__(self, other): + def __eq__(self, other: Type[BlockType]) -> bool: return self.blockUID == other.blockUID - def __lt__(self, other): + def __lt__(self, other: Type[BlockType]) -> bool: return self.blockUID < other.blockUID - def __gt__(self, other): + def __gt__(self, other: Type[BlockType]) -> bool: return self.blockUID > other.blockUID - def __le__(self, other): + def __le__(self, other: Type[BlockType]) -> bool: return self.blockUID <= other.blockUID - def __ge__(self, other): + def __ge__(self, other: Type[BlockType]) -> bool: return self.blockUID >= other.blockUID diff --git a/duniterpy/documents/ws2p/heads.py b/duniterpy/documents/ws2p/heads.py index 0483a74b390a7516622f1222c2b72ba5f7d611d5..1f63d90b0e408c71cbb39606d16b3deb064a670d 100644 --- a/duniterpy/documents/ws2p/heads.py +++ b/duniterpy/documents/ws2p/heads.py @@ -2,8 +2,8 @@ import re import attr -from ..document import MalformedDocumentError from ..block import BlockUID +from ..document import MalformedDocumentError from ...constants import WS2P_PUBLIC_PREFIX_REGEX, WS2P_PRIVATE_PREFIX_REGEX, WS2P_HEAD_REGEX, \ PUBKEY_REGEX, SIGNATURE_REGEX, WS2PID_REGEX, BLOCK_UID_REGEX @@ -18,21 +18,15 @@ class API: ws2p_public=WS2P_PUBLIC_PREFIX_REGEX)) @classmethod - def from_inline(cls, inline): + def from_inline(cls, inline: str): data = API.re_inline.match(inline) - if data.group(1): - private = data.group(1) - else: - private = "" - - if data.group(2): - public = data.group(2) - else: - public = "" - + if data is None: + raise MalformedDocumentError("WS2P API Document") + private = data.group(1) + public = data.group(2) return cls(private, public) - def __str__(self): + def __str__(self) -> str: return "WS2P" + self.private + self.public @@ -43,19 +37,18 @@ class Head: re_inline = re.compile(WS2P_HEAD_REGEX) @classmethod - def from_inline(cls, inline): + def from_inline(cls, inline: str): try: data = Head.re_inline.match(inline) + if data is None: + raise MalformedDocumentError("Head") head = data.group(0).split(':') - if len(head) == 2: - version = int(head[1]) - else: - version = 0 + version = int(head[1]) if len(head) == 2 else 0 return cls(version) except AttributeError: raise MalformedDocumentError("Head") - def __str__(self): + def __str__(self) -> str: return "HEAD" if self.version == 0 else "HEAD:{}".format(str(self.version)) @@ -81,9 +74,11 @@ class HeadV0: re_signature = re.compile(SIGNATURE_REGEX) @classmethod - def from_inline(cls, inline, signature): + def from_inline(cls, inline: str, signature: str): try: data = HeadV0.re_inline.match(inline) + if data is None: + raise MalformedDocumentError("HeadV0") api = API.from_inline(data.group(1)) head = Head.from_inline(data.group(2)) pubkey = data.group(3) @@ -93,7 +88,7 @@ class HeadV0: except AttributeError: raise MalformedDocumentError("HeadV0") - def inline(self): + def inline(self) -> str: values = (str(v) for v in attr.astuple(self, recurse=False, filter=attr.filters.exclude(attr.fields(HeadV0).signature))) return ":".join(values) @@ -114,10 +109,12 @@ class HeadV1: pow_prefix="[0-9]+")) @classmethod - def from_inline(cls, inline, signature): + def from_inline(cls, inline: str, signature: str): try: v0, offload = HeadV0.from_inline(inline, signature) data = HeadV1.re_inline.match(offload) + if data is None: + raise MalformedDocumentError("HeadV1") ws2pid = data.group(1) software = data.group(2) software_version = data.group(3) @@ -127,20 +124,20 @@ class HeadV1: except AttributeError: raise MalformedDocumentError("HeadV1") - def inline(self): + def inline(self) -> str: values = [str(v) for v in attr.astuple(self, True, filter=attr.filters.exclude(attr.fields(HeadV1).v0))] return self.v0.inline() + ":" + ":".join(values) @property - def pubkey(self): + def pubkey(self) -> str: return self.v0.pubkey @property - def signature(self): + def signature(self) -> str: return self.v0.signature @property - def blockstamp(self): + def blockstamp(self) -> BlockUID: return self.v0.blockstamp @@ -155,28 +152,30 @@ class HeadV2: free_mirror_room="[0-9]+")) @classmethod - def from_inline(cls, inline, signature): + def from_inline(cls, inline: str, signature: str): try: v1, offload = HeadV1.from_inline(inline, signature) data = HeadV2.re_inline.match(offload) + if data is None: + raise MalformedDocumentError("HeadV2") free_member_room = int(data.group(1)) free_mirror_room = int(data.group(2)) return cls(v1, free_member_room, free_mirror_room), "" except AttributeError: raise MalformedDocumentError("HeadV2") - def inline(self): + def inline(self) -> str: values = (str(v) for v in attr.astuple(self, True, filter=attr.filters.exclude(attr.fields(HeadV2).v1))) return self.v1.inline() + ":" + ":".join(values) @property - def pubkey(self): + def pubkey(self) -> str: return self.v1.pubkey @property - def signature(self): + def signature(self) -> str: return self.v1.signature @property - def blockstamp(self): + def blockstamp(self) -> BlockUID: return self.v1.blockstamp diff --git a/release.sh b/release.sh old mode 100755 new mode 100644