-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[explorer/rest]: init setup rest endpoint for explorer
Add a new repo for Explorer/REST - Added ci script - Added Facade to process db for block and blocks. - Added DB connection and connect to nem db - Added Flask to handle endpoint response for block and blocks.
- Loading branch information
1 parent
f7724fa
commit 56cf5b8
Showing
31 changed files
with
872 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[run] | ||
source = rest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
defaultCiPipeline { | ||
operatingSystem = ['ubuntu'] | ||
instanceSize = 'medium' | ||
|
||
ciBuildDockerfile = 'postgres.Dockerfile' | ||
|
||
packageId = 'explorer-rest' | ||
|
||
codeCoverageTool = 'coverage' | ||
minimumCodeCoverage = 95 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pytest==7.2.2 | ||
pytest-aiohttp==1.0.4 | ||
coverage>=6.3 | ||
testing.postgresql==1.3.0 | ||
psycopg2-binary==2.9.6 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
aiohttp==3.8.4 | ||
Flask==2.2.3 | ||
psycopg2-binary==2.9.6 | ||
symbol-sdk-python==3.0.11 | ||
zenlog==1.1 | ||
Path==16.6.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import configparser | ||
from collections import namedtuple | ||
from pathlib import Path | ||
|
||
from flask import Flask, abort, jsonify, request | ||
from zenlog import log | ||
|
||
from rest.facade.NemRestFacade import NemRestFacade | ||
|
||
DatabaseConfig = namedtuple('DatabaseConfig', ['database', 'user', 'password', 'host', 'port']) | ||
|
||
|
||
def create_app(): | ||
app = Flask(__name__) | ||
|
||
setup_error_handlers(app) | ||
|
||
nem_api_facade = setup_nem_facade(app) | ||
setup_nem_routes(app, nem_api_facade) | ||
|
||
return app | ||
|
||
|
||
def setup_nem_facade(app): | ||
app.config.from_envvar('EXPLORER_REST_SETTINGS') | ||
config = configparser.ConfigParser() | ||
db_path = Path(app.config.get('DATABASE_CONFIG_FILEPATH')) | ||
|
||
log.info(f'loading database config from {db_path}') | ||
|
||
config.read(db_path) | ||
|
||
nem_db_config = config['nem_db'] | ||
db_params = DatabaseConfig( | ||
nem_db_config['database'], | ||
nem_db_config['user'], | ||
nem_db_config['password'], | ||
nem_db_config['host'], | ||
nem_db_config['port'] | ||
) | ||
|
||
return NemRestFacade(db_params) | ||
|
||
|
||
def setup_nem_routes(app, nem_api_facade): | ||
@app.route('/api/nem/block/<height>') | ||
def api_get_nem_block_by_height(height): | ||
try: | ||
height = int(height) | ||
if height < 1: | ||
raise ValueError() | ||
|
||
except ValueError: | ||
abort(400) | ||
|
||
result = nem_api_facade.get_block(height) | ||
if not result: | ||
abort(404) | ||
|
||
return jsonify(result) | ||
|
||
@app.route('/api/nem/blocks') | ||
def api_get_nem_blocks(): | ||
try: | ||
limit = int(request.args.get('limit', 10)) | ||
offset = int(request.args.get('offset', 0)) | ||
min_height = int(request.args.get('min_height', 1)) | ||
|
||
if limit < 0 or offset < 0 or min_height < 1: | ||
raise ValueError() | ||
|
||
except ValueError: | ||
abort(400) | ||
|
||
return jsonify(nem_api_facade.get_blocks(limit=limit, offset=offset, min_height=min_height)) | ||
|
||
|
||
def setup_error_handlers(app): | ||
@app.errorhandler(404) | ||
def not_found(_): | ||
response = { | ||
'status': 404, | ||
'message': 'Resource not found' | ||
} | ||
return jsonify(response), 404 | ||
|
||
@app.errorhandler(400) | ||
def bad_request(_): | ||
response = { | ||
'status': 400, | ||
'message': 'Bad request' | ||
} | ||
return jsonify(response), 400 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from psycopg2 import pool | ||
|
||
|
||
class DatabaseConnectionPool: | ||
"""Database connection pool class.""" | ||
|
||
def __init__(self, db_config, min_connections=1, max_connections=10): | ||
"""Initialize the database connection pool with given configurations.""" | ||
|
||
self.db_config = db_config | ||
self.min_connections = min_connections | ||
self.max_connections = max_connections | ||
self._pool = self._create_pool() | ||
|
||
def _create_pool(self): | ||
return pool.SimpleConnectionPool( | ||
self.min_connections, | ||
self.max_connections, | ||
database=self.db_config.database, | ||
user=self.db_config.user, | ||
password=self.db_config.password, | ||
host=self.db_config.host, | ||
port=self.db_config.port | ||
) | ||
|
||
def connection(self): | ||
"""Acquires a managed database connection instance from the pool.""" | ||
|
||
return PooledConnection(self._pool) | ||
|
||
|
||
class PooledConnection: | ||
""" | ||
Represents a managed database connection from the connection pool. | ||
Intended for use within a context manager (`with` statement). | ||
""" | ||
|
||
def __init__(self, connection_pool): | ||
"""Initialize with a reference to a connection pool.""" | ||
|
||
self._pool = connection_pool | ||
self.connection = None | ||
|
||
def __enter__(self): | ||
"""Acquire a database connection from the pool upon entering the context of a `with` statement.""" | ||
|
||
self.connection = self._pool.getconn() | ||
return self.connection | ||
|
||
def __exit__(self, exc_type, exc_value, traceback): | ||
"""Ensure the connection is returned to the pool upon exiting the context of a `with` statement.""" | ||
|
||
if self.connection: | ||
self._pool.putconn(self.connection) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from binascii import hexlify | ||
|
||
from rest.model.Block import BlockView | ||
|
||
from .DatabaseConnection import DatabaseConnectionPool | ||
|
||
|
||
def _format_bytes(buffer): | ||
return hexlify(buffer).decode('utf8').upper() | ||
|
||
|
||
class NemDatabase(DatabaseConnectionPool): | ||
"""Database containing Nem blockchain data.""" | ||
|
||
@staticmethod | ||
def _create_block_view(result): | ||
return BlockView( | ||
height=result[0], | ||
timestamp=str(result[1]), | ||
total_fees=result[2], | ||
total_transactions=result[3], | ||
difficulty=result[4], | ||
block_hash=_format_bytes(result[5]), | ||
signer=_format_bytes(result[6]), | ||
signature=_format_bytes(result[7]) | ||
) | ||
|
||
def get_block(self, height): | ||
"""Gets block by height in database.""" | ||
|
||
with self.connection() as connection: | ||
cursor = connection.cursor() | ||
cursor.execute(''' | ||
SELECT * | ||
FROM blocks | ||
WHERE height = %s | ||
''', (height,)) | ||
result = cursor.fetchone() | ||
|
||
return self._create_block_view(result) if result else None | ||
|
||
def get_blocks(self, limit, offset, min_height): | ||
"""Gets blocks pagination in database.""" | ||
|
||
with self.connection() as connection: | ||
cursor = connection.cursor() | ||
cursor.execute(''' | ||
SELECT * | ||
FROM blocks | ||
WHERE height >= %s | ||
LIMIT %s OFFSET %s | ||
''', (min_height, limit, offset,)) | ||
results = cursor.fetchall() | ||
|
||
return [self._create_block_view(result) for result in results] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from rest.db.NemDatabase import NemDatabase | ||
|
||
|
||
class NemRestFacade: | ||
"""Nem Rest Facade.""" | ||
|
||
def __init__(self, db_config): | ||
"""Creates a facade object.""" | ||
|
||
self.nem_db = NemDatabase(db_config) | ||
|
||
def get_block(self, height): | ||
"""Gets block by height.""" | ||
|
||
block = self.nem_db.get_block(height) | ||
|
||
return block.to_dict() if block else None | ||
|
||
def get_blocks(self, limit, offset, min_height): | ||
"""Gets blocks pagination.""" | ||
|
||
blocks = self.nem_db.get_blocks(limit, offset, min_height) | ||
|
||
return [block.to_dict() for block in blocks] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
class BlockView: | ||
def __init__(self, height, timestamp, total_fees, total_transactions, difficulty, block_hash, signer, signature): | ||
"""Create Block view.""" | ||
|
||
# pylint: disable=too-many-arguments | ||
|
||
self.height = height | ||
self.timestamp = timestamp | ||
self.total_fees = total_fees | ||
self.total_transactions = total_transactions | ||
self.difficulty = difficulty | ||
self.block_hash = block_hash | ||
self.signer = signer | ||
self.signature = signature | ||
|
||
def __eq__(self, other): | ||
return isinstance(other, BlockView) and all([ | ||
self.height == other.height, | ||
self.timestamp == other.timestamp, | ||
self.total_fees == other.total_fees, | ||
self.total_transactions == other.total_transactions, | ||
self.difficulty == other.difficulty, | ||
self.block_hash == other.block_hash, | ||
self.signer == other.signer, | ||
self.signature == other.signature | ||
]) | ||
|
||
def to_dict(self): | ||
"""Formats the block info as a dictionary.""" | ||
|
||
return { | ||
'height': self.height, | ||
'timestamp': str(self.timestamp), | ||
'totalFees': self.total_fees, | ||
'totalTransactions': self.total_transactions, | ||
'difficulty': self.difficulty, | ||
'hash': str(self.block_hash), | ||
'signer': str(self.signer), | ||
'signature': str(self.signature) | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../../linters/python/lint.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/bin/bash | ||
|
||
set -ex | ||
|
||
python3 -m pip install -r "$(git rev-parse --show-toplevel)/linters/python/lint_requirements.txt" | ||
python3 -m pip install -r requirements.txt | ||
python3 -m pip install -r dev_requirements.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#!/bin/bash | ||
|
||
set -ex | ||
|
||
export POSTGRES_DB="explorer_rest" | ||
export POSTGRES_USER="postgres" | ||
export POSTGRES_PASSWORD="testPassword" | ||
export POSTGRES_HOST_AUTH_METHOD="password" | ||
export PGDATA=/var/lib/postgresql/data/pgdata | ||
|
||
initdb -U "${POSTGRES_USER}" -D "${PGDATA}" | ||
pg_ctl start --wait | ||
pg_ctl status | ||
|
||
psql -U "${POSTGRES_USER}" --command "CREATE USER ubuntu WITH SUPERUSER PASSWORD 'ubuntu';" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/bash | ||
|
||
set -ex | ||
|
||
TEST_RUNNER=$([ "$1" = "code-coverage" ] && echo "coverage run --append" || echo "python3") | ||
${TEST_RUNNER} -m pytest --asyncio-mode=auto -v |
Empty file.
Empty file.
Oops, something went wrong.