Commit e118a6fb authored by Vincent Texier's avatar Vincent Texier

issue #52 add type hinting everywhere in the code (except on local variables)

parent 31d06c61
File mode changed from 100755 to 100644
File mode changed from 100644 to 100755
......@@ -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)
......@@ -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)
......@@ -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",
......
......@@ -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)
......@@ -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)
......@@ -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)
......@@ -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')
......@@ -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)
......
......@@ -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):
......
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
......@@ -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
This diff is collapsed.
......@@ -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: