add commad line tool
This commit is contained in:
parent
1e3bf5a196
commit
64427aba50
|
@ -157,4 +157,6 @@ cython_debug/
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
.idea/
|
||||||
|
|
||||||
|
*.pypirc
|
|
@ -0,0 +1,3 @@
|
||||||
|
include *.py
|
||||||
|
recursive-include namekoplus/chassis *.py
|
||||||
|
recursive-include namekoplus/templates *.py *.yml
|
22
README.md
22
README.md
|
@ -1,2 +1,20 @@
|
||||||
# nameko-plus
|
# namekoplus
|
||||||
A Python distributed microservice solution
|
A lightweight Python distributed microservice solution
|
||||||
|
|
||||||
|
## Document
|
||||||
|
|
||||||
|
[中文文档](https://doc.bearcatlog.com/)
|
||||||
|
|
||||||
|
## Command Line Tool Usage
|
||||||
|
|
||||||
|
### Checkout Command
|
||||||
|
|
||||||
|
```shell
|
||||||
|
namekoplus --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Initialize a nameko service from templates
|
||||||
|
|
||||||
|
```shell
|
||||||
|
namekoplus init --directory <dir_name> --type <template_type>
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from namekoplus.chassis.chassis import *
|
|
@ -0,0 +1,20 @@
|
||||||
|
def init_statsd(prefix=None, host=None, port=8125):
|
||||||
|
from statsd import StatsClient
|
||||||
|
statsd = StatsClient(host, port, prefix=prefix)
|
||||||
|
return statsd
|
||||||
|
|
||||||
|
|
||||||
|
def init_logger():
|
||||||
|
import logging
|
||||||
|
from logstash_formatter import LogstashFormatterV1
|
||||||
|
logger = logging.getLogger()
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
formatter = LogstashFormatterV1()
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
def init_sentry():
|
||||||
|
from nameko_sentry import SentryReporter
|
||||||
|
return SentryReporter()
|
|
@ -0,0 +1,85 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def status(status_msg: str, newline: bool = False, quiet: bool = False):
|
||||||
|
msg_suffix = ' ...' if not newline else ' ...\n'
|
||||||
|
click.echo(status_msg + msg_suffix)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except Exception as e:
|
||||||
|
if not quiet:
|
||||||
|
click.echo(' FAILED\n')
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
if not quiet:
|
||||||
|
click.echo(' Done\n')
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_directory() -> str:
|
||||||
|
"""Return the directory where nameko_plus setup templates are found.
|
||||||
|
|
||||||
|
This method is used by the nameko_plus ``init`` commands.
|
||||||
|
"""
|
||||||
|
import namekoplus
|
||||||
|
|
||||||
|
package_dir = os.path.abspath(os.path.dirname(namekoplus.__file__))
|
||||||
|
return os.path.join(package_dir, 'templates')
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def cli():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('-d', '--directory',
|
||||||
|
required=True,
|
||||||
|
help='The directory name of nameko services')
|
||||||
|
@click.option('-f', '--type', '_type',
|
||||||
|
default='rpc',
|
||||||
|
show_default=True,
|
||||||
|
type=click.Choice(['rpc', 'event', 'http', 'timer'], case_sensitive=False),
|
||||||
|
help='The template type of nameko service')
|
||||||
|
def init(directory, _type):
|
||||||
|
"""
|
||||||
|
Initialize a new service via templates.
|
||||||
|
"""
|
||||||
|
if os.access(directory, os.F_OK) and os.listdir(directory):
|
||||||
|
click.echo('Directory {} already exists and is not empty'.format(directory), err=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
template_dir = os.path.join(get_template_directory(), _type)
|
||||||
|
if not os.access(template_dir, os.F_OK):
|
||||||
|
click.echo('No such template type {}'.format(_type), err=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建目录
|
||||||
|
if not os.access(directory, os.F_OK):
|
||||||
|
with status(f'Creating directory {os.path.abspath(directory)!r}'):
|
||||||
|
os.makedirs(directory)
|
||||||
|
|
||||||
|
# 把 templates 放入新建的目录
|
||||||
|
for file_ in os.listdir(template_dir):
|
||||||
|
if file_ == '__pycache__':
|
||||||
|
continue
|
||||||
|
src_file_path = os.path.join(template_dir, file_)
|
||||||
|
output_file = os.path.join(directory, file_)
|
||||||
|
with status(f'Generating {os.path.abspath(output_file)}'):
|
||||||
|
shutil.copy(src_file_path, output_file)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
def start():
|
||||||
|
"""
|
||||||
|
Start a middleware, such as RabbitMQ.
|
||||||
|
"""
|
||||||
|
click.echo('Initialized the database')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cli()
|
|
@ -0,0 +1,24 @@
|
||||||
|
AMQP_URI: pyamqp://${RABBIT_USER:guest}:${RABBIT_PASSWORD:guest}@${RABBIT_HOST:localhost}:${RABBIT_PORT:5672}/
|
||||||
|
RPC_EXCHANGE: 'nameko-rpc'
|
||||||
|
|
||||||
|
max_workers: 10
|
||||||
|
parent_calls_tracked: 20
|
||||||
|
|
||||||
|
LOGGING:
|
||||||
|
version: 1
|
||||||
|
formatters:
|
||||||
|
tracer:
|
||||||
|
(): nameko_tracer.formatters.PrettyJSONFormatter
|
||||||
|
handlers:
|
||||||
|
tracer:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: tracer
|
||||||
|
loggers:
|
||||||
|
nameko_tracer:
|
||||||
|
level: INFO
|
||||||
|
handlers: [tracer]
|
||||||
|
|
||||||
|
SENTRY:
|
||||||
|
DSN: ${SENTRY_DSN}
|
||||||
|
CLIENT_CONFIG:
|
||||||
|
site: ${SENTRY_SITE}
|
|
@ -0,0 +1,60 @@
|
||||||
|
from nameko.events import EventDispatcher, event_handler
|
||||||
|
from nameko.rpc import rpc
|
||||||
|
from nameko_tracer import Tracer
|
||||||
|
from namekoplus import init_statsd, init_sentry
|
||||||
|
|
||||||
|
|
||||||
|
class EventPublisherService:
|
||||||
|
name = "publisher_service"
|
||||||
|
|
||||||
|
dispatch = EventDispatcher()
|
||||||
|
|
||||||
|
tracer = Tracer()
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
@statsd.timer('publish')
|
||||||
|
def publish(self, event_type, payload):
|
||||||
|
self.dispatch(event_type, payload)
|
||||||
|
|
||||||
|
|
||||||
|
class AnEventListenerService:
|
||||||
|
name = "an_event_listener_service"
|
||||||
|
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@event_handler("publisher_service", "an_event")
|
||||||
|
@statsd.timer('consume_an_event')
|
||||||
|
def consume_an_event(self, payload):
|
||||||
|
print("service {} received:".format(self.name), payload)
|
||||||
|
|
||||||
|
|
||||||
|
class AnotherEventListenerService:
|
||||||
|
name = "another_event_listener_service"
|
||||||
|
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@event_handler("publisher_service", "another_event")
|
||||||
|
@statsd.timer('consume_another_event')
|
||||||
|
def consume_another_event(self, payload):
|
||||||
|
print("service {} received:".format(self.name), payload)
|
||||||
|
|
||||||
|
|
||||||
|
class ListenBothEventsService:
|
||||||
|
name = "listen_both_events_service"
|
||||||
|
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@event_handler("publisher_service", "an_event")
|
||||||
|
@statsd.timer('consume_an_event')
|
||||||
|
def consume_an_event(self, payload):
|
||||||
|
print("service {} received:".format(self.name), payload)
|
||||||
|
|
||||||
|
@event_handler("publisher_service", "another_event")
|
||||||
|
@statsd.timer('consume_another_event')
|
||||||
|
def consume_another_event(self, payload):
|
||||||
|
print("service {} received:".format(self.name), payload)
|
|
@ -0,0 +1,24 @@
|
||||||
|
AMQP_URI: pyamqp://${RABBIT_USER:guest}:${RABBIT_PASSWORD:guest}@${RABBIT_HOST:localhost}:${RABBIT_PORT:5672}/
|
||||||
|
RPC_EXCHANGE: 'nameko-rpc'
|
||||||
|
|
||||||
|
max_workers: 10
|
||||||
|
parent_calls_tracked: 20
|
||||||
|
|
||||||
|
LOGGING:
|
||||||
|
version: 1
|
||||||
|
formatters:
|
||||||
|
tracer:
|
||||||
|
(): nameko_tracer.formatters.PrettyJSONFormatter
|
||||||
|
handlers:
|
||||||
|
tracer:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: tracer
|
||||||
|
loggers:
|
||||||
|
nameko_tracer:
|
||||||
|
level: INFO
|
||||||
|
handlers: [tracer]
|
||||||
|
|
||||||
|
SENTRY:
|
||||||
|
DSN: ${SENTRY_DSN}
|
||||||
|
CLIENT_CONFIG:
|
||||||
|
site: ${SENTRY_SITE}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import json
|
||||||
|
from nameko.web.handlers import http
|
||||||
|
from nameko_tracer import Tracer
|
||||||
|
from werkzeug.wrappers import Response
|
||||||
|
from namekoplus import init_statsd, init_sentry
|
||||||
|
|
||||||
|
|
||||||
|
class HttpDemoService:
|
||||||
|
name = "http_demo_service"
|
||||||
|
|
||||||
|
tracer = Tracer()
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@http("GET", "/broken")
|
||||||
|
@statsd.timer('broken')
|
||||||
|
def broken(self, request):
|
||||||
|
raise ConnectionRefusedError()
|
||||||
|
|
||||||
|
@http('GET', '/books/<string:uuid>')
|
||||||
|
@statsd.timer('demo_get')
|
||||||
|
def demo_get(self, request, uuid):
|
||||||
|
data = {'id': uuid, 'title': 'The unbearable lightness of being',
|
||||||
|
'author': 'Milan Kundera'}
|
||||||
|
return Response(json.dumps({'book': data}),
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
|
@http('POST', '/books')
|
||||||
|
@statsd.timer('demo_post')
|
||||||
|
def demo_post(self, request):
|
||||||
|
return Response(json.dumps({'book': request.data.decode()}),
|
||||||
|
mimetype='application/json')
|
|
@ -0,0 +1,24 @@
|
||||||
|
AMQP_URI: pyamqp://${RABBIT_USER:guest}:${RABBIT_PASSWORD:guest}@${RABBIT_HOST:localhost}:${RABBIT_PORT:5672}/
|
||||||
|
RPC_EXCHANGE: 'nameko-rpc'
|
||||||
|
|
||||||
|
max_workers: 10
|
||||||
|
parent_calls_tracked: 20
|
||||||
|
|
||||||
|
LOGGING:
|
||||||
|
version: 1
|
||||||
|
formatters:
|
||||||
|
tracer:
|
||||||
|
(): nameko_tracer.formatters.PrettyJSONFormatter
|
||||||
|
handlers:
|
||||||
|
tracer:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: tracer
|
||||||
|
loggers:
|
||||||
|
nameko_tracer:
|
||||||
|
level: INFO
|
||||||
|
handlers: [tracer]
|
||||||
|
|
||||||
|
SENTRY:
|
||||||
|
DSN: ${SENTRY_DSN}
|
||||||
|
CLIENT_CONFIG:
|
||||||
|
site: ${SENTRY_SITE}
|
|
@ -0,0 +1,31 @@
|
||||||
|
from nameko.rpc import rpc, ServiceRpc
|
||||||
|
from nameko_tracer import Tracer
|
||||||
|
from namekoplus import init_statsd, init_sentry
|
||||||
|
|
||||||
|
|
||||||
|
class RpcResponderDemoService:
|
||||||
|
name = "rpc_responder_demo_service"
|
||||||
|
|
||||||
|
tracer = Tracer()
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
@statsd.timer('hello')
|
||||||
|
def hello(self, name):
|
||||||
|
return "Hello, {}!".format(name)
|
||||||
|
|
||||||
|
|
||||||
|
class RpcCallerDemoService:
|
||||||
|
name = "rpc_caller_demo_service"
|
||||||
|
|
||||||
|
remote = ServiceRpc("rpc_responder_demo_service")
|
||||||
|
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
@statsd.timer('remote_hello')
|
||||||
|
def remote_hello(self, value="John Doe"):
|
||||||
|
res = u"{}".format(value)
|
||||||
|
return self.remote.hello(res)
|
|
@ -0,0 +1,24 @@
|
||||||
|
AMQP_URI: pyamqp://${RABBIT_USER:guest}:${RABBIT_PASSWORD:guest}@${RABBIT_HOST:localhost}:${RABBIT_PORT:5672}/
|
||||||
|
RPC_EXCHANGE: 'nameko-rpc'
|
||||||
|
|
||||||
|
max_workers: 10
|
||||||
|
parent_calls_tracked: 20
|
||||||
|
|
||||||
|
LOGGING:
|
||||||
|
version: 1
|
||||||
|
formatters:
|
||||||
|
tracer:
|
||||||
|
(): nameko_tracer.formatters.PrettyJSONFormatter
|
||||||
|
handlers:
|
||||||
|
tracer:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: tracer
|
||||||
|
loggers:
|
||||||
|
nameko_tracer:
|
||||||
|
level: INFO
|
||||||
|
handlers: [tracer]
|
||||||
|
|
||||||
|
SENTRY:
|
||||||
|
DSN: ${SENTRY_DSN}
|
||||||
|
CLIENT_CONFIG:
|
||||||
|
site: ${SENTRY_SITE}
|
|
@ -0,0 +1,17 @@
|
||||||
|
from nameko.timer import timer
|
||||||
|
from nameko_tracer import Tracer
|
||||||
|
from namekoplus import init_statsd, init_sentry
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
name = "service"
|
||||||
|
|
||||||
|
tracer = Tracer()
|
||||||
|
sentry = init_sentry()
|
||||||
|
statsd = init_statsd('statsd_prefix', 'statsd_host', 'statsd_port')
|
||||||
|
|
||||||
|
@timer(interval=1)
|
||||||
|
@statsd.timer('ping')
|
||||||
|
def ping(self):
|
||||||
|
# method executed every second
|
||||||
|
print("pong")
|
|
@ -0,0 +1,71 @@
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
from codecs import open
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
here = path.abspath(path.dirname(__file__))
|
||||||
|
|
||||||
|
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||||
|
long_description = f.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='namekoplus',
|
||||||
|
version='0.1.0',
|
||||||
|
description='A lightweight Python distributed microservice solution',
|
||||||
|
long_description=long_description,
|
||||||
|
url='https://github.com/Bryanthelol/namekoplus',
|
||||||
|
|
||||||
|
author='Bryant He',
|
||||||
|
author_email='bryantsisu@qq.com',
|
||||||
|
|
||||||
|
license='MIT',
|
||||||
|
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 3 - Alpha',
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
|
],
|
||||||
|
platforms='any',
|
||||||
|
python_requires='>=3',
|
||||||
|
|
||||||
|
keywords='lightweight python distributed microservice solution',
|
||||||
|
|
||||||
|
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
|
||||||
|
include_package_data=True,
|
||||||
|
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'namekoplus = namekoplus.command:cli',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
install_requires=[
|
||||||
|
'nameko==3.0.0rc11',
|
||||||
|
'nameko-sentry==1.0.0',
|
||||||
|
'nameko-tracer==1.4.0',
|
||||||
|
'click==8.1.5',
|
||||||
|
'pytest==7.4.0',
|
||||||
|
'environs==9.5.0',
|
||||||
|
'logstash_formatter==0.5.17',
|
||||||
|
'statsd==4.0.1',
|
||||||
|
'tenacity==8.2.2',
|
||||||
|
'cachetools==5.3.0',
|
||||||
|
'circuitbreaker==2.0.0',
|
||||||
|
'shortuuid==1.0.11',
|
||||||
|
'cryptography'
|
||||||
|
],
|
||||||
|
extras_require={
|
||||||
|
'apiflask': ['apiflask>=1.3.1',
|
||||||
|
'gevent>=22.10.2',
|
||||||
|
'gunicorn==20.1.0'],
|
||||||
|
'rocketry': ['rocketry==2.4.0'],
|
||||||
|
'gutter': ['gutter==0.5.0'],
|
||||||
|
'mysql': ['pymysql==1.0.3',
|
||||||
|
'sqlalchemy==2.0.15',
|
||||||
|
'sqlacodegen==2.3.0',
|
||||||
|
'alembic==1.11.1'],
|
||||||
|
'dev': ['mako==1.2.4'],
|
||||||
|
},
|
||||||
|
)
|
Loading…
Reference in New Issue