Skip to content
Snippets Groups Projects
Commit 5bbb5c55 authored by Vincent Texier's avatar Vincent Texier
Browse files

Refactor api : module classes become static methods with explicit keyword args (work in progress)

Example working: request_data.py
parent 9af35190
No related branches found
No related tags found
1 merge request!31Dev : refactor api with static methods
......@@ -19,7 +19,7 @@
import aiohttp, json, logging, jsonschema
import warnings
from ..errors import DuniterError
logger = logging.getLogger("duniter")
......@@ -28,15 +28,20 @@ logger = logging.getLogger("duniter")
class ConnectionHandler(object):
"""Helper class used by other API classes to ease passing server connection information."""
def __init__(self, server, port):
"""
Arguments:
- `server`: server hostname
- `port`: port number
def __init__(self, server, port, session=None):
"""
Init instance of connection handler
:param str server: Server IP or domaine name
:param int port: Port
:param aiohttp.ClientSession|None session: Session AIOHTTP
"""
self.server = server
self.port = port
if session is None:
self.session = aiohttp.ClientSession()
else:
self.session = session
def __str__(self):
return 'connection info: %s:%d' % (self.server, self.port)
......@@ -45,6 +50,7 @@ class ConnectionHandler(object):
class API(object):
"""APIRequest is a class used as an interface. The intermediate derivated classes are the modules and the leaf classes are the API requests."""
schema = {}
error_schema = {
"type": "object",
"properties": {
......@@ -62,21 +68,20 @@ class API(object):
"""
Asks a module in order to create the url used then by derivated classes.
Arguments:
- `module`: module name
- `connection_handler`: connection handler
:param ConnectionHandler connection_handler: Connection handler
:param str module: Module path
"""
self.module = module
self.connection_handler = connection_handler
self.headers = {}
def reverse_url(self, scheme, path):
"""
Reverses the url using self.url and path given in parameter.
Reverses the url using scheme and path given in parameter.
Arguments:
- `path`: the request path
:param str scheme: Scheme of the url
:param str path: Path of the url
:return: str
"""
server, port = self.connection_handler.server, self.connection_handler.port
......@@ -87,30 +92,6 @@ class API(object):
module=self.module)
return url + path
def get(self, session, **kwargs):
"""wrapper of overloaded __get__ method."""
return self.__get__(session, **kwargs)
def post(self, session, **kwargs):
"""wrapper of overloaded __post__ method."""
logger.debug('do some work with')
data = self.__post__(session, **kwargs)
logger.debug('and send back')
return data
async def __get__(self, session, **kwargs):
"""interface purpose for GET request"""
pass
async def __post__(self, session, **kwargs):
"""interface purpose for POST request"""
pass
def parse_text(self, text):
"""
Validate and parse the BMA answer from websocket
......@@ -139,30 +120,31 @@ class API(object):
except (TypeError, json.decoder.JSONDecodeError):
raise jsonschema.ValidationError("Could not parse json")
async def parse_response(self, response):
async def parse_response(self, response, schema=None):
"""
Validate and parse the BMA answer
:param response:
:param aiohttp.ClientResponse response: Response of aiohttp request
:param dict schema: The expected response structure
:return: the json data
"""
try:
data = await response.json()
jsonschema.validate(data, self.schema)
if schema is not None:
jsonschema.validate(data, schema)
return data
except (TypeError, json.decoder.JSONDecodeError):
raise jsonschema.ValidationError("Could not parse json")
async def requests_get(self, session, path, **kwargs):
async def requests_get(self, path, **kwargs):
"""
Requests GET wrapper in order to use API parameters.
:params aiohttp.ClientSession session: the client session
:params str path: the request path
"""
logging.debug("Request : {0}".format(self.reverse_url("http", path)))
with aiohttp.Timeout(15):
response = await session.get(self.reverse_url("http", path), params=kwargs,headers=self.headers)
response = await self.connection_handler.session.get(self.reverse_url("http", path), params=kwargs,headers=self.headers)
if response.status != 200:
try:
error_data = self.parse_error(await response.text())
......@@ -172,11 +154,10 @@ class API(object):
return response
async def requests_post(self, session, path, **kwargs):
async def requests_post(self, path, **kwargs):
"""
Requests POST wrapper in order to use API parameters.
:param aiohttp.ClientSession session: the request session
:param str path: the request path
"""
if 'self_' in kwargs:
......@@ -184,7 +165,11 @@ class API(object):
logging.debug("POST : {0}".format(kwargs))
with aiohttp.Timeout(15):
response = await session.post(self.reverse_url("http", path), data=kwargs, headers=self.headers)
response = await self.connection_handler.session.post(
self.reverse_url("http", path),
data=kwargs,
headers=self.headers
)
return response
def connect_ws(self, session, path):
......
......@@ -20,143 +20,9 @@ from duniterpy.api.bma import API, logging
logger = logging.getLogger("duniter/blockchain")
URL_PATH = 'blockchain'
class Blockchain(API):
def __init__(self, connection_handler, module='blockchain'):
super(Blockchain, self).__init__(connection_handler, module)
class Parameters(Blockchain):
"""GET the blockchain parameters used by this node."""
schema = {
"type": "object",
"properties":
{
"currency": {
"type": "string"
},
"c": {
"type": "number"
},
"dt": {
"type": "number"
},
"ud0": {
"type": "number"
},
"sigPeriod": {
"type": "number"
},
"sigStock": {
"type": "number"
},
"sigWindow": {
"type": "number"
},
"sigValidity": {
"type": "number"
},
"sigQty": {
"type": "number"
},
"xpercent": {
"type": "number"
},
"msValidity": {
"type": "number"
},
"stepMax": {
"type": "number"
},
"medianTimeBlocks": {
"type": "number"
},
"avgGenTime": {
"type": "number"
},
"dtDiffEval": {
"type": "number"
},
"blocksRot": {
"type": "number"
},
"percentRot": {
"type": "number"
},
},
"required": ["currency", "c", "dt", "ud0","sigPeriod", "sigValidity", "sigQty", "xpercent", "sigStock",
"sigWindow", "msValidity","stepMax", "medianTimeBlocks",
"avgGenTime", "dtDiffEval", "blocksRot", "percentRot"]
}
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/parameters', **kwargs)
return (await self.parse_response(r))
class Membership(Blockchain):
"""GET/POST a Membership document."""
schema = {
"type": "object",
"properties":
{
"pubkey": {
"type": "string"
},
"uid": {
"type": "string",
},
"sigDate": {
"type": "string"
},
"memberships": {
"type": "array",
"items": {
"type": "object",
"properties": {
"version": {
"type": "number"
},
"currency": {
"type": "string"
},
"membership": {
"type": "string"
},
"blockNumber": {
"type": "number"
},
"written": {
"type": ["number", "null"]
}
},
"required": ["version", "currency", "membership", "blockNumber", "blockHash", "written"]
}
}
},
"required": ["pubkey", "uid", "sigDate", "memberships"]
}
def __init__(self, connection_handler, search=None):
super().__init__(connection_handler)
self.search = search
async def __post__(self, session, **kwargs):
assert 'membership' in kwargs
r = await self.requests_post(session, '/membership', **kwargs)
return r
async def __get__(self, session, **kwargs):
assert self.search is not None
r = await self.requests_get(session, '/memberships/%s' % self.search, **kwargs)
return (await self.parse_response(r))
class Block(Blockchain):
"""GET/POST a block from/to the blockchain."""
schema = {
BLOCK_SCHEMA = {
"type": "object",
"properties": {
"version": {
......@@ -288,72 +154,217 @@ class Block(Blockchain):
"joiners", "leavers", "excluded", "certifications", "transactions", "signature"]
}
def __init__(self, connection_handler, number=None):
BLOCK_NUMBERS_SCHEMA = {
"type": "object",
"properties": {
"result": {
"type": "object",
"properties": {
"blocks": {
"type": "array",
"items": {
"type": "number"
}
},
},
"required": ["blocks"]
}
},
"required": ["result"]
}
async def parameters(connection):
"""
GET the blockchain parameters used by this node
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
schema = {
"type": "object",
"properties":
{
"currency": {
"type": "string"
},
"c": {
"type": "number"
},
"dt": {
"type": "number"
},
"ud0": {
"type": "number"
},
"sigPeriod": {
"type": "number"
},
"sigStock": {
"type": "number"
},
"sigWindow": {
"type": "number"
},
"sigValidity": {
"type": "number"
},
"sigQty": {
"type": "number"
},
"xpercent": {
"type": "number"
},
"msValidity": {
"type": "number"
},
"stepMax": {
"type": "number"
},
"medianTimeBlocks": {
"type": "number"
},
"avgGenTime": {
"type": "number"
},
"dtDiffEval": {
"type": "number"
},
"blocksRot": {
"type": "number"
},
"percentRot": {
"type": "number"
},
},
"required": ["currency", "c", "dt", "ud0","sigPeriod", "sigValidity", "sigQty", "xpercent", "sigStock",
"sigWindow", "msValidity","stepMax", "medianTimeBlocks",
"avgGenTime", "dtDiffEval", "blocksRot", "percentRot"]
}
client = API(connection, URL_PATH)
r = await client.requests_get('/parameters')
return await client.parse_response(r, schema)
async def membership(connection, **kwargs):
"""
Use the number parameter in order to select a block number.
GET/POST a Membership document
Arguments:
- `number`: block number to select
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
schema = {
"type": "object",
"properties":
{
"pubkey": {
"type": "string"
},
"uid": {
"type": "string",
},
"sigDate": {
"type": "string"
},
"memberships": {
"type": "array",
"items": {
"type": "object",
"properties": {
"version": {
"type": "number"
},
"currency": {
"type": "string"
},
"membership": {
"type": "string"
},
"blockNumber": {
"type": "number"
},
"written": {
"type": ["number", "null"]
}
},
"required": ["version", "currency", "membership", "blockNumber", "blockHash", "written"]
}
}
},
"required": ["pubkey", "uid", "sigDate", "memberships"]
}
super(Block, self).__init__(connection_handler)
client = API(connection, URL_PATH)
if 'membership' in kwargs:
r = await client.requests_post('/membership')
else:
r = await client.requests_get('/memberships/%s' % kwargs[0])
self.number = number
return await client.parse_response(r, schema)
async def __get__(self, session, **kwargs):
assert self.number is not None
r = await self.requests_get(session, '/block/%d' % self.number, **kwargs)
return (await self.parse_response(r))
async def block(connection, number=0, block=None, signature=None):
"""
GET/POST a block from/to the blockchain
async def __post__(self, session, **kwargs):
assert 'block' in kwargs
assert 'signature' in kwargs
:param API.ConnectionHandler connection: Connection handler instance
:param int number: Block number to get
:param dict block: Block document to post
:param str signature: Signature of the block document issuer
:return: dict
"""
r = await self.requests_post(session, '/block', **kwargs)
client = API(connection, URL_PATH)
# POST block
if block is not None and signature is not None:
r = await client.requests_post('/block', block=block, signature=signature)
return r
# GET block
r = await client.requests_get('/block/%d' % number)
return await client.parse_response(r, BLOCK_SCHEMA)
class Current(Blockchain):
"""GET, same as block/[number], but return last accepted block."""
async def current(connection):
"""
GET, return last accepted block
schema = Block.schema
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/current', **kwargs)
return (await self.parse_response(r))
client = API(connection, URL_PATH)
r = await client.requests_get('/current')
return await client.parse_response(r, BLOCK_SCHEMA)
class Blocks(Blockchain):
"""GET list of blocks from the blockchain."""
async def blocks(connection, count, start):
"""
GET list of blocks from the blockchain
:param API.ConnectionHandler connection: Connection handler instance
:param int count: Number of blocks
:param int start: First block number
:return: list
"""
schema = {
"type": "array",
"items": Block.schema
"items": BLOCK_SCHEMA
}
def __init__(self, connection_handler, count=None, from_=None):
"""
Use the number parameter in order to select a block number.
client = API(connection, URL_PATH)
assert count is int
assert start is int
r = await client.requests_get('/blocks/%d/%d' % (count, start))
return await client.parse_response(r, schema)
Arguments:
- `count`: block count to select
- `from_`: first block to select
async def hardship(connection, pubkey):
"""
GET hardship level for given member's public key for writing next block
super(Blocks, self).__init__(connection_handler)
self.count = count
self.from_ = from_
async def __get__(self, session, **kwargs):
assert self.count is not None
assert self.from_ is not None
r = await self.requests_get(session, '/blocks/%d/%d' % self.count, self.from_, **kwargs)
return (await self.parse_response(r))
class Hardship(Blockchain):
"""GET hardship level for given member's fingerprint for writing next block."""
:param API.ConnectionHandler connection: Connection handler instance
:param str pubkey: Public key of the member
:return: dict
"""
schema = {
"type": "object",
"properties": {
......@@ -367,116 +378,101 @@ class Hardship(Blockchain):
"required": ["block", "level"]
}
def __init__(self, connection_handler, fingerprint):
"""
Use the number parameter in order to select a block number.
client = API(connection, URL_PATH)
r = await client.requests_get('/hardship/%s' % pubkey)
return await client.parse_response(r, schema)
Arguments:
- `fingerprint`: member fingerprint
async def newcomers(connection):
"""
GET, return block numbers containing newcomers
super(Hardship, self).__init__(connection_handler)
self.fingerprint = fingerprint
async def __get__(self, session, **kwargs):
assert self.fingerprint is not None
r = await self.requests_get(session, '/hardship/%s' % self.fingerprint.upper(), **kwargs)
return (await self.parse_response(r))
class Newcomers(Blockchain):
"""GET, return block numbers containing newcomers."""
schema = {
"type": "object",
"properties": {
"result":{
"type": "object",
"properties": {
"blocks": {
"type": "array",
"items": {
"type": "number"
}
},
},
"required": ["blocks"]
}
},
"required": ["result"]
}
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/newcomers', **kwargs)
return await self.parse_response(r)
class Certifications(Blockchain):
"""GET, return block numbers containing certifications."""
schema = Newcomers.schema
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/certs', **kwargs)
return await self.parse_response(r)
class Joiners(Blockchain):
"""GET, return block numbers containing joiners."""
schema = Newcomers.schema
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/joiners', **kwargs)
return await self.parse_response(r)
client = API(connection, URL_PATH)
r = await client.requests_get('/with/newcomers')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
async def certifications(connection):
"""
GET, return block numbers containing certifications
class Actives(Blockchain):
"""GET, return block numbers containing actives."""
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
schema = Newcomers.schema
client = API(connection, URL_PATH)
r = await client.requests_get('/with/certs')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/actives', **kwargs)
return await self.parse_response(r)
async def joiners(connection):
"""
GET, return block numbers containing joiners
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
class Leavers(Blockchain):
"""GET, return block numbers containing leavers."""
client = API(connection, URL_PATH)
r = await client.requests_get('/with/joiners')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
schema = Newcomers.schema
async def actives(connection):
"""
GET, return block numbers containing actives
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/leavers', **kwargs)
return await self.parse_response(r)
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
client = API(connection, URL_PATH)
r = await client.requests_get('/with/actives')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
class Excluded(Blockchain):
"""GET, return block numbers containing excluded."""
async def leavers(connection):
"""
GET, return block numbers containing leavers
schema = Newcomers.schema
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/excluded', **kwargs)
return await self.parse_response(r)
client = API(connection, URL_PATH)
r = await client.requests_get('/with/leavers')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
async def excluded(connection):
"""
GET, return block numbers containing excluded
class UD(Blockchain):
"""GET, return block numbers containing universal dividend."""
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
schema = Newcomers.schema
client = API(connection, URL_PATH)
r = await client.requests_get('/with/excluded')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/ud', **kwargs)
return await self.parse_response(r)
async def ud(connection):
"""
GET, return block numbers containing universal dividend
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
client = API(connection, URL_PATH)
r = await client.requests_get('/with/ud')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
class TX(Blockchain):
"""GET, return block numbers containing transactions."""
async def tx(connection):
"""
GET, return block numbers containing transactions
schema = Newcomers.schema
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/with/tx', **kwargs)
return await self.parse_response(r)
client = API(connection, URL_PATH)
r = await client.requests_get('/with/tx')
return await client.parse_response(r, BLOCK_NUMBERS_SCHEMA)
......@@ -20,14 +20,15 @@ from duniterpy.api.bma import API, logging
logger = logging.getLogger("duniter/node")
URL_PATH = 'node'
class Node(API):
def __init__(self, connection_handler, module='node'):
super(Node, self).__init__(connection_handler, module)
async def summary(connection):
"""
GET Certification data over a member
class Summary(Node):
"""GET Certification data over a member."""
:param API.ConnectionHandler connection: Connection handler instance
:return: dict
"""
schema = {
"type": "object",
"properties": {
......@@ -49,11 +50,6 @@ class Summary(Node):
},
"required": ["duniter"]
}
def __init__(self, connection_handler, module='node'):
super(Summary, self).__init__(connection_handler, module)
async def __get__(self, session, **kwargs):
r = await self.requests_get(session, '/summary', **kwargs)
return await self.parse_response(r)
client = API(connection, URL_PATH)
r = await client.requests_get('/summary')
return await client.parse_response(r, schema)
......@@ -17,8 +17,7 @@
#
from duniterpy.api.bma import API, logging
from duniterpy.api.bma.blockchain import Block as _Block
from duniterpy.api.bma.network.peering import Peers as _Peers
from duniterpy.api.bma.blockchain import BLOCK_SCHEMA
logger = logging.getLogger("duniter/ws")
......@@ -30,7 +29,7 @@ class Websocket(API):
class Block(Websocket):
"""Connect to block websocket."""
schema = _Block.schema
schema = BLOCK_SCHEMA
def connect(self, session):
r = self.connect_ws(session, '/block')
......
......@@ -6,6 +6,7 @@ from . import BlockUID
from .. import PROTOCOL_VERSION, MANAGED_API
from .constants import block_hash_regex, pubkey_regex
class Peer(Document):
"""
.. note:: A peer document is specified by the following format :
......@@ -177,13 +178,13 @@ class BMAEndpoint(Endpoint):
IPv6=(" {0}".format(self.ipv6) if self.ipv6 else ""),
PORT=(" {0}".format(self.port) if self.port else ""))
def conn_handler(self):
def conn_handler(self, session=None):
if self.server:
return ConnectionHandler(self.server, self.port)
return ConnectionHandler(self.server, self.port, session)
elif self.ipv4:
return ConnectionHandler(self.ipv4, self.port)
return ConnectionHandler(self.ipv4, self.port, session)
else:
return ConnectionHandler("[{0}]".format(self.ipv6), self.port)
return ConnectionHandler("[{0}]".format(self.ipv6), self.port, session)
def __str__(self):
return self.inline()
......
......@@ -21,18 +21,22 @@ async def main():
Main code
"""
# connection handler from BMA endpoint
connection = BMAEndpoint.from_inline(BMA_ENDPOINT).conn_handler()
connection = BMAEndpoint.from_inline(BMA_ENDPOINT).conn_handler(AIOHTTP_SESSION)
# Get the node summary infos
response = await bma.node.Summary(connection).get(AIOHTTP_SESSION)
response = await bma.node.summary(connection)
print(response)
# Get the current block data
response = await bma.blockchain.Current(connection).get(AIOHTTP_SESSION)
response = await bma.blockchain.parameters(connection)
print(response)
# Get the current block data
response = await bma.blockchain.current(connection)
print(response)
# Get the block number 0
response = await bma.blockchain.Block(connection, 0).get(AIOHTTP_SESSION)
response = await bma.blockchain.block(connection, number=10)
print(response)
with AIOHTTP_SESSION:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment