############################################################################## # ##############################################################################

# Imports # ============================================================================

# Make Python 3-ish from __future__ import (absolute_import, division, print_function) __metaclass__ = type

# Stdlib # —————————————————————————-

# Need {os.environ}, {os.path.join} import os

# Need {urllib.quote_plus} to URL-encode socket paths for the # {requests_unixsocket} package. import urllib

# Deps # —————————————————————————-

# Facilities making requests to UNIX domain sockets with the {requests} # package. import requests_unixsocket

# Constants # ============================================================================

# The name of the ENV var that holds the socket path, which is created in a # temp dir that only exists while the main QB process is running and is only # accessible to the user that ran it. # RPC_SOCKET_ENV_VAR_NAME = 'QB_RPC_SOCKET'

# Module Variables # ============================================================================

# A “module-level global” (yeah, weird they're called “globals”) that holds # the default {Client}, which is created on demand. # # Need to preface it's use with # # global _client # _client = None

# Module Functions # ============================================================================ # # Static helpers and functions that operate on the default {Client}, which is # created on demand using the ENV var set by the QB master process that hosts # the server (during normal execution… things are set up flexibly because I'm # sure we'd want to do things differently during testing). # # NOTE Due to the way Python's files<->imports system works, this seems like # the least annoying way to create a decent API without sticking stuff in # the `__init__.py` file, which I hate because it's really hard to # remember which ones have shit in them. #

def client_from_env():

return Client(socket_path=os.environ[RPC_SOCKET_ENV_VAR_NAME])

def requests_path_for(socket_path):

'''
URL-quotes the socket file path and protocol prefixes it with `http+unix://`

The {requests} module - as extended by {requests_unixsocket} - requires that
the actual file path to the socket by URL-quoted, probably because it uses
some split-by-/ logic to parse it, which would normally consider the socket
path part of the HTTP path.

:rtype: str
:return: Path ready for use with {requests_unixsocket.Session}.
'''
return "http+unix://{}".format(urllib.quote_plus(socket_path))

def init_from_env(force=False):

global _client

if _client is None or force is True:
    _client = client_from_env()
    return True
else:
    return False

def get_client():

global _client
init_from_env()
return _client

def set_client(client):

global _client
_client = client

def get(path):

return get_client().get(path)

def post(path, **payload):

return get_client().post(path, **payload)

def send(receiver, method, *args, **kwds):

return get_client().send(receiver, method, *args, **kwds)

class Client:

'''
RPC client for making calls to the QB master Ruby process (HTTP over 
a UNIX domain socket).
'''

def __init__(self, socket_path):
    self.socket_path = socket_path
    self.session = requests_unixsocket.Session()
    self.requests_path = requests_path_for(self.socket_path)

def full_path_for(self, path):
    if path[0] == '/':
        path = path[1:]
    return os.path.join(self.requests_path, path)

def handle_response(self, response):
    return response.json()['data']

def get(self, path):
    return self.handle_response(
        self.session.get(
            self.full_path_for(path)
        )
    )

def post(self, path, **payload):
    return self.handle_response(
        self.session.post(
            self.full_path_for(path),
            json = payload,
        )
    )

def send(self, receiver, method, *args, **kwds):
    return self.handle_response(
        self.session.post(
            self.full_path_for('/send'),
            json = dict(
                receiver = receiver,
                method = method,
                args = args,
                kwds = kwds,
            )
        )
    )