resdb_driver/utils.py (52 lines of code) (raw):
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from urllib.parse import urlparse, urlunparse
import rapidjson
import time
import re
from .exceptions import ValidationError
def serialize(data: dict) -> str:
"""! Serialize a dict into a JSON formatted string.
This function enforces rules like the separator and order of keys.
This ensures that all dicts are serialized in the same way.
This is specially important for hashing data. We need to make sure that
everyone serializes their data in the same way so that we do not have
hash mismatches for the same structure due to serialization
differences.
@param data (dict): Data to serialize
@return JSON formatted string
"""
return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True)
def gen_timestamp():
"""! The Unix time, rounded to the nearest second.
See https://en.wikipedia.org/wiki/Unix_time
@return The Unix time
"""
return str(round(time.time()))
DEFAULT_NODE = "http://localhost:9984"
class CreateOperation:
"""! Class representing the ``'CREATE'`` transaction operation.
"""
class TransferOperation:
"""! Class representing the ``'TRANSFER'`` transaction operation.
"""
ops_map = {
"CREATE": CreateOperation,
"TRANSFER": TransferOperation,
}
def _normalize_operation(operation):
"""! Normalizes the given operation string. For now, this simply means
converting the given string to uppercase, looking it up in
:attr:`~.ops_map`, and returning the corresponding class if
present.
@param operation (str): The operation string to convert.
@return The class corresponding to the given string,
:class:`~.CreateOperation` or :class:`~TransferOperation`.
.. important:: If the :meth:`str.upper` step, or the
:attr:`~.ops_map` lookup fails, the given ``operation``
argument is returned.
"""
try:
operation = operation.upper()
except AttributeError:
pass
try:
operation = ops_map[operation]()
except KeyError:
pass
return operation
def _get_default_port(scheme):
return 443 if scheme == "https" else 9984
def normalize_url(node):
"""! Normalizes the given node url"""
if not node:
node = DEFAULT_NODE
elif "://" not in node:
node = "//{}".format(node)
parts = urlparse(node, scheme="http", allow_fragments=False)
port = parts.port if parts.port else _get_default_port(parts.scheme)
netloc = "{}:{}".format(parts.hostname, port)
return urlunparse((parts.scheme, netloc, parts.path, "", "", ""))
def normalize_node(node, headers=None):
"""! Normalizes given node as str or dict with headers"""
headers = {} if headers is None else headers
if isinstance(node, str):
url = normalize_url(node)
return {"endpoint": url, "headers": headers}
url = normalize_url(node["endpoint"])
node_headers = node.get("headers", {})
return {"endpoint": url, "headers": {**headers, **node_headers}}
def normalize_nodes(*nodes, headers=None):
"""! Normalizes given dict or array of driver nodes"""
if not nodes:
return (normalize_node(DEFAULT_NODE, headers),)
normalized_nodes = ()
for node in nodes:
normalized_nodes += (normalize_node(node, headers),)
return normalized_nodes