PKPG2examples/__init__.py PKPGuvvexamples/ping_pong/settings.ini[APP] NAME = PING PONG REGISTRY = wolverine.discovery.consul.MicroConsul [DISCOVERY] HOST = localhost PORT = 8500 PKsTG|examples/ping_pong/service.pyimport asyncio import logging from wolverine.module.service import ServiceMessage, MicroService logger = logging.getLogger(__name__) class PingPongService(MicroService): def __init__(self, app, **options): self.delay = options.pop('delay', 1) self.routing = options.pop('routing', False) super(PingPongService, self).__init__(app, 'ping', **options) def ping1(self, data): logger.debug('--ping1 handler--') logger.debug('data: ' + str(data)) yield from asyncio.sleep(self.delay) return data def ping(self, data): logger.debug('data: ' + str(data)) yield from asyncio.sleep(self.delay) response = ServiceMessage() response.data = data return response def ping2(self, data): logger.debug('--ping1 handler--') logger.debug('data: ' + str(data)) yield from asyncio.sleep(self.delay) return data PK=SGAUUexamples/ping_pong/__main__.pyimport logging from optparse import OptionParser from examples.ping_pong import ping_pong LOG_FORMAT = "%(asctime)s %(levelname)s" \ " %(name)s:%(lineno)s %(message)s" def main(): log_level = logging.INFO usage = "usage: %prog [options] arg" parser = OptionParser(usage) parser.add_option("-p", "--port", dest="port", help="port to bind to", default='9210') parser.add_option("-v", "--version", dest="version", help="app version", default='1') parser.add_option("-d", "--delay", dest="delay", help="simulated workload delay", default="1") parser.add_option("-t", "--times", dest="times", help="number of times to ping", default="-1") parser.add_option("-r", "--routing", dest="routing", action='store_true', help="enable advanced routing") parser.add_option("-a", "--async", dest="async", action='store_true', help="enable async client/service worker") parser.add_option("-l", "--log-level", dest="loglevel", help="log level one of " "(DEBUG, INFO, WARNING, ERROR, Critical)", default="ERROR") (options, args) = parser.parse_args() if len(args) != 1: parser.error("incorrect number of arguments") if options.loglevel: log_level = getattr(logging, options.loglevel.upper()) logging.basicConfig(level=log_level, format=LOG_FORMAT) ping_pong(args[0], options) if __name__ == "__main__": main() PK TG%i##examples/ping_pong/__init__.pyimport asyncio import logging from uuid import uuid1 import functools import os from examples.ping_pong.service import PingPongService from wolverine.module.controller.zmq import ZMQMicroController logger = logging.getLogger(__name__) def ping_pong(mode, options): """Run a ping and pong service across a zmq dealer/router device :param mode: either client or server :param port: any arbitrary port. must not be in use if running the client :return: yo momma """ from wolverine import MicroApp loop = asyncio.get_event_loop() config_file = os.path.join(__path__[0], 'settings.ini') app = MicroApp(loop=loop, config_file=config_file) if 'ping' == mode: app.register_module( ping_client(int(options.port), delay=int(options.delay), times=int(options.times), routing=options.routing, async=options.async, version=options.version)) if 'pong' == mode: app.register_module( pong_controller(app, delay=int(options.delay), routing=options.routing, version=options.version)) if 'pong2' == mode: app.register_module( pong_server(delay=int(options.delay), routing=options.routing)) if 'gateway' == mode: app.register_module(gateway()) app.run() def gateway(): module = ZMQMicroController() gateway_id = str(uuid1())[:8] global gw_port gw_port = 1986 @module.data('service:', recurse=True) def ping_pong_data(data): service_names = [] for d in data: service_id = gateway_id + '_' + d['version'] service_name = d['name'] + ':' + service_id service_names.append(service_name) if service_name not in module.app.router.clients.keys(): create_client(d) removed = list(set(module.app.router.clients.keys()) - set(service_names)) for service_name in removed: logger.debug('removing client for service ' + service_name) module.app.loop.create_task( module.app.router.remove_client(service_name)) def create_client(data): global gw_port service_id = gateway_id + '_' + data['version'] service_name = data['name'] + ':' + service_id gw_port += 1 options = { 'service_id': service_id, 'port': gw_port, 'tags': ['version:' + data['version']], 'async': True } route = data['routes'][0] module.app.loop.create_task( module.connect_client(data['name'], functools.partial( callback, route, service_name, data['version']), **options)) logger.debug('data: ' + str(data)) def read_data(future): data = future.result() logger.info('response: ' + str(data)) def client_done(results, service_name): try: done, pending = yield from results logger.error('DONE: ' + str(len(done))) logger.error('PENDING:' + str(len(pending))) except Exception: logger.error('an error') logger.debug('service ' + service_name + ' is done') def callback(route, service_name, version): calls = [] data = {'message': service_name} try: up = True while up: yield from asyncio.sleep(1) future = asyncio.Future() logger.debug('sending ' + str(data) + ' to ' + route + ' version ' + version) up = yield from module.app.router.send(data, route, version=version, future=future) if up: logger.debug('sent success') future.add_done_callback(read_data) calls.append(future) else: logger.debug('sent failed') future.cancel() module.app.loop.create_task( client_done( asyncio.wait(calls, timeout=10), service_name)) except Exception: pass return return module def pong_controller(app, **options): pong_service = PingPongService(app, **options) module = ZMQMicroController() module.register_service(pong_service) return module def pong_server(**options): delay = options.pop('delay', 1) routing = options.pop('routing', False) module = ZMQMicroController() if not routing: @module.handler('ping', listen_type='health', handler_type='server') def pong(data): logger.debug(str(data)) yield from asyncio.sleep(delay) return data else: @module.handler('ping', route='ping1', listen_type='health', handler_type='server') def pong1(data): logger.debug('--ping1 handler--') logger.debug(str(data)) yield from asyncio.sleep(delay) return data @module.handler('ping', route='ping2', listen_type='health', handler_type='server') def pong2(data): logger.debug('--ping2 handler--') logger.debug(str(data)) yield from asyncio.sleep(delay) return data return module def ping_client(port, **options): delay = options.pop('delay', 1) times = options.pop('times', -1) routing = options.pop('routing', False) async = options.pop('async', False) module = ZMQMicroController() version = options.pop('version', '1') ping_opts = { 'address': 'tcp://127.0.0.1', 'port': port, 'tags': ['version:' + version], } def get_result(future): data = future.result() logger.info('response:' + str(data)) def finish(results): done, pending = yield from results logger.error('DONE: ' + str(len(done))) logger.error('PENDING:' + str(len(pending))) for fail in pending: logger.error('task never finished... ' + fail.result()) module.app.loop.create_task(module.app.stop('SIGTERM')) @module.client('ping', async=async, **ping_opts) def ping(): send_count = 1 tasks = [] try: while send_count <= times or times <= 0: yield from asyncio.sleep(delay) data = {'message': send_count} logger.info('sending ' + str(data) + ' to ping/ping1') if async: future = asyncio.Future() tasks.append(future) yield from module.app.router.send(data, 'ping/ping1', version=version, future=future) future.add_done_callback(get_result) if routing: send_count += 1 data2 = {'message': send_count} logger.info('sending ' + str(data2) + 'to ping/ping2') future2 = asyncio.Future() tasks.append(future2) yield from module.app.router.send(data2, 'ping/ping2', version=version, future=future2) future2.add_done_callback(get_result) else: response = yield from \ module.app.router.send(data, 'ping/ping1', version=version) logger.info('response:' + str(response)) if routing and send_count < times: send_count += 1 data2 = {'message': send_count} logger.info('sending ' + str(data2) + 'to ping/ping2') response = yield from \ module.app.router.send(data2, 'ping/ping2', version=version) logger.info('response:' + str(response)) send_count += 1 if async: module.app.loop.create_task( finish(asyncio.wait(tasks, timeout=10))) else: module.app.loop.create_task(module.app.stop('SIGTERM')) except asyncio.CancelledError: logger.debug('ping client killed') return module PKPGjjwolverine/settings.ini[APP] NAME = WOLVERINE ROUTER = wolverine.routers.MicroRouter REGISTRY = wolverine.discovery.MicroRegistryPKNXG)PPwolverine/__init__.pyimport asyncio from configparser import ConfigParser import logging import importlib import os import signal import functools import types from .discovery import MicroRegistry logger = logging.getLogger(__name__) class MicroApp(object): SIG_NAMES = ('SIGINT', 'SIGTERM', 'SIGHUP') _default_registry = MicroRegistry def __init__(self, loop=None, config_file='settings.ini'): self.name = '' self.config_file = config_file default_settings = os.path.join(__path__[0], 'settings.ini') self.config = ConfigParser() self.config.read(default_settings) self.tasks = [] self.modules = {} self._loop = loop @property def loop(self): if not self._loop: self._loop = asyncio.get_event_loop() return self._loop def register_module(self, module): logger.info("registering module [" + module.name + "]") module.register_app(self) self.modules[module.name] = module def _get_module_class(self, app_var): _path = self.config['APP'][app_var] module_name, class_name = _path.rsplit(".", 1) _module = importlib.import_module(module_name) return getattr(_module, class_name)() def _load_registry(self): registry = self._get_module_class('REGISTRY') self.register_module(registry) def _load_router(self): router = self._get_module_class('ROUTER') self.register_module(router) def run(self): """ Registers the internal modules. Loads the user defined configuration (if any) then gives each module a chance to execute their run method lastly calls the _run method to start the loop. """ self.config.read(self.config_file) self._load_registry() self._load_router() self.config.read(self.config_file) @asyncio.coroutine def _module_run(): for key, module in self.modules.items(): ret = module.run() if isinstance(ret, types.GeneratorType): yield from ret self.loop.run_until_complete(_module_run()) print('') self.name = self.config['APP'].get('NAME', 'Spooky Ash') print('-' * 20) print(' --', self.name, '--') print('-' * 20) print('') self._run() def _run(self): """ attach the exit signal handler and start the loop""" for sig in self.SIG_NAMES: self.loop.add_signal_handler(getattr(signal, sig), functools.partial(self._exit, sig)) try: self.loop.run_forever() logger.info('closing loop') self.loop.close() except Exception: logger.error('boom', exc_info=True) def _exit(self, sig_name): self.loop.create_task(self.stop(sig_name)) def stop(self, sig_name): if sig_name in self.SIG_NAMES: for key, module in self.modules.items(): yield from module.stop() tasks = asyncio.Task.all_tasks(self.loop) for task in tasks: try: task.cancel() except Exception: logger.error('failed to cancel task', exc_info=True) self.loop.stop() def __getattr__(self, item): if item in self.modules.keys(): return self.modules[item] else: raise AttributeError('no module named "' + item + '" in the app') PK@TGwolverine/routers/__init__.pyimport asyncio from collections import OrderedDict import logging import uuid import msgpack import re import types from wolverine.module import MicroModule from wolverine.module.controller.zhelpers import unpackb, packb, dump from wolverine.module.service import ServiceMessage logger = logging.getLogger(__name__) class MicroRouter(MicroModule): def __init__(self): super(MicroRouter, self).__init__() self.name = 'router' self.service_handlers = OrderedDict() self.client_handlers = {} self.clients = {} self.servers = {} self.async_req_queue = {} def run(self): pass # self.sort_handlers() def sort_handlers(self): """sort the service handlers by key length from shortest to longest""" d = self.service_handlers.copy() sorted_handlers = OrderedDict( sorted(d.items(), key=lambda t: len(t[0]))) self.service_handlers = sorted_handlers @asyncio.coroutine def stop(self): for service_name in list(self.servers.keys()): self.remove_server(service_name) for key in list(self.clients.keys()): yield from self.remove_client(key) self.service_handlers = {} logger.info("router exited") @asyncio.coroutine def add_client(self, client, name, **options): service_id = options.get('service_id', name) if service_id not in self.clients.keys(): self.clients[service_id] = client up = yield from self.app.registry.register(name, register_type='service', **options) return up else: logger.warning('not overriding a client with route ' + name) return True def remove_client(self, name): if name in self.clients.keys(): try: self.clients[name].close() except Exception: logger.error('error closing client ' + name, exc_info=True) try: yield from \ self.app.registry.deregister(name, register_type='service') except Exception: logger.error('failed to deregister client' + name, exc_info=True) del self.clients[name] def add_server(self, name, service): if name not in self.servers.keys(): self.servers[name] = service return True else: logger.warning('service ' + name + ' already registered') return False def remove_server(self, name): if name in self.servers.keys(): self.servers[name].close() del self.servers[name] def add_service_handler(self, route, func): if route not in self.service_handlers.keys(): self.service_handlers[route] = [] self.service_handlers[route].append(func) def remove_service_handler(self, handler): if handler in self.service_handlers.keys(): logger.info('removing all handlers for route ' + handler) logger.info('removed ' + str(len(self.service_handlers[handler])) + ' handlers') del self.service_handlers[handler] def add_client_handler(self, route, func): if route not in self.client_handlers.keys(): self.client_handlers[route] = [] self.client_handlers[route].append(func) def remove_client_handler(self, handler): if handler in self.service_handlers.keys(): logger.info('removing all handlers for route ' + handler) logger.info('removed ' + str(len(self.service_handlers[handler])) + 'handlers') del self.service_handlers[handler] def handle_service(self, data): route = data[-2] logger.info('handling data for route ' + route.decode('utf-8')) if logger.getEffectiveLevel() == logging.DEBUG: dump(data) return self._handle_service(route, data) def _handle_service(self, route, data): result = {'data': [], 'errors': []} if isinstance(route, bytes): route = route.decode('utf-8') found = False for key, handlers in self.service_handlers.items(): pattern = re.compile(key) if pattern.match(route): req = unpackb(data[-1]) found = True logger.info('handler: ' + key) for func in handlers: try: response = func(req) if isinstance(response, types.GeneratorType): response = yield from response if isinstance(response, ServiceMessage): if response.has_error(): result['errors'].append(response.errors) response = response.data if response is not None: result['data'].append(response) except Exception as ex: logger.error('failed in handling data: ', exc_info=True) logger.error('failed in data handling') result['errors'].append("{0}".format(ex)) break if not found: logger.info('no matching route for ' + route) packet = data[:-1] + [packb(result)] return packet def reply(self, data, name): if name in self.servers.keys(): client = self.servers[name] client.write(data) yield from client.drain() def _send(self, data, client): client.write(data) yield from client.drain() data = yield from client.read() return data @asyncio.coroutine def _send_async(self, data, client, correlation_id, future): self.async_req_queue[correlation_id] = future client.write(data) yield from client.drain() return future def send(self, data, route='.*', version='1', **options): client = None future = options.pop('future', None) service = route.split('/')[0] if len(route.split('/')) < 2: route += '/' for c_client_id, c_client in self.clients.items(): c_service, c_service_id = c_client_id.split(':') c_version = c_service_id.split('_')[1] if service == c_service and version == c_version: client = c_client break if client: correlation_id = str(uuid.uuid1())[:8] b_data = msgpack.packb(data, use_bin_type=True) packet = (bytes(correlation_id, encoding='utf-8'), bytes(route, encoding='utf-8'), b_data) if not future: response = yield from self._send(packet, client) response = msgpack.unpackb(response[-1]) else: response = yield from self._send_async(packet, client, correlation_id, future) return response else: return None PK5PG \wolverine/module/__init__.pyimport asyncio import logging logger = logging.getLogger(__name__) class MicroModule(object): def __init__(self): self.name = self.__class__.__name__ def register_app(self, app): self.app = app logger.debug('registering ' + self.name + ' with app') def run(self): logger.debug('running module ' + self.name) @asyncio.coroutine def stop(self): logger.debug('closing module ' + self.name) PKSGvHo. . 'wolverine/module/controller/zhelpers.py# encoding: utf-8 """ Helper module for example applications. Mimics ZeroMQ Guide's zhelpers.h. """ from __future__ import print_function import binascii import logging import msgpack import os from random import randint import zmq logger = logging.getLogger(__name__) def dump(msg_or_socket): out = '\n-------data-packet-------' """Receives all message parts from socket, printing each frame neatly""" if isinstance(msg_or_socket, zmq.Socket): # it's a socket, call on current message msg = msg_or_socket.recv_multipart() else: msg = msg_or_socket for part in msg: out += "\n[%03d] " % len(part) try: out += part.decode('utf-8') except UnicodeDecodeError: try: out += str(msgpack.unpackb(part)) except Exception: out += r"0x%s" % (binascii.hexlify(part).decode('ascii')) out += '\n' + '-'*25 logger.debug(out) def packb(data): try: return msgpack.packb(data, use_bin_type=True) except Exception: logger.error('error packing data', extra={'data': data}, exc_info=True) def unpack(data): try: return data.decode('utf-8') except UnicodeDecodeError: return unpackb(data) def unpackb(data): try: return msgpack.unpackb(data, encoding='utf-8') except Exception: logger.warning('couldn\'t decode data' + str(data)) return data def set_id(zsocket): """Set simple random printable identity on socket""" identity = u"%04x-%04x" % (randint(0, 0x10000), randint(0, 0x10000)) zsocket.setsockopt_string(zmq.IDENTITY, identity) def zpipe(ctx): """build inproc pipe for talking to threads mimic pipe used in czmq zthread_fork. Returns a pair of PAIRs connected via inproc """ a = ctx.socket(zmq.PAIR) b = ctx.socket(zmq.PAIR) a.linger = b.linger = 0 a.hwm = b.hwm = 1 iface = "inproc://%s" % binascii.hexlify(os.urandom(8)) a.bind(iface) b.connect(iface) return a,b ZMQ_EVENTS = { getattr(zmq, name): name.replace('EVENT_', '').lower().replace('_', ' ') for name in [i for i in dir(zmq) if i.startswith('EVENT_')]} def event_description(event): """ Return a human readable description of the event """ return ZMQ_EVENTS.get(event, 'unknown') PKثTGh$$"wolverine/module/controller/zmq.pyimport ast import asyncio from asyncio.futures import InvalidStateError import logging from uuid import uuid1 import aiozmq import msgpack import types import zmq from . import MicroController from .zhelpers import event_description, unpack logger = logging.getLogger(__name__) class ZMQMicroController(MicroController): bind_types = { "observer": zmq.ROUTER, 'provider': zmq.DEALER } def __init__(self): super(ZMQMicroController, self).__init__() @asyncio.coroutine def stop(self): logger.info('closing module ' + self.name) def connect_client(self, name, func, **options): port = options.pop('port', '9210') tags = options.pop('tags', ['version:1']) version = '1' for tag in tags: tag_name = tag.split(':')[0] if tag_name == 'version' and len(tag.split(':')) > 0: version = tag.split(':')[1] async = options.pop('async', False) address = options.pop('address', 'tcp://127.0.0.1') uri = address + ':' + str(port) logger.info('client connect for service: ' + name) default_service_id = str(uuid1())[:8] + '_' + version service_id = options.pop('service_id', default_service_id) service_name = name + ':' + service_id check_ttl = options.pop('ttl', 12) ttl_ping = options.pop('ttl_ping', 10) try: client = yield from aiozmq.create_zmq_stream( zmq.DEALER) # self.streams['client:' + service_name] = client yield from client.transport.enable_monitor() self.app.loop.create_task( self.monitor_stream(name, client)) yield from client.transport.bind(uri) except Exception as ex: logger.error('failed to bind zqm socket for dealer ' + service_name, exc_info=True) return service_opts = { 'service_id': service_name, 'address': address, 'port': int(port), 'tags': tags, 'check_ttl': str(check_ttl) + 's', 'ttl_ping': int(ttl_ping) } up = yield from self.app.router.add_client(client, name, **service_opts) if up: logger.info('service ' + name + ' registered with consul with id ' + service_name) else: logger.error('failed to register client ' + service_name + ' with consul... ') logger.error('shutting down zqm dealer ' + name) client.close() return if async: self.app.loop.create_task(self.connect_client_handler(client)) try: response = func() if isinstance(response, types.GeneratorType): yield from response except aiozmq.ZmqStreamClosed: logger.info('stream closed') except Exception: logger.error('failed in client callback', exc_info=True) logger.error('client closing') client.close() def connect_client_handler(self, client): try: while True: response = yield from client.read() correlation_id = response[0].decode('utf-8') if correlation_id in self.app.router.async_req_queue: future = self.app.router.async_req_queue[correlation_id] if not future.cancelled(): data = msgpack.unpackb(response[-1], encoding='utf-8') if not future.done(): future.set_result(data) except aiozmq.ZmqStreamClosed: logger.info('closing client read buffer') @asyncio.coroutine def connect_data(self, name, func, **options): listen_type = options.pop('listen_type', 'kv') @self.app.registry.listen(name, listen_type=listen_type, **options) def discover_data(packet): try: index, data_set = packet logger.debug('data set: ' + str(data_set)) if data_set is None: data_set = [] params = [] for data in data_set: value = data['Value'] if isinstance(value, bytes): value = unpack(value) value = ast.literal_eval(value) params.append(value) func(params) except Exception: logger.error('kv data handling exception', exc_info=True) @asyncio.coroutine def connect_service(self, name, service): try: options = service.options except Exception: options = {} try: options['tag'] = 'version:' + str(service.version) except Exception: options['tag'] = 'version:1' bind_type = options.pop('bind_type', zmq.ROUTER) listen_type = options.pop('listen_type', 'health') @self.app.registry.listen(name, listen_type=listen_type, singleton=True, **options) def discover_service(data): try: data = self.app.registry.unwrap(data, listen_type) if data and 'passing' in data: new = list(set(data['passing'].keys()) - set(self.app.router.servers.keys())) removed = list(set(self.app.router.servers.keys()) - set(data['passing'].keys())) logger.info('\n' + '-' * 20 + '\n discovery\n' + 'service:' + name + '\nnew: ' + str(len(new)) + ' removed: ' + str(len(removed)) + '\n' + '-' * 20) for key in new: logger.info('registering new handler for ' + key) s = data['passing'][key] address = s.pop('Address', '') port = s.pop('Port', '') uri = address + ':' + str(port) service = yield from \ self.bind_service(key, uri, bind_type) self.app.router.add_server(key, service) for key in removed: logger.info('removed handler for ' + key) self.app.router.remove_server(key) except Exception as e: logger.error('service binding error:', exc_info=True) @asyncio.coroutine def bind_service(self, service_name, address, bind_type): server = yield from aiozmq.create_zmq_stream(bind_type) yield from server.transport.enable_monitor() yield from server.transport.connect(address) logger.info('zmq stream ' + service_name + ' registered at ' + address) self.app.loop.create_task(self.monitor_stream(service_name, server)) def run(): alive = True while alive: try: work = yield from server.read() self.app.loop.create_task( self.handle_service_data(work, service_name)) except aiozmq.ZmqStreamClosed: logger.info('zmq stream ' + service_name + ' closed') alive = False except Exception: pass # logger.error(service_name + ' work halted', exc_info=True) self.app.loop.create_task(run()) return server @asyncio.coroutine def monitor_stream(self, name, stream): logger.debug('monitoring stream' + name) try: while True: event = yield from stream.read_event() event = event_description(event.event) logger.debug('stream ' + name + ' event:' + event) except aiozmq.ZmqStreamClosed: logger.debug('monitoring closed for stream ' + name) @asyncio.coroutine def handle_service_data(self, d, service_name): state = 0 try: state = 1 response = self.app.router.handle_service(d) if isinstance(response, types.GeneratorType): response = yield from response ret = self.app.router.reply(response, service_name) if isinstance(ret, types.GeneratorType): yield from ret state = 2 except aiozmq.ZmqStreamClosed: logger.info('stream closed') except InvalidStateError: logger.info('invalid state') except Exception: logger.error('failure while handling data', extra={'service_name': service_name, 'data': d}, exc_info=True) finally: if state != 2: logger.error('service handler reached invalid state', extra={'state': 2}) PKTGr 'wolverine/module/controller/__init__.pyimport inspect import logging import msgpack from wolverine.module import MicroModule from wolverine.module.service import ServiceDef logger = logging.getLogger(__name__) class MicroController(MicroModule): def __init__(self): super(MicroController, self).__init__() self.handlers = [] self.services = {} self.service_defs = {} self.clients = {} self.name = self.__class__.__name__ def run(self): logger.info('running module ' + self.name) for name, service in self.services.items(): self.app.loop.create_task( self.connect_service(name, service)) for name, packet in self.service_defs.items(): self.app.loop.create_task( self.app.registry.register(name, value=packet)) for handler in self.handlers: self.register_handler(handler) self.app.router.sort_handlers() def register_handler(self, handler): name, handler_type, func, options = handler try: if 'server' == handler_type: route = name + '/' + options.pop('route', ".*") self.app.router.add_service_handler(route, func) if 'client' == handler_type: self.app.loop.create_task( self.connect_client(name, func, **options)) if 'data' == handler_type: self.app.loop.create_task( self.connect_data(name, func, **options) ) except Exception: logger.error('handler connect failed for ' + name, exc_info=True) def register_service(self, service): service_name = service.name or service.__class__.__name__ service_def = ServiceDef(service_name, service.version) members = inspect.getmembers(service, predicate=inspect.ismethod) for name, func in members: if '__init__' == name: continue options = {'route': name} self.add_handler(service_name, 'server', func, **options) service_def.routes.append(service_name + '/' + name) packet = msgpack.packb(str(service_def), encoding='utf-8') self.service_defs[service_def.fqn()] = packet self.services[service_name] = service def handler(self, service, **options): """Collects data to build zmq endpoints based on consul services actual binding and initialization is deferred to module registration :param rule: :param options: :return: """ def decorator(f): handler_type = options.pop("handler_type", "server") self.add_handler(service, handler_type, f, **options) return f return decorator def client(self, name, **options): def decorator(f): self.add_handler(name, 'client', f, **options) return f return decorator def data(self, name, **options): def decorator(f): self.add_handler(name, 'data', f, **options) return f return decorator def add_handler(self, name, handler_type, f, **options): self.handlers.append((name, handler_type, f, options)) def connect_service(self, name, service): logger.debug('connecting handler service ' + name) def connect_client(self, name, func, **options): logger.debug('connecting handler client ' + name) def connect_data(self, name, func, **options): logger.debug('connecting handler data' + name) PK[TG3$wolverine/module/service/__init__.py class MicroService(object): def __init__(self, app, name='service', version='1', **options): self.app = app self.name = name self.version = version self.options = options class ServiceMessage(object): """ Blueprint for messages returning from services. We want all the services building up results and returning them, as well as errors in a uniform way. """ def __init__(self): self.data = [] self.errors = [] def has_error(self): """ @:return Boolean: True if errors exist """ return len(self.errors) > 0 def err(self, msg): """ @:param msg: An error message to add """ self.errors.append(msg) def response(self): """ Builds a result with only the desired fields @:returns dict: The structure understood by the services """ return { 'errors': self.errors, 'data': self.data } class ServiceDef(object): def __init__(self, name='', version='1'): self.name = name self.version = version self.routes = [] def fqn(self): return 'service:' + self.name + ':' + str(self.version) def __repr__(self): return str({ 'name': self.name, 'routes': self.routes, 'version': self.version }) def __str__(self): return self.__repr__() PKXG54wolverine/test/__init__.pyimport logging from wolverine import MicroApp logger = logging.getLogger(__name__) class TestMicroApp(MicroApp): def _run(self): logger.info('test app running') PK ZGbwolverine/web/settings.ini[APP] NAME = WOLVERINE WEB CONSOLE ROUTER = wolverine.routers.MicroRouter REGISTRY = wolverine.discovery.consul.MicroConsul [WEB_CONSOLE] HTTP_HOST=0.0.0.0 HTTP_PORT=8080 PK1ZGAS wolverine/web/__main__.pyimport asyncio import logging from optparse import OptionParser import os from wolverine import MicroApp from wolverine.gateway import GatewayModule from wolverine.web import WebModule LOG_FORMAT = "%(asctime)s %(levelname)s" \ " %(name)s:%(lineno)s %(message)s" def web(): log_level = logging.INFO usage = "usage: %prog [options] arg" parser = OptionParser(usage) parser.add_option("-l", "--log-level", dest="log_level", help="log level one of " "(DEBUG, INFO, WARNING, ERROR, Critical)", default="ERROR") (options, args) = parser.parse_args() if options.log_level: log_level = getattr(logging, options.log_level.upper()) logging.basicConfig(level=log_level, format=LOG_FORMAT) loop = asyncio.get_event_loop() import wolverine.web default_settings = os.path.join(wolverine.web.__path__[0], 'settings.ini') app = MicroApp(loop=loop, config_file=default_settings) gateway = GatewayModule() app.register_module(gateway) web_console = WebModule() app.register_module(web_console) app.run() if __name__ == '__main__': web() PK ZGׇ  wolverine/web/__init__.pyimport ast import asyncio import json import logging import os from aiohttp import web as aio_web from wolverine.module import MicroModule logger = logging.getLogger(__name__) class WebModule(MicroModule): def __init__(self): super(WebModule, self).__init__() self.name = 'web_console' self.default_settings = os.path.join(__path__[0], 'settings.ini') self.http = aio_web.Application() self.http_handler = self.http.make_handler() self.http_tasks = [] def register_app(self, app): self.app = app app.config.read(self.default_settings) def read_config(self): config = self.app.config[self.name.upper()] self.http_host = config.get('HTTP_HOST') self.http_port = config.get('HTTP_PORT') def run(self): logger.info('running the web console') self.read_config() self.srv = self.app.loop.create_server( self.http_handler, self.http_host, self.http_port) self.app.loop.create_task(self.srv) self.http.router.add_route('GET', '/', self.hello) self.http.router.add_route('GET', '/service', self.get_services) self.http.router.add_route('GET', '/service/{service}', self.get_service_routes) self.http.router.add_route('POST', '/service/{service}' '/{route}/{version}', self.post_to_route) def stop(self): yield from self.http_handler.finish_connections(1.0) yield from self.http.finish() @asyncio.coroutine def get_services(self, request): services = self.app.gateway.client_services data = json.dumps(services) return aio_web.Response(text=data) @asyncio.coroutine def get_service_routes(self, request): service_name = request.match_info['service'] servers = self.app.gateway.client_services.get(service_name, {}) data = json.dumps(servers) return aio_web.Response(text=data) @asyncio.coroutine def post_to_route(self, request): try: service_name = request.match_info['service'] route_name = request.match_info['route'] route = service_name + '/' + route_name version = request.match_info['version'] data_str = yield from request.content.read() data = ast.literal_eval(data_str.decode('utf-8')) future = asyncio.Future() self.http_tasks.append(future) service_request = yield from self.app.router.send(data, route, version=version, future=future) service_data = yield from service_request body = bytes(str(service_data), encoding='utf-8') except Exception as ex: logger.error('error reading web console request', exc_info=True) data = {'error': 'invalid request'} body = bytes(str(data), encoding='utf-8') return aio_web.Response(body=body) @asyncio.coroutine def hello(self, request): return aio_web.Response(body=b"Hello, world") PKZGgBBwolverine/gateway/settings.ini[GATEWAY] GATEWAY_HOST=tcp://0.0.0.0 GATEWAY_PORT_RANGE=1800-1900 PKJZG0Ft  wolverine/gateway/__init__.pyimport logging from uuid import uuid1 import functools import os from wolverine.module.controller.zmq import ZMQMicroController logger = logging.getLogger(__name__) class GatewayModule(ZMQMicroController): def __init__(self): super(GatewayModule, self).__init__() self.name = 'gateway' self.gateway_id = str(uuid1())[:8] self.default_settings = os.path.join(__path__[0], 'settings.ini') self.client_services = {} self.used_ports = [] self.recycled_ports = [] def register_app(self, app): self.app = app app.config.read(self.default_settings) def read_config(self): config = self.app.config['GATEWAY'] self.gw_host = config.get('GATEWAY_HOST') self.gw_start_port, self.gw_end_port = config.get( 'GATEWAY_PORT_RANGE').split('-') self.gw_start_port = int(self.gw_start_port) self.gw_end_port = int(self.gw_end_port) def run(self): logger.info('running the gateway') self.read_config() self.bind_service_data() super(GatewayModule, self).run() def get_gw_port(self): port = self.gw_start_port if len(self.recycled_ports) > 0: port = self.recycled_ports.pop(0) elif len(self.used_ports) > 0: port = max(self.used_ports) + 1 if port > self.gw_end_port: logger.warning( 'using port {}, which is above ' 'the configured range of {}-{} ' .format(port, self.gw_start_port, self.gw_end_port)) self.used_ports.append(int(port)) return port def bind_service_data(self): @self.data('service:', recurse=True) def service_data(data): service_names = [] for d in data: service_id = self.gateway_id + '_' + d['version'] service_name = d['name'] + ':' + service_id service_names.append(service_name) if service_name not in self.app.router.clients.keys(): self.create_client(d) removed = list(set(self.app.router.clients.keys()) - set(service_names)) for service_name in removed: self.remove_client(service_name) def remove_client(self, name): logger.debug('removing client for service ' + name) data = self.client_services[list(name.split(':'))[0]][name] port = int(data['port']) if port in self.used_ports: self.used_ports.remove(port) self.recycled_ports.append(port) del self.client_services[list(name.split(':'))[0]][name] self.app.loop.create_task( self.app.router.remove_client(name)) def create_client(self, data): service_id = self.gateway_id + '_' + data['version'] service_name = data['name'] + ':' + service_id port = self.get_gw_port() data['port'] = port if data['name'] not in self.client_services: self.client_services[data['name']] = {} self.client_services[data['name']][service_name] = data options = { 'service_id': service_id, 'address': self.gw_host, 'port': port, 'tags': ['version:' + data['version']], 'async': True } route = data['routes'][0] self.app.loop.create_task( self.connect_client(data['name'], functools.partial( self.callback, route, service_name, data['version']), **options)) logger.debug('data: ' + str(data)) def callback(self, route, service_name, version): pass PKPGhH88wolverine/shell/settings.ini[DISCOVERY] HOST = localhost PORT = 8500 SCHEME = httpPKPG[@wolverine/shell/__main__.pyfrom configparser import ConfigParser import datetime import logging from consul import Consul import os logger = logging.getLogger(__name__) def execfile(pythonrc): with open(pythonrc) as f: startup_code = compile(f.read(), pythonrc, 'exec') exec(startup_code) def shell(): import code import wolverine.shell default_settings = os.path.join(wolverine.shell.__path__[0], 'settings.ini') config = ConfigParser() config.read([default_settings, 'settings.ini']) # Set up a dictionary to serve as the environment for the shell, so # that tab completion works on objects that are imported at runtime. # See ticket 5082. imported_objects = {'datetime': datetime, 'config': config} try: imported_objects['consul'] = Consul(**config['DISCOVERY']) except Exception: logger.error('could not connect to consul') try: # Try activating rlcompleter, because it's handy. import readline except ImportError: pass else: # We don't have to wrap the following import in a 'try', because # we already know 'readline' was imported successfully. import rlcompleter readline.set_completer(rlcompleter.Completer(imported_objects).complete) readline.parse_and_bind("tab:complete") # We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system # conventions and get $PYTHONSTARTUP first then import user. pythonrc = os.environ.get("PYTHONSTARTUP") if pythonrc and os.path.isfile(pythonrc): try: execfile(pythonrc) except NameError: pass code.interact(local=imported_objects) if __name__ == '__main__': shell() PKPG2wolverine/shell/__init__.py PK9VGD(ewolverine/discovery/__init__.pyfrom wolverine.module import MicroModule class MicroRegistry(MicroModule): def __init__(self): super(MicroRegistry, self).__init__() self.name = 'registry' def register(self, key, value, **options): pass def deregister(self, key, **options): pass def listen(self, name, **options): pass def unwrap(self, data, data_type=None): pass PKJTGp11wolverine/discovery/consul.pyimport asyncio import logging import os from consul import Check from functools import wraps from consul.aio import Consul import types from wolverine.discovery import MicroRegistry logger = logging.getLogger(__name__) class MicroConsul(MicroRegistry): def __init__(self): super(MicroConsul, self).__init__() self.name = 'registry' self.binds = {} self.health_tasks = {} self.run_tick = 3 self.is_connected = False self._loop = None self._registry = None self.alive = False import wolverine.discovery self.default_settings = os.path.join(wolverine.discovery.__path__[0], 'consul.ini') def register_app(self, app): self.app = app app.config.read(self.default_settings) self._loop = app.loop def run(self): self.config = self.app.config['DISCOVERY'] self.host = self.config.get('HOST', 'localhost') self.port = int(self.config.get('PORT', '8500')) self.token = self.config.get('TOKEN', None) self.scheme = self.config.get('SCHEME', 'http') self.consistency = self.config.get('CONSISTENCY', 'default') self.dc = self.config.get('DC', None) self.verify = self.config.get('VERIFY', True) self._connect() logger.info('Consul - Initializing discovery listeners') def node_change(data): logger.info("node:" + str(data)) pass self.bind_listener('node', 'node', node_change) self.run_task = self._loop.create_task(self.run_forever()) @asyncio.coroutine def stop(self): self.alive = False def _connect(self): self._registry = Consul(self.host, self.port, self.token, self.scheme, self.consistency, self.dc, self.verify, loop=self._loop) self.agent = self._registry.agent self.catalog = self._registry.catalog self.health = self._registry.health self.kv = self._registry.kv def run_forever(self): self.alive = True while self.alive: if not self.is_connected: try: agent_data = yield from self._registry.agent.self() self.is_connected = agent_data['Member']['Status'] self.is_connected = True self._bind_all() except Exception: logger.error('failed to connect to agent') self.is_connected = False yield from asyncio.sleep(self.run_tick) def _bind_all(self): if self.is_connected: for key, bind in self.binds.items(): if bind.state in [0, -1]: logger.info('binding ' + bind.bind_type + ' ' + bind.name) self._loop.create_task(bind.run()) def bind_listener(self, bind_type, name, func, **kwargs): bind = None single = kwargs.pop('singleton', False) if bind_type == 'kv': bind = ConsulKVBind(self, name, func, **kwargs) if bind_type == 'service': bind = ConsulServiceBind(self, name, func, **kwargs) if bind_type == 'health': bind = ConsulServiceHealthBind(self, name, func, **kwargs) if bind_type == 'node': bind = ConsulNodeBind(self, name, func, **kwargs) if isinstance(bind, ConsulBind): if bind.key in self.binds.keys(): if not single: bind = self.binds[bind.key] bind.callbacks.append(func) logger.warning("binding count for " + bind.key + ':', len(bind.callbacks)) if 'data' in kwargs: bind.data.append(kwargs['data']) else: logger.warning('discovery warning:not binding ' 'additional callbacks for singleton:' + name) else: self.binds[bind.key] = bind else: logger.warning("bind type not recognized: " + bind_type) return bind def listen(self, name, listen_type="kv", **kwargs): def listen_decorator(func): bind = self.bind_listener(listen_type, name, func, **kwargs) @wraps(func) def bind_run(data): for val in bind.run(): for callback in bind.callbacks: return callback(val) return bind_run return listen_decorator def unwrap(self, data, data_type='kv'): if 'kv' == data_type: return unwrap_kv(data) if 'service' == data_type: return unwrap_service(data) if 'health' == data_type: return unwrap_health(data) @asyncio.coroutine def register(self, name, register_type='kv', value=None, **options): try: if 'kv' == register_type: yield from self.kv.put(name, value, **options) if 'service' == register_type: service_id = options.pop('service_id', name) check_ttl = options.pop('check_ttl', None) if check_ttl: options['check'] = Check.ttl(check_ttl) ttl = None if 'ttl_ping' in options: ttl = options.pop('ttl_ping') yield from self.agent.service.register(name, service_id=service_id, **options) if ttl: self.health_tasks[service_id] = self._loop.create_task( self._health_ttl_ping(service_id, ttl)) return True except Exception: logger.critical('failed to register with consul') return False @asyncio.coroutine def deregister(self, key, register_type='kv', **options): logger.info('deregistering ' + register_type + ' ' + key) if 'kv' == register_type: yield from self.kv.delete(key) if 'service' == register_type: yield from self.agent.service.deregister(key) if key in self.health_tasks: self.health_tasks[key].cancel() del self.health_tasks[key] logger.info('removed health task ' + key) @asyncio.coroutine def _health_ttl_ping(self, service_id, ttl): check_id = 'service:' + service_id alive = True while alive: try: data = \ yield from self.agent.check.ttl_pass(check_id) logger.info('health check returned with ' + str(data)) yield from asyncio.sleep(ttl) except Exception: alive = False logger.info('health ttl ' + service_id + ' stopped') class ConsulBind(object): bind_type = "vanilla" def __init__(self, client, name, callback, **kwargs): self.index = None self.name = name self.cache = {} self.registry = {} self.extra = [] self.connect_retries = 10 self.connect_delay = 3 self.callbacks = [] self.client = client self.key = self.bind_type + ':' + name self.state = 0 if 'data' in kwargs: self.callbacks.append( self.wrap_callback(callback, kwargs.get('data'))) del kwargs['data'] else: self.callbacks.append(callback) self.params = kwargs @asyncio.coroutine def run(self): pass def wrap_callback(self, callback, app_data): def wrap(data): data = data + (app_data,) callback(data) return wrap def stop(self): self.state = 0 def __del__(self): self.state = -1 class ConsulKVBind(ConsulBind): bind_type = "kv" def run(self): self.state = 1 logger.debug('listening to key: ' + self.name) while self.state == 1: try: index, data = yield from self.client.kv.get(self.name, **self.params) if self.cache != data: self.cache = data for callback in self.callbacks: callback((index, data)) self.index = index except asyncio.CancelledError: logger.warning('Value bind cancelled for ' + self.name) self.state = 0 self.index = None except Exception: self.state = 0 self.index = None class ConsulServiceBind(ConsulBind): bind_type = 'service' def run(self): self.state = 1 try: while self.state == 1: index, data = yield from self.client.catalog.service( self.name, index=self.index, **self.params) if self.cache != data: self.cache = data for callback in self.callbacks: callback((index, data)) self.index = index except asyncio.CancelledError: logger.warning('service bind cancelled for ' + self.name) except Exception: logger.error('service bind failed', exc_info=True) finally: self.state = 0 self.index = None class ConsulServiceHealthBind(ConsulBind): bind_type = 'health' def run(self): self.state = 1 try: while self.state == 1: response = yield from self.client.health.service( self.name, index=self.index, **self.params) logger.debug('\n' + '-'*20 + '\nhealth data:\n' + str(response) + '\n' + '-'*20) if response is not None: index, data = response if self.cache != data: self.cache = data for callback in self.callbacks: response = callback((index, data)) if isinstance(response, types.GeneratorType): yield from response self.index = index except asyncio.CancelledError: logger.warning('health bind cancelled for ' + self.name) except Exception: logger.error('service health bind failed', exc_info=True) finally: self.state = 0 self.index = None class ConsulNodeBind(ConsulBind): bind_type = 'node' def load_default_agent_name(self): data = yield from self.client.agent.self() return data['Member']['Name'] def run(self): self.state = 1 try: while self.state == 1: if self.name == 'default': self.name = yield from self.load_default_agent_name() index, data = yield from self.client.catalog.node( self.name, index=self.index, **self.params) if self.cache != data: self.cache = data for callback in self.callbacks: callback((index, data)) self.index = index except asyncio.CancelledError: logger.warning('node bind cancelled for ' + self.name) except Exception: # logger.error('node bind failed', exc_info=True) logger.error('node bind failed') finally: self.client.is_connected = False self.state = 0 self.index = None def unwrap_kv(data): if data is not None and 'Value' in data: return data['Value'].decode('utf-8') else: return None def unwrap_service(data): return data def unwrap_health(data): ret = {'passing': {}, 'failing': {}} if data and len(data) > 0: services = list(data[1]) for node in services: service = node['Service'].copy() checks = node['Checks'] is_alive = True name = 'service:' + service['ID'] for check in checks: if check['Status'] != 'passing': is_alive = False if is_alive: ret['passing'][name] = service else: ret['failing'][name] = service return ret PKPGŹaOOwolverine/discovery/consul.ini[DISCOVERY] HOST = localhost PORT = 8500 SCHEME = http CONSISTENCY = default PK)ZG^- )wolverine-0.2.2.dist-info/DESCRIPTION.rstUNKNOWN PK)ZG;KG$^^'wolverine-0.2.2.dist-info/metadata.json{"extensions": {"python.details": {"contacts": [{"email": "techlance@gmail.com", "name": "Lance Andersen", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/drankinn/wolverine"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "license": "MIT 2.0", "metadata_version": "2.0", "name": "wolverine", "run_requires": [{"requires": ["aiohttp (>=0.17.4)", "aiozmq (>=0.7.1)", "msgpack-python (>=0.4.6)", "python-consul (>=0.4.5)", "pyzmq (>=14.4.1)"]}], "summary": "micro service framework with python 3.5 and asyncio", "version": "0.2.2"}PK)ZG3 /'wolverine-0.2.2.dist-info/top_level.txtexamples wolverine PK)ZG''\\wolverine-0.2.2.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PK)ZG搅"wolverine-0.2.2.dist-info/METADATAMetadata-Version: 2.0 Name: wolverine Version: 0.2.2 Summary: micro service framework with python 3.5 and asyncio Home-page: http://github.com/drankinn/wolverine Author: Lance Andersen Author-email: techlance@gmail.com License: MIT 2.0 Platform: UNKNOWN Requires-Dist: aiohttp (>=0.17.4) Requires-Dist: aiozmq (>=0.7.1) Requires-Dist: msgpack-python (>=0.4.6) Requires-Dist: python-consul (>=0.4.5) Requires-Dist: pyzmq (>=14.4.1) UNKNOWN PK)ZGxe_ _ wolverine-0.2.2.dist-info/RECORDexamples/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 examples/ping_pong/__init__.py,sha256=XNJsMuNMGwVn14_QregKkCN3HzWxLy_b2SONVjPTb34,8963 examples/ping_pong/__main__.py,sha256=q7t_tMaQVydqpGg12ybM07c6TsOTr0txaQhhB4TDCag,1621 examples/ping_pong/service.py,sha256=IVBqAjeHtnvdXhUI_cJ0BYh1zprM7GXlfXjGmBKh-CA,943 examples/ping_pong/settings.ini,sha256=ZZXRX6bxMD9JXtkWtk5ktm3cBlZds1nO7Ips0g32Hc0,118 wolverine/__init__.py,sha256=J1vobd7bGvthpRWCoQ1MWGWwtob9ClhCE8WDpV95opQ,3664 wolverine/settings.ini,sha256=fhHyYxq76bAYfnIeSevRsmvp--oy3HarBuJTDey0iiA,106 wolverine/discovery/__init__.py,sha256=ES2J7U_XiKXHQOJUSpErIKnI4HEo5WJxM7JCyPFV68Q,407 wolverine/discovery/consul.ini,sha256=5jaTuq0eI2e0JciyM7CCiyotj0nrvDBIIehc2A04pMU,79 wolverine/discovery/consul.py,sha256=tLWoxtMFigm0dskPF9WhCk2pfgeP1dg-xu5NdBWZahg,12747 wolverine/gateway/__init__.py,sha256=kIR9-FS1BhMC3a0O6VhirPYQpBNJBw9dSjA_N7xKlqU,3853 wolverine/gateway/settings.ini,sha256=3m-RXzgxMSxjz_KBFS55-1FbOIElpI_wgb8WuPQBI5c,66 wolverine/module/__init__.py,sha256=TxfSVtGdB6MsUvOx2sOalyD5Wow1n1tJBdO5BErG4jg,455 wolverine/module/controller/__init__.py,sha256=GLuHa_6QtHAyKSq0NmGdwKqCMxAm0SapEXtXszIvre0,3568 wolverine/module/controller/zhelpers.py,sha256=U43bDs3gAQEffi5L4-4TZxteU-nmKtBAYbrUdElEJXo,2350 wolverine/module/controller/zmq.py,sha256=mp5ZYMpRaCj1KuAU3RBOEbDXOTI_lZTTZnW6AVWqVnI,9391 wolverine/module/service/__init__.py,sha256=J1i9rGzNsDnQk_aYQE5yyBrtzgYi0t_8Uap4zp9nYEU,1477 wolverine/routers/__init__.py,sha256=YHvkwBv9gYBYHf3jkFFaWWbdrpluxf_9bchSaEhbtJ4,7323 wolverine/shell/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 wolverine/shell/__main__.py,sha256=8ybskwxP9YySFAR3DPCxPUKjqBfUdUWVMb8p8Y9mKUo,1764 wolverine/shell/settings.ini,sha256=jOtF7S1X7i5K9sFjrgdhIN4HP5DE0W8dgmmDtBUoZEI,56 wolverine/test/__init__.py,sha256=Wa3x4lV2EMaZSkMdp167Rbw2qyDlYoSTTRFOWttn82c,177 wolverine/web/__init__.py,sha256=wxfw6Gakmkb9UU6xqGeZQzVeLx-_TJd0yzf164RMFlI,3332 wolverine/web/__main__.py,sha256=OW8bvrdBw6I36IDCEgZbdCi3qG2PX3Z6AzBbfnt6oms,1247 wolverine/web/settings.ini,sha256=Ae6no_3Re84xCBG0wPjYjtHMdWFfJUsvfsDwCMxYaFY,172 wolverine-0.2.2.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 wolverine-0.2.2.dist-info/METADATA,sha256=rAk59oRzSBa6rDUS8pwoXCNTJZDTOvr5s8EKJg_jZV4,442 wolverine-0.2.2.dist-info/RECORD,, wolverine-0.2.2.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 wolverine-0.2.2.dist-info/metadata.json,sha256=xIEGofVvWvdi6GhsZMl92NJa_DLCd0dch5jPrn0yO_c,606 wolverine-0.2.2.dist-info/top_level.txt,sha256=SvuOPPAwamNTU6Up6w869gVgIaZYbq2lXcIPctgw8wc,19 PKPG2examples/__init__.pyPKPGuvv3examples/ping_pong/settings.iniPKsTG|examples/ping_pong/service.pyPK=SGAUUexamples/ping_pong/__main__.pyPK TG%i##a examples/ping_pong/__init__.pyPKPGjj.wolverine/settings.iniPKNXG)PP>/wolverine/__init__.pyPK@TG=wolverine/routers/__init__.pyPK5PG \Zwolverine/module/__init__.pyPKSGvHo. . '\wolverine/module/controller/zhelpers.pyPKثTGh$$" fwolverine/module/controller/zmq.pyPKTGr 'wolverine/module/controller/__init__.pyPK[TG3$/wolverine/module/service/__init__.pyPKXG546wolverine/test/__init__.pyPK ZGbwolverine/web/settings.iniPK1ZGAS wolverine/web/__main__.pyPK ZGׇ  wolverine/web/__init__.pyPKZGgBBTwolverine/gateway/settings.iniPKJZG0Ft  ҳwolverine/gateway/__init__.pyPKPGhH88wolverine/shell/settings.iniPKPG[@wolverine/shell/__main__.pyPKPG2wolverine/shell/__init__.pyPK9VGD(ewolverine/discovery/__init__.pyPKJTGp11wolverine/discovery/consul.pyPKPGŹaOOwolverine/discovery/consul.iniPK)ZG^- )Hwolverine-0.2.2.dist-info/DESCRIPTION.rstPK)ZG;KG$^^'wolverine-0.2.2.dist-info/metadata.jsonPK)ZG3 /'<wolverine-0.2.2.dist-info/top_level.txtPK)ZG''\\wolverine-0.2.2.dist-info/WHEELPK)ZG搅"-wolverine-0.2.2.dist-info/METADATAPK)ZGxe_ _ 'wolverine-0.2.2.dist-info/RECORDPK;