Cic notify build
This commit is contained in:
parent
85f06aa445
commit
cebdb24ed6
@ -3,6 +3,7 @@ include:
|
|||||||
- local: 'apps/contract-migration/.gitlab-ci.yml'
|
- local: 'apps/contract-migration/.gitlab-ci.yml'
|
||||||
- local: 'apps/cic-eth/.gitlab-ci.yml'
|
- local: 'apps/cic-eth/.gitlab-ci.yml'
|
||||||
- local: 'apps/cic-ussd/.gitlab-ci.yml'
|
- local: 'apps/cic-ussd/.gitlab-ci.yml'
|
||||||
|
- local: 'apps/cic-notify/.gitlab-ci.yml'
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
|||||||
[submodule "apps/cic-notify"]
|
|
||||||
path = apps/cic-notify
|
|
||||||
url = git@gitlab.com:grassrootseconomics/cic-notify.git
|
|
||||||
[submodule "apps/cic-cache"]
|
[submodule "apps/cic-cache"]
|
||||||
path = apps/cic-cache
|
path = apps/cic-cache
|
||||||
url = git@gitlab.com:grassrootseconomics/cic-cache.git
|
url = git@gitlab.com:grassrootseconomics/cic-cache.git
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Subproject commit ba8e5989fa369782ed086e8b10bdd3524e353fe4
|
|
4
apps/cic-notify/.config/africastalking.ini
Normal file
4
apps/cic-notify/.config/africastalking.ini
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[AFRICASTALKING]
|
||||||
|
api_username = foo
|
||||||
|
api_key = bar
|
||||||
|
api_sender_id = baz
|
3
apps/cic-notify/.config/celery.ini
Normal file
3
apps/cic-notify/.config/celery.ini
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[celery]
|
||||||
|
broker_url = redis://
|
||||||
|
result_url = redis://
|
10
apps/cic-notify/.config/database.ini
Normal file
10
apps/cic-notify/.config/database.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[DATABASE]
|
||||||
|
user = postgres
|
||||||
|
password =
|
||||||
|
host = localhost
|
||||||
|
port = 5432
|
||||||
|
name = /tmp/cic-notify.db
|
||||||
|
#engine = postgresql
|
||||||
|
#driver = psycopg2
|
||||||
|
engine = sqlite
|
||||||
|
driver = pysqlite
|
4
apps/cic-notify/.config/tasks.ini
Normal file
4
apps/cic-notify/.config/tasks.ini
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[TASKS]
|
||||||
|
africastalking = cic_notify.tasks.sms.africastalking
|
||||||
|
db = cic_notify.tasks.sms.db
|
||||||
|
log = cic_notify.tasks.sms.log
|
4
apps/cic-notify/.config/test/africastalking.ini
Normal file
4
apps/cic-notify/.config/test/africastalking.ini
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[AFRICASTALKING]
|
||||||
|
api_username = foo
|
||||||
|
api_key = bar
|
||||||
|
api_sender_id = baz
|
3
apps/cic-notify/.config/test/celery.ini
Normal file
3
apps/cic-notify/.config/test/celery.ini
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[celery]
|
||||||
|
broker_url = filesystem://
|
||||||
|
result_url = filesystem://
|
10
apps/cic-notify/.config/test/database.ini
Normal file
10
apps/cic-notify/.config/test/database.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[DATABASE]
|
||||||
|
user = postgres
|
||||||
|
password =
|
||||||
|
host = localhost
|
||||||
|
port = 5432
|
||||||
|
name = /tmp/cic-notify.db
|
||||||
|
#engine = postgresql
|
||||||
|
#driver = psycopg2
|
||||||
|
engine = sqlite
|
||||||
|
driver = pysqlite
|
4
apps/cic-notify/.config/test/tasks.ini
Normal file
4
apps/cic-notify/.config/test/tasks.ini
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[TASKS]
|
||||||
|
africastalking = cic_notify.tasks.sms.africastalking
|
||||||
|
db = cic_notify.tasks.sms.db
|
||||||
|
log = cic_notify.tasks.sms.log
|
4
apps/cic-notify/.gitignore
vendored
Normal file
4
apps/cic-notify/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
venv/
|
||||||
|
.idea/
|
23
apps/cic-notify/.gitlab-ci.yml
Normal file
23
apps/cic-notify/.gitlab-ci.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.cic_notify_variables:
|
||||||
|
variables:
|
||||||
|
APP_NAME: cic-notify
|
||||||
|
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
|
||||||
|
|
||||||
|
.this_changes_target:
|
||||||
|
rules:
|
||||||
|
- changes:
|
||||||
|
- $CONTEXT/$APP_NAME/*
|
||||||
|
|
||||||
|
build-mr-cic-notify:
|
||||||
|
extends:
|
||||||
|
- .this_changes_target
|
||||||
|
- .py_build_merge_request
|
||||||
|
- .cic_notify_variables
|
||||||
|
|
||||||
|
build-push-cic-notify:
|
||||||
|
extends:
|
||||||
|
- .this_changes_target
|
||||||
|
- .py_build_push
|
||||||
|
- .cic_notify_variables
|
||||||
|
|
||||||
|
|
24
apps/cic-notify/CHANGELOG
Normal file
24
apps/cic-notify/CHANGELOG
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
- 0.3.2
|
||||||
|
* Relax dependencies to compatible
|
||||||
|
- 0.3.1
|
||||||
|
* Upgrade dependencies
|
||||||
|
- 0.3.0
|
||||||
|
* Rehabilitate africastalking script
|
||||||
|
* Add queue identifiers
|
||||||
|
- 0.2.0
|
||||||
|
* Remove unnecessarily complicated code
|
||||||
|
- 0.1.0
|
||||||
|
* Simplify tasks
|
||||||
|
- 0.0.4
|
||||||
|
* Add python api
|
||||||
|
* Add send sms tool
|
||||||
|
* Add database to tasker script
|
||||||
|
* Rename tasker script
|
||||||
|
* Fix no-commit bug in db notification
|
||||||
|
- 0.0.3
|
||||||
|
* Add sms notify to db
|
||||||
|
- 0.0.2
|
||||||
|
* Re-introduce log sms notify handler
|
||||||
|
- 0.0.1
|
||||||
|
* Port code from cic-ussd
|
||||||
|
* Make dynamic task module loader
|
1
apps/cic-notify/cic_notify/__init__.py
Normal file
1
apps/cic-notify/cic_notify/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .api import *
|
55
apps/cic-notify/cic_notify/api.py
Normal file
55
apps/cic-notify/cic_notify/api.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import celery
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.tasks import sms
|
||||||
|
|
||||||
|
app = celery.current_app
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
sms_tasks_matcher = r"^(cic_notify.tasks.sms)(\.\w+)?"
|
||||||
|
|
||||||
|
|
||||||
|
class Api:
|
||||||
|
# TODO: Implement callback strategy
|
||||||
|
def __init__(self, queue='cic-notify'):
|
||||||
|
"""
|
||||||
|
:param queue: The queue on which to execute notification tasks
|
||||||
|
:type queue: str
|
||||||
|
"""
|
||||||
|
registered_tasks = app.tasks
|
||||||
|
self.sms_tasks = []
|
||||||
|
|
||||||
|
for task in registered_tasks.keys():
|
||||||
|
logg.debug(f'Found: {task} {registered_tasks[task]}')
|
||||||
|
match = re.match(sms_tasks_matcher, task)
|
||||||
|
if match:
|
||||||
|
self.sms_tasks.append(task)
|
||||||
|
|
||||||
|
self.queue = queue
|
||||||
|
logg.info(f'api using queue: {self.queue}')
|
||||||
|
|
||||||
|
def sms(self, message, recipient):
|
||||||
|
"""This function chains all sms tasks in order to send a message, log and persist said data to disk
|
||||||
|
:param message: The message to be sent to the recipient.
|
||||||
|
:type message: str
|
||||||
|
:param recipient: The phone number of the recipient.
|
||||||
|
:type recipient: str
|
||||||
|
:return: a celery Task
|
||||||
|
:rtype: Celery.Task
|
||||||
|
"""
|
||||||
|
signatures = []
|
||||||
|
for task in self.sms_tasks:
|
||||||
|
signature = celery.signature(task)
|
||||||
|
signatures.append(signature)
|
||||||
|
signature_group = celery.group(signatures)
|
||||||
|
result = signature_group.apply_async(
|
||||||
|
args=[message, recipient],
|
||||||
|
queue=self.queue
|
||||||
|
)
|
||||||
|
return result
|
34
apps/cic-notify/cic_notify/db/__init__.py
Normal file
34
apps/cic-notify/cic_notify/db/__init__.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.db.models.base import SessionBase
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
def dsn_from_config(config):
|
||||||
|
scheme = config.get('DATABASE_ENGINE')
|
||||||
|
if config.get('DATABASE_DRIVER') != None:
|
||||||
|
scheme += '+{}'.format(config.get('DATABASE_DRIVER'))
|
||||||
|
|
||||||
|
dsn = ''
|
||||||
|
if config.get('DATABASE_ENGINE') == 'sqlite':
|
||||||
|
dsn = '{}:///{}'.format(
|
||||||
|
scheme,
|
||||||
|
config.get('DATABASE_NAME'),
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
dsn = '{}://{}:{}@{}:{}/{}'.format(
|
||||||
|
scheme,
|
||||||
|
config.get('DATABASE_USER'),
|
||||||
|
config.get('DATABASE_PASSWORD'),
|
||||||
|
config.get('DATABASE_HOST'),
|
||||||
|
config.get('DATABASE_PORT'),
|
||||||
|
config.get('DATABASE_NAME'),
|
||||||
|
)
|
||||||
|
logg.debug('parsed dsn from config: {}'.format(dsn))
|
||||||
|
return dsn
|
||||||
|
|
7
apps/cic-notify/cic_notify/db/enum.py
Normal file
7
apps/cic-notify/cic_notify/db/enum.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
class NotificationStatusEnum(enum.Enum):
|
||||||
|
UNKNOWN = 'UNKNOWN'
|
||||||
|
|
||||||
|
class NotificationTransportEnum(enum.Enum):
|
||||||
|
SMS = 'SMS'
|
1
apps/cic-notify/cic_notify/db/migrations/default/README
Normal file
1
apps/cic-notify/cic_notify/db/migrations/default/README
Normal file
@ -0,0 +1 @@
|
|||||||
|
Generic single-database configuration.
|
85
apps/cic-notify/cic_notify/db/migrations/default/alembic.ini
Normal file
85
apps/cic-notify/cic_notify/db/migrations/default/alembic.ini
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# path to migration scripts
|
||||||
|
script_location = migrations
|
||||||
|
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# timezone to use when rendering the date
|
||||||
|
# within the migration file as well as the filename.
|
||||||
|
# string value is passed to dateutil.tz.gettz()
|
||||||
|
# leave blank for localtime
|
||||||
|
# timezone =
|
||||||
|
|
||||||
|
# max length of characters to apply to the
|
||||||
|
# "slug" field
|
||||||
|
# truncate_slug_length = 40
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
# set to 'true' to allow .pyc and .pyo files without
|
||||||
|
# a source .py file to be detected as revisions in the
|
||||||
|
# versions/ directory
|
||||||
|
# sourceless = false
|
||||||
|
|
||||||
|
# version location specification; this defaults
|
||||||
|
# to migrations/versions. When using multiple version
|
||||||
|
# directories, initial revisions must be specified with --version-path
|
||||||
|
# version_locations = %(here)s/bar %(here)s/bat migrations/versions
|
||||||
|
|
||||||
|
# the output encoding used when revision files
|
||||||
|
# are written from script.py.mako
|
||||||
|
# output_encoding = utf-8
|
||||||
|
|
||||||
|
sqlalchemy.url = postgres+psycopg2://postgres@localhost/cic-notify
|
||||||
|
|
||||||
|
|
||||||
|
[post_write_hooks]
|
||||||
|
# post_write_hooks defines scripts or Python functions that are run
|
||||||
|
# on newly generated revision scripts. See the documentation for further
|
||||||
|
# detail and examples
|
||||||
|
|
||||||
|
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||||
|
# hooks=black
|
||||||
|
# black.type=console_scripts
|
||||||
|
# black.entrypoint=black
|
||||||
|
# black.options=-l 79
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
77
apps/cic-notify/cic_notify/db/migrations/default/env.py
Normal file
77
apps/cic-notify/cic_notify/db/migrations/default/env.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
target_metadata = None
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online():
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
connectable = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section),
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection, target_metadata=target_metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
@ -0,0 +1,24 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
branch_labels = ${repr(branch_labels)}
|
||||||
|
depends_on = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
@ -0,0 +1,48 @@
|
|||||||
|
"""Notify sms log
|
||||||
|
|
||||||
|
Revision ID: b2aedf79b0b2
|
||||||
|
Revises:
|
||||||
|
Create Date: 2020-10-11 15:59:02.765157
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b2aedf79b0b2'
|
||||||
|
down_revision = None
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
status_enum = sa.Enum(
|
||||||
|
'UNKNOWN', # the state of the message is not known
|
||||||
|
name='notification_status',
|
||||||
|
)
|
||||||
|
|
||||||
|
transport_enum = sa.Enum(
|
||||||
|
'SMS',
|
||||||
|
name='notification_transport',
|
||||||
|
)
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('notification',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('transport', transport_enum, nullable=False),
|
||||||
|
sa.Column('status', status_enum, nullable=False),
|
||||||
|
sa.Column('status_code', sa.String(), nullable=True),
|
||||||
|
sa.Column('status_serial', sa.Integer(), nullable=False, server_default='0'),
|
||||||
|
sa.Column('recipient', sa.String(), nullable=False),
|
||||||
|
sa.Column('created', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('updated', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('message', sa.String(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
)
|
||||||
|
op.create_index('notification_recipient_transport_idx', 'notification', ['transport', 'recipient'], schema=None, unique=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_index('notification_recipient_transport_idx')
|
||||||
|
op.drop_table('notification')
|
||||||
|
status_enum.drop(op.get_bind(), checkfirst=False)
|
||||||
|
transport_enum.drop(op.get_bind(), checkfirst=False)
|
0
apps/cic-notify/cic_notify/db/models/__init__.py
Normal file
0
apps/cic-notify/cic_notify/db/models/__init__.py
Normal file
40
apps/cic-notify/cic_notify/db/models/base.py
Normal file
40
apps/cic-notify/cic_notify/db/models/base.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# third-party imports
|
||||||
|
from sqlalchemy import Column, Integer
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
Model = declarative_base(name='Model')
|
||||||
|
|
||||||
|
|
||||||
|
class SessionBase(Model):
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
|
||||||
|
engine = None
|
||||||
|
session = None
|
||||||
|
query = None
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_session():
|
||||||
|
session = sessionmaker(bind=SessionBase.engine)
|
||||||
|
SessionBase.session = session()
|
||||||
|
return SessionBase.session
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _set_engine(engine):
|
||||||
|
SessionBase.engine = engine
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build():
|
||||||
|
Model.metadata.create_all(bind=SessionBase.engine)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def connect(dsn):
|
||||||
|
e = create_engine(dsn)
|
||||||
|
SessionBase._set_engine(e)
|
27
apps/cic-notify/cic_notify/db/models/notification.py
Normal file
27
apps/cic-notify/cic_notify/db/models/notification.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# standard imports
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
from sqlalchemy import Enum, Column, String, DateTime
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from .base import SessionBase
|
||||||
|
from ..enum import NotificationStatusEnum, NotificationTransportEnum
|
||||||
|
|
||||||
|
|
||||||
|
class Notification(SessionBase):
|
||||||
|
__tablename__ = 'notification'
|
||||||
|
|
||||||
|
transport = Column(Enum(NotificationTransportEnum))
|
||||||
|
status = Column(Enum(NotificationStatusEnum))
|
||||||
|
recipient = Column(String)
|
||||||
|
message = Column(String)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, transport, recipient, message, **kwargs):
|
||||||
|
super(Notification, self).__init__(**kwargs)
|
||||||
|
self.transport = transport
|
||||||
|
self.recipient = recipient
|
||||||
|
self.message = message
|
||||||
|
self.status = NotificationStatusEnum.UNKNOWN
|
11
apps/cic-notify/cic_notify/error.py
Normal file
11
apps/cic-notify/cic_notify/error.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class NotInitializedError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AlreadyInitializedError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PleaseCommitFirstError(Exception):
|
||||||
|
"""Raised when there exists uncommitted changes in the code while trying to build out the package."""
|
||||||
|
pass
|
110
apps/cic-notify/cic_notify/runnable/tasker.py
Normal file
110
apps/cic-notify/cic_notify/runnable/tasker.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import importlib
|
||||||
|
import argparse
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import celery
|
||||||
|
import confini
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.db.models.base import SessionBase
|
||||||
|
from cic_notify.db import dsn_from_config
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.WARNING)
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
config_dir = os.path.join('/usr/local/etc/cic-notify')
|
||||||
|
|
||||||
|
argparser = argparse.ArgumentParser()
|
||||||
|
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||||
|
argparser.add_argument('-q', type=str, default='cic-notify', help='queue name for worker tasks')
|
||||||
|
argparser.add_argument('-v', action='store_true', help='be verbose')
|
||||||
|
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||||
|
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||||
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
if args.vv:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
elif args.v:
|
||||||
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
|
config = confini.Config(args.c, args.env_prefix)
|
||||||
|
config.process()
|
||||||
|
config.censor('PASSWORD', 'DATABASE')
|
||||||
|
|
||||||
|
# connect to database
|
||||||
|
dsn = dsn_from_config(config)
|
||||||
|
SessionBase.connect(dsn)
|
||||||
|
|
||||||
|
# verify database connection with minimal sanity query
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
session.execute('select version_num from alembic_version')
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
# set up celery
|
||||||
|
app = celery.Celery(__name__)
|
||||||
|
|
||||||
|
broker = config.get('CELERY_BROKER_URL')
|
||||||
|
if broker[:4] == 'file':
|
||||||
|
bq = tempfile.mkdtemp()
|
||||||
|
bp = tempfile.mkdtemp()
|
||||||
|
app.conf.update({
|
||||||
|
'broker_url': broker,
|
||||||
|
'broker_transport_options': {
|
||||||
|
'data_folder_in': bq,
|
||||||
|
'data_folder_out': bq,
|
||||||
|
'data_folder_processed': bp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
logg.warning('celery broker dirs queue i/o {} processed {}, will NOT be deleted on shutdown'.format(bq, bp))
|
||||||
|
else:
|
||||||
|
app.conf.update({
|
||||||
|
'broker_url': broker,
|
||||||
|
})
|
||||||
|
|
||||||
|
result = config.get('CELERY_RESULT_URL')
|
||||||
|
if result[:4] == 'file':
|
||||||
|
rq = tempfile.mkdtemp()
|
||||||
|
app.conf.update({
|
||||||
|
'result_backend': 'file://{}'.format(rq),
|
||||||
|
})
|
||||||
|
logg.warning('celery backend store dir {} created, will NOT be deleted on shutdown'.format(rq))
|
||||||
|
else:
|
||||||
|
app.conf.update({
|
||||||
|
'result_backend': result,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
for key in config.store.keys():
|
||||||
|
if key[:5] == 'TASKS':
|
||||||
|
logg.info(f'adding sms task from {key}')
|
||||||
|
module = importlib.import_module(config.store[key])
|
||||||
|
if key == 'TASKS_AFRICASTALKING':
|
||||||
|
africastalking_notifier = module.AfricasTalkingNotifier
|
||||||
|
africastalking_notifier.initialize(
|
||||||
|
config.get('AFRICASTALKING_API_USERNAME'),
|
||||||
|
config.get('AFRICASTALKING_API_KEY'),
|
||||||
|
config.get('AFRICASTALKING_API_SENDER_ID')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argv = ['worker']
|
||||||
|
if args.vv:
|
||||||
|
argv.append('--loglevel=DEBUG')
|
||||||
|
elif args.v:
|
||||||
|
argv.append('--loglevel=INFO')
|
||||||
|
argv.append('-Q')
|
||||||
|
argv.append(args.q)
|
||||||
|
argv.append('-n')
|
||||||
|
argv.append(args.q)
|
||||||
|
|
||||||
|
app.worker_main(argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
apps/cic-notify/cic_notify/tasks/__init__.py
Normal file
1
apps/cic-notify/cic_notify/tasks/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import sms
|
12
apps/cic-notify/cic_notify/tasks/sms/__init__.py
Normal file
12
apps/cic-notify/cic_notify/tasks/sms/__init__.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# standard imports
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
import celery
|
||||||
|
|
||||||
|
celery_app = celery.current_app
|
||||||
|
|
||||||
|
from .africastalking import send
|
||||||
|
from .db import persist_notification
|
||||||
|
from .log import log
|
69
apps/cic-notify/cic_notify/tasks/sms/africastalking.py
Normal file
69
apps/cic-notify/cic_notify/tasks/sms/africastalking.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# third party imports
|
||||||
|
import celery
|
||||||
|
import africastalking
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.error import NotInitializedError, AlreadyInitializedError
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
celery_app = celery.current_app
|
||||||
|
|
||||||
|
|
||||||
|
class AfricasTalkingNotifier:
|
||||||
|
initiated = None
|
||||||
|
sender_id = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not self.initiated:
|
||||||
|
raise NotInitializedError()
|
||||||
|
self.api_client = africastalking.SMS
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def initialize(api_username, api_key, sender_id=None):
|
||||||
|
"""
|
||||||
|
:param api_username:
|
||||||
|
:type api_username:
|
||||||
|
:param api_key:
|
||||||
|
:type api_key:
|
||||||
|
:param sender_id:
|
||||||
|
:type sender_id:
|
||||||
|
"""
|
||||||
|
if AfricasTalkingNotifier.initiated:
|
||||||
|
raise AlreadyInitializedError()
|
||||||
|
africastalking.initialize(username=api_username, api_key=api_key)
|
||||||
|
|
||||||
|
AfricasTalkingNotifier.sender_id = sender_id
|
||||||
|
AfricasTalkingNotifier.initiated = True
|
||||||
|
|
||||||
|
def send(self, message, recipient):
|
||||||
|
"""
|
||||||
|
:param message:
|
||||||
|
:type message:
|
||||||
|
:param recipient:
|
||||||
|
:type recipient:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
if self.sender_id:
|
||||||
|
response = self.api_client.send(message=message, recipients=[recipient], sender_id=self.sender_id)
|
||||||
|
logg.debug(f'Africastalking response sender-id {response}')
|
||||||
|
else:
|
||||||
|
response = self.api_client.send(message=message, recipients=[recipient])
|
||||||
|
logg.debug(f'africastalking response no-sender-id {response}')
|
||||||
|
|
||||||
|
|
||||||
|
@celery_app.task
|
||||||
|
def send(message, recipient):
|
||||||
|
"""
|
||||||
|
:param message:
|
||||||
|
:type message:
|
||||||
|
:param recipient:
|
||||||
|
:type recipient:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
africastalking_notifier = AfricasTalkingNotifier()
|
||||||
|
africastalking_notifier.send(message=message, recipient=recipient)
|
26
apps/cic-notify/cic_notify/tasks/sms/db.py
Normal file
26
apps/cic-notify/cic_notify/tasks/sms/db.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# standard imports
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import celery
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.db.models.notification import Notification
|
||||||
|
from cic_notify.db.enum import NotificationTransportEnum
|
||||||
|
|
||||||
|
celery_app = celery.current_app
|
||||||
|
|
||||||
|
|
||||||
|
@celery_app.task
|
||||||
|
def persist_notification(recipient, message):
|
||||||
|
"""
|
||||||
|
:param recipient:
|
||||||
|
:type recipient:
|
||||||
|
:param message:
|
||||||
|
:type message:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
Notification.create_session()
|
||||||
|
notification = Notification(transport=NotificationTransportEnum.SMS, recipient=recipient, message=message)
|
||||||
|
Notification.session.add(notification)
|
||||||
|
Notification.session.commit()
|
26
apps/cic-notify/cic_notify/tasks/sms/log.py
Normal file
26
apps/cic-notify/cic_notify/tasks/sms/log.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import celery
|
||||||
|
|
||||||
|
celery_app = celery.current_app
|
||||||
|
logg = celery_app.log.get_default_logger()
|
||||||
|
local_logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@celery_app.task
|
||||||
|
def log(recipient, message):
|
||||||
|
"""
|
||||||
|
:param recipient:
|
||||||
|
:type recipient:
|
||||||
|
:param message:
|
||||||
|
:type message:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
timestamp = time.time()
|
||||||
|
log_string = f'[{timestamp}] {__name__} message to {recipient}: {message}'
|
||||||
|
logg.info(log_string)
|
||||||
|
local_logg.info(log_string)
|
47
apps/cic-notify/cic_notify/version.py
Normal file
47
apps/cic-notify/cic_notify/version.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import semver
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.error import PleaseCommitFirstError
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
version = (0, 4, 0, 'alpha.2')
|
||||||
|
|
||||||
|
version_object = semver.VersionInfo(
|
||||||
|
major=version[0],
|
||||||
|
minor=version[1],
|
||||||
|
patch=version[2],
|
||||||
|
prerelease=version[3],
|
||||||
|
)
|
||||||
|
|
||||||
|
version_string = str(version_object)
|
||||||
|
|
||||||
|
|
||||||
|
def git_hash():
|
||||||
|
import subprocess
|
||||||
|
git_diff = subprocess.run(['git', 'diff'], capture_output=True)
|
||||||
|
if len(git_diff.stdout) > 0:
|
||||||
|
raise PleaseCommitFirstError()
|
||||||
|
|
||||||
|
git_hash = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True)
|
||||||
|
git_hash_brief = git_hash.stdout.decode('utf-8')[:8]
|
||||||
|
return git_hash_brief
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
version_git = git_hash()
|
||||||
|
version_string += '.build.{}'.format(version_git)
|
||||||
|
except FileNotFoundError:
|
||||||
|
time_string_pair = str(time.time()).split('.')
|
||||||
|
version_string += '+build.{}{:<09d}'.format(
|
||||||
|
time_string_pair[0],
|
||||||
|
int(time_string_pair[1]),
|
||||||
|
)
|
||||||
|
logg.info(f'Final version string will be {version_string}')
|
||||||
|
|
||||||
|
__version_string__ = version_string
|
5
apps/cic-notify/doc/texinfo/Makefile
Normal file
5
apps/cic-notify/doc/texinfo/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
all: html
|
||||||
|
|
||||||
|
.PHONY: html
|
||||||
|
html:
|
||||||
|
makeinfo --html --output html index.texi
|
14
apps/cic-notify/doc/texinfo/api.texi
Normal file
14
apps/cic-notify/doc/texinfo/api.texi
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
@node API
|
||||||
|
@chapter API
|
||||||
|
|
||||||
|
A setuptools file is provided to install @code{cic-notify} as a normal python package.
|
||||||
|
|
||||||
|
A single Python API method is provided so far:
|
||||||
|
|
||||||
|
@itemize @code
|
||||||
|
@item sms(recipient, content)
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
@code{recipient} is a string with phone number in @code{msisdn} format with a @code{+} prefix.
|
||||||
|
|
||||||
|
@code{content} is a UTF-8 string containing the message to be sent.
|
61
apps/cic-notify/doc/texinfo/html/API.html
Normal file
61
apps/cic-notify/doc/texinfo/html/API.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<!-- Released 2020 under GPL3 -->
|
||||||
|
<!-- Created by GNU Texinfo 6.7, http://www.gnu.org/software/texinfo/ -->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>API (Grassroots Economics CIC notify 0.2.0)</title>
|
||||||
|
|
||||||
|
<meta name="description" content="API (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="keywords" content="API (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="resource-type" content="document">
|
||||||
|
<meta name="distribution" content="global">
|
||||||
|
<meta name="Generator" content="makeinfo">
|
||||||
|
<link href="index.html" rel="start" title="Top">
|
||||||
|
<link href="index.html#SEC_Contents" rel="contents" title="Table of Contents">
|
||||||
|
<link href="index.html" rel="up" title="Top">
|
||||||
|
<link href="Run.html" rel="prev" title="Run">
|
||||||
|
<style type="text/css">
|
||||||
|
<!--
|
||||||
|
a.summary-letter {text-decoration: none}
|
||||||
|
blockquote.indentedblock {margin-right: 0em}
|
||||||
|
div.display {margin-left: 3.2em}
|
||||||
|
div.example {margin-left: 3.2em}
|
||||||
|
div.lisp {margin-left: 3.2em}
|
||||||
|
kbd {font-style: oblique}
|
||||||
|
pre.display {font-family: inherit}
|
||||||
|
pre.format {font-family: inherit}
|
||||||
|
pre.menu-comment {font-family: serif}
|
||||||
|
pre.menu-preformatted {font-family: serif}
|
||||||
|
span.nolinebreak {white-space: nowrap}
|
||||||
|
span.roman {font-family: initial; font-weight: normal}
|
||||||
|
span.sansserif {font-family: sans-serif; font-weight: normal}
|
||||||
|
ul.no-bullet {list-style: none}
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body lang="en">
|
||||||
|
<span id="API"></span><div class="header">
|
||||||
|
<p>
|
||||||
|
Previous: <a href="Run.html" accesskey="p" rel="prev">Run</a>, Up: <a href="index.html" accesskey="u" rel="up">Top</a> [<a href="index.html#SEC_Contents" title="Table of contents" rel="contents">Contents</a>]</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span id="API-1"></span><h2 class="chapter">4 API</h2>
|
||||||
|
|
||||||
|
<p>A single Python API method is provided so far:
|
||||||
|
</p>
|
||||||
|
<ul class="no-bullet">
|
||||||
|
<li> sms(recipient, content)
|
||||||
|
</li></ul>
|
||||||
|
|
||||||
|
<p><code>recipient</code> is a string with phone number in <code>msisdn</code> format with a <code>+</code> prefix.
|
||||||
|
</p>
|
||||||
|
<p><code>content</code> is a UTF-8 string containing the message to be sent.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
72
apps/cic-notify/doc/texinfo/html/Overview.html
Normal file
72
apps/cic-notify/doc/texinfo/html/Overview.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<!-- Released 2020 under GPL3 -->
|
||||||
|
<!-- Created by GNU Texinfo 6.7, http://www.gnu.org/software/texinfo/ -->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>Overview (Grassroots Economics CIC notify 0.2.0)</title>
|
||||||
|
|
||||||
|
<meta name="description" content="Overview (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="keywords" content="Overview (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="resource-type" content="document">
|
||||||
|
<meta name="distribution" content="global">
|
||||||
|
<meta name="Generator" content="makeinfo">
|
||||||
|
<link href="index.html" rel="start" title="Top">
|
||||||
|
<link href="index.html#SEC_Contents" rel="contents" title="Table of Contents">
|
||||||
|
<link href="index.html" rel="up" title="Top">
|
||||||
|
<link href="Setup.html" rel="next" title="Setup">
|
||||||
|
<link href="index.html" rel="prev" title="Top">
|
||||||
|
<style type="text/css">
|
||||||
|
<!--
|
||||||
|
a.summary-letter {text-decoration: none}
|
||||||
|
blockquote.indentedblock {margin-right: 0em}
|
||||||
|
div.display {margin-left: 3.2em}
|
||||||
|
div.example {margin-left: 3.2em}
|
||||||
|
div.lisp {margin-left: 3.2em}
|
||||||
|
kbd {font-style: oblique}
|
||||||
|
pre.display {font-family: inherit}
|
||||||
|
pre.format {font-family: inherit}
|
||||||
|
pre.menu-comment {font-family: serif}
|
||||||
|
pre.menu-preformatted {font-family: serif}
|
||||||
|
span.nolinebreak {white-space: nowrap}
|
||||||
|
span.roman {font-family: initial; font-weight: normal}
|
||||||
|
span.sansserif {font-family: sans-serif; font-weight: normal}
|
||||||
|
ul.no-bullet {list-style: none}
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body lang="en">
|
||||||
|
<span id="Overview"></span><div class="header">
|
||||||
|
<p>
|
||||||
|
Next: <a href="Setup.html" accesskey="n" rel="next">Setup</a>, Up: <a href="index.html" accesskey="u" rel="up">Top</a> [<a href="index.html#SEC_Contents" title="Table of contents" rel="contents">Contents</a>]</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span id="Overview-1"></span><h2 class="chapter">1 Overview</h2>
|
||||||
|
|
||||||
|
<p>This system is in early stages of development. It is intended to be a flexible notification broker where additional notification targets easily can be plugged in.
|
||||||
|
</p>
|
||||||
|
<p>The framework is designed to asynchronously execute all tasks belonging to a specific context, based on the name of the task.
|
||||||
|
</p>
|
||||||
|
<p>Currently, only handlers for <strong>sms</strong> notifications are implemented. Any task with a <code>notify.sms.</code> prefix registered in the celery task worker pool will be executed upon the high-level "send sms" task.
|
||||||
|
</p>
|
||||||
|
<p>Similary, any other notification category can be implemented. e.g. <code>notify.email.</code>, <code>notify.telegram.</code>, <code>notify.mattermost.</code> etc.
|
||||||
|
</p>
|
||||||
|
<span id="Contents"></span><h3 class="section">1.1 Contents</h3>
|
||||||
|
|
||||||
|
<p>The only implementations so far are three <code>sms</code> notification tasks:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li> log (as in python logger, thus more or less noop)
|
||||||
|
</li><li> db (postgres)
|
||||||
|
</li><li> Africas Talking API
|
||||||
|
</li></ul>
|
||||||
|
|
||||||
|
<p><strong>NOTE</strong>: The Africas Talking API will be removed from the suite, and provided as an add-on package down the road. It will illustrate how to include arbitrary tasks to a asynchronous group of notification targets.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
54
apps/cic-notify/doc/texinfo/html/Run.html
Normal file
54
apps/cic-notify/doc/texinfo/html/Run.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<!-- Released 2020 under GPL3 -->
|
||||||
|
<!-- Created by GNU Texinfo 6.7, http://www.gnu.org/software/texinfo/ -->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>Run (Grassroots Economics CIC notify 0.2.0)</title>
|
||||||
|
|
||||||
|
<meta name="description" content="Run (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="keywords" content="Run (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="resource-type" content="document">
|
||||||
|
<meta name="distribution" content="global">
|
||||||
|
<meta name="Generator" content="makeinfo">
|
||||||
|
<link href="index.html" rel="start" title="Top">
|
||||||
|
<link href="index.html#SEC_Contents" rel="contents" title="Table of Contents">
|
||||||
|
<link href="index.html" rel="up" title="Top">
|
||||||
|
<link href="API.html" rel="next" title="API">
|
||||||
|
<link href="Setup.html" rel="prev" title="Setup">
|
||||||
|
<style type="text/css">
|
||||||
|
<!--
|
||||||
|
a.summary-letter {text-decoration: none}
|
||||||
|
blockquote.indentedblock {margin-right: 0em}
|
||||||
|
div.display {margin-left: 3.2em}
|
||||||
|
div.example {margin-left: 3.2em}
|
||||||
|
div.lisp {margin-left: 3.2em}
|
||||||
|
kbd {font-style: oblique}
|
||||||
|
pre.display {font-family: inherit}
|
||||||
|
pre.format {font-family: inherit}
|
||||||
|
pre.menu-comment {font-family: serif}
|
||||||
|
pre.menu-preformatted {font-family: serif}
|
||||||
|
span.nolinebreak {white-space: nowrap}
|
||||||
|
span.roman {font-family: initial; font-weight: normal}
|
||||||
|
span.sansserif {font-family: sans-serif; font-weight: normal}
|
||||||
|
ul.no-bullet {list-style: none}
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body lang="en">
|
||||||
|
<span id="Run"></span><div class="header">
|
||||||
|
<p>
|
||||||
|
Next: <a href="API.html" accesskey="n" rel="next">API</a>, Previous: <a href="Setup.html" accesskey="p" rel="prev">Setup</a>, Up: <a href="index.html" accesskey="u" rel="up">Top</a> [<a href="index.html#SEC_Contents" title="Table of contents" rel="contents">Contents</a>]</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span id="Running-the-Application"></span><h2 class="chapter">3 Running the Application</h2>
|
||||||
|
|
||||||
|
<p>A convenience task runner is provided in <samp>scripts/cic-notify-tasker.py</samp>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
64
apps/cic-notify/doc/texinfo/html/Setup.html
Normal file
64
apps/cic-notify/doc/texinfo/html/Setup.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<!-- Released 2020 under GPL3 -->
|
||||||
|
<!-- Created by GNU Texinfo 6.7, http://www.gnu.org/software/texinfo/ -->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>Setup (Grassroots Economics CIC notify 0.2.0)</title>
|
||||||
|
|
||||||
|
<meta name="description" content="Setup (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="keywords" content="Setup (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="resource-type" content="document">
|
||||||
|
<meta name="distribution" content="global">
|
||||||
|
<meta name="Generator" content="makeinfo">
|
||||||
|
<link href="index.html" rel="start" title="Top">
|
||||||
|
<link href="index.html#SEC_Contents" rel="contents" title="Table of Contents">
|
||||||
|
<link href="index.html" rel="up" title="Top">
|
||||||
|
<link href="Run.html" rel="next" title="Run">
|
||||||
|
<link href="Overview.html" rel="prev" title="Overview">
|
||||||
|
<style type="text/css">
|
||||||
|
<!--
|
||||||
|
a.summary-letter {text-decoration: none}
|
||||||
|
blockquote.indentedblock {margin-right: 0em}
|
||||||
|
div.display {margin-left: 3.2em}
|
||||||
|
div.example {margin-left: 3.2em}
|
||||||
|
div.lisp {margin-left: 3.2em}
|
||||||
|
kbd {font-style: oblique}
|
||||||
|
pre.display {font-family: inherit}
|
||||||
|
pre.format {font-family: inherit}
|
||||||
|
pre.menu-comment {font-family: serif}
|
||||||
|
pre.menu-preformatted {font-family: serif}
|
||||||
|
span.nolinebreak {white-space: nowrap}
|
||||||
|
span.roman {font-family: initial; font-weight: normal}
|
||||||
|
span.sansserif {font-family: sans-serif; font-weight: normal}
|
||||||
|
ul.no-bullet {list-style: none}
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body lang="en">
|
||||||
|
<span id="Setup"></span><div class="header">
|
||||||
|
<p>
|
||||||
|
Next: <a href="Run.html" accesskey="n" rel="next">Run</a>, Previous: <a href="Overview.html" accesskey="p" rel="prev">Overview</a>, Up: <a href="index.html" accesskey="u" rel="up">Top</a> [<a href="index.html#SEC_Contents" title="Table of contents" rel="contents">Contents</a>]</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span id="Setup-1"></span><h2 class="chapter">2 Setup</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<span id="Handlers"></span><h3 class="section">2.1 Handlers</h3>
|
||||||
|
|
||||||
|
<p>Notification tasks in this package are intended to be loaded dynamically, making no assumptions on which handlers that should be connected to the different tasks. The tasks provided are:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li> <strong>notify.sms.log</strong>: (none)
|
||||||
|
</li><li> <strong>notify.sms.db</strong>: Postgresql, along with python packages alembic, SQLAlchemy and psycopg2.
|
||||||
|
</li><li> <strong>notify.sms.africastalking</strong>: africastalking python package.
|
||||||
|
</li></ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
94
apps/cic-notify/doc/texinfo/html/index.html
Normal file
94
apps/cic-notify/doc/texinfo/html/index.html
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<!-- Released 2020 under GPL3 -->
|
||||||
|
<!-- Created by GNU Texinfo 6.7, http://www.gnu.org/software/texinfo/ -->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>Top (Grassroots Economics CIC notify 0.2.0)</title>
|
||||||
|
|
||||||
|
<meta name="description" content="Top (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="keywords" content="Top (Grassroots Economics CIC notify 0.2.0)">
|
||||||
|
<meta name="resource-type" content="document">
|
||||||
|
<meta name="distribution" content="global">
|
||||||
|
<meta name="Generator" content="makeinfo">
|
||||||
|
<link href="#Top" rel="start" title="Top">
|
||||||
|
<link href="#SEC_Contents" rel="contents" title="Table of Contents">
|
||||||
|
<link href="Overview.html" rel="next" title="Overview">
|
||||||
|
<style type="text/css">
|
||||||
|
<!--
|
||||||
|
a.summary-letter {text-decoration: none}
|
||||||
|
blockquote.indentedblock {margin-right: 0em}
|
||||||
|
div.display {margin-left: 3.2em}
|
||||||
|
div.example {margin-left: 3.2em}
|
||||||
|
div.lisp {margin-left: 3.2em}
|
||||||
|
kbd {font-style: oblique}
|
||||||
|
pre.display {font-family: inherit}
|
||||||
|
pre.format {font-family: inherit}
|
||||||
|
pre.menu-comment {font-family: serif}
|
||||||
|
pre.menu-preformatted {font-family: serif}
|
||||||
|
span.nolinebreak {white-space: nowrap}
|
||||||
|
span.roman {font-family: initial; font-weight: normal}
|
||||||
|
span.sansserif {font-family: sans-serif; font-weight: normal}
|
||||||
|
ul.no-bullet {list-style: none}
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body lang="en">
|
||||||
|
<h1 class="settitle" align="center">Grassroots Economics CIC notify 0.2.0</h1>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<span id="SEC_Contents"></span>
|
||||||
|
<h2 class="contents-heading">Table of Contents</h2>
|
||||||
|
|
||||||
|
<div class="contents">
|
||||||
|
|
||||||
|
<ul class="no-bullet">
|
||||||
|
<li><a id="toc-Overview-1" href="Overview.html#Overview">1 Overview</a>
|
||||||
|
<ul class="no-bullet">
|
||||||
|
<li><a id="toc-Contents" href="Overview.html#Contents">1.1 Contents</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a id="toc-Setup-1" href="Setup.html#Setup">2 Setup</a>
|
||||||
|
<ul class="no-bullet">
|
||||||
|
<li><a id="toc-Handlers" href="Setup.html#Handlers">2.1 Handlers</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a id="toc-Running-the-Application" href="Run.html#Run">3 Running the Application</a></li>
|
||||||
|
<li><a id="toc-API-1" href="API.html#API">4 API</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<span id="Top"></span><div class="header">
|
||||||
|
<p>
|
||||||
|
[<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>]</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span id="Grassroots-Economics-CIC-ETH"></span><h1 class="top">Grassroots Economics CIC ETH</h1>
|
||||||
|
|
||||||
|
<p>This document describes microservices that broker external notifications from the CIC network
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="menu" border="0" cellspacing="0">
|
||||||
|
<tr><td align="left" valign="top">• <a href="Overview.html" accesskey="1">Overview</a></td><td> </td><td align="left" valign="top">
|
||||||
|
</td></tr>
|
||||||
|
<tr><td align="left" valign="top">• <a href="Setup.html" accesskey="2">Setup</a></td><td> </td><td align="left" valign="top">
|
||||||
|
</td></tr>
|
||||||
|
<tr><td align="left" valign="top">• <a href="Run.html" accesskey="3">Running the Application</a></td><td> </td><td align="left" valign="top">
|
||||||
|
</td></tr>
|
||||||
|
<tr><td align="left" valign="top">• <a href="API.html" accesskey="4">API</a></td><td> </td><td align="left" valign="top">
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div class="header">
|
||||||
|
<p>
|
||||||
|
[<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>]</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
28
apps/cic-notify/doc/texinfo/index.texi
Normal file
28
apps/cic-notify/doc/texinfo/index.texi
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
\input texinfo
|
||||||
|
@settitle Grassroots Economics CIC notify 0.2.0
|
||||||
|
|
||||||
|
@copying
|
||||||
|
Released 2020 under GPL3
|
||||||
|
@end copying
|
||||||
|
|
||||||
|
@titlepage
|
||||||
|
@title Community Inclusion Currency Network
|
||||||
|
@author Louis Holbrook
|
||||||
|
|
||||||
|
@end titlepage
|
||||||
|
|
||||||
|
@c
|
||||||
|
@contents
|
||||||
|
|
||||||
|
@ifnottex
|
||||||
|
@node Top
|
||||||
|
@top Grassroots Economics CIC ETH
|
||||||
|
|
||||||
|
This document describes microservices that broker external notifications from the CIC network
|
||||||
|
@end ifnottex
|
||||||
|
|
||||||
|
|
||||||
|
@include overview.texi
|
||||||
|
@include setup.texi
|
||||||
|
@include run.texi
|
||||||
|
@include api.texi
|
22
apps/cic-notify/doc/texinfo/overview.texi
Normal file
22
apps/cic-notify/doc/texinfo/overview.texi
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@node Overview
|
||||||
|
@chapter Overview
|
||||||
|
|
||||||
|
This system is in early stages of development. It is intended to be a flexible notification broker where additional notification targets easily can be plugged in.
|
||||||
|
|
||||||
|
The framework is designed to asynchronously execute all tasks belonging to a specific context, based on the name of the task.
|
||||||
|
|
||||||
|
Currently, only handlers for @strong{sms} notifications are implemented. Any task with a @code{notify.sms.} prefix registered in the celery task worker pool will be executed upon the high-level "send sms" task.
|
||||||
|
|
||||||
|
Similary, any other notification category can be implemented. e.g. @code{notify.email.}, @code{notify.telegram.}, @code{notify.mattermost.} etc.
|
||||||
|
|
||||||
|
@section Contents
|
||||||
|
|
||||||
|
The only implementations so far are three @code{sms} notification tasks:
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item log (as in python logger, thus more or less noop)
|
||||||
|
@item db (postgres)
|
||||||
|
@item Africas Talking API
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
@strong{NOTE}: The Africas Talking API will be removed from the suite, and provided as an add-on package down the road. It will illustrate how to include arbitrary tasks to a asynchronous group of notification targets.
|
4
apps/cic-notify/doc/texinfo/run.texi
Normal file
4
apps/cic-notify/doc/texinfo/run.texi
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
@node Run
|
||||||
|
@chapter Running the Application
|
||||||
|
|
||||||
|
A convenience task runner is provided in @file{scripts/cic-notify-tasker.py}.
|
17
apps/cic-notify/doc/texinfo/setup.texi
Normal file
17
apps/cic-notify/doc/texinfo/setup.texi
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@node Setup
|
||||||
|
@chapter Setup
|
||||||
|
|
||||||
|
|
||||||
|
@section Handlers
|
||||||
|
|
||||||
|
Notification tasks in this package are intended to be loaded dynamically, making no assumptions on which handlers that should be connected to the different tasks. The tasks provided are:
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
@strong{notify.sms.log}: (none)
|
||||||
|
@item
|
||||||
|
@strong{notify.sms.db}: Postgresql, along with python packages alembic, SQLAlchemy and psycopg2.
|
||||||
|
@item
|
||||||
|
@strong{notify.sms.africastalking}: africastalking python package.
|
||||||
|
@end itemize
|
||||||
|
|
39
apps/cic-notify/docker/Dockerfile
Normal file
39
apps/cic-notify/docker/Dockerfile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
FROM python:3.8.6
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps
|
||||||
|
|
||||||
|
WORKDIR /usr/src/cic-notify
|
||||||
|
|
||||||
|
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433'
|
||||||
|
ARG root_requirement_file='requirements.txt'
|
||||||
|
RUN echo "copying root req file ${root_requirement_file}"
|
||||||
|
COPY $root_requirement_file .
|
||||||
|
RUN pip install -r $root_requirement_file $pip_extra_index_url_flag
|
||||||
|
|
||||||
|
COPY cic-notify/setup.cfg \
|
||||||
|
cic-notify/setup.py \
|
||||||
|
./
|
||||||
|
COPY cic-notify/cic_notify/ ./cic_notify/
|
||||||
|
COPY cic-notify/requirements.txt \
|
||||||
|
cic-notify/test_requirements.txt \
|
||||||
|
./
|
||||||
|
|
||||||
|
COPY cic-notify/scripts/ scripts/
|
||||||
|
RUN pip install $pip_extra_index_url_flag .[africastalking,notifylog]
|
||||||
|
|
||||||
|
COPY cic-notify/tests/ tests/
|
||||||
|
COPY cic-notify/docker/db.sh \
|
||||||
|
cic-notify/docker/start_tasker.sh \
|
||||||
|
/root/
|
||||||
|
|
||||||
|
#RUN apk add postgresql-client
|
||||||
|
#RUN apk add bash
|
||||||
|
|
||||||
|
# ini files in config directory defines the configurable parameters for the application
|
||||||
|
# they can all be overridden by environment variables
|
||||||
|
# to generate a list of environment variables from configuration, use: confini-dump -z <dir> (executable provided by confini package)
|
||||||
|
COPY cic-notify/.config/ /usr/local/etc/cic-notify/
|
||||||
|
COPY cic-notify/cic_notify/db/migrations/ /usr/local/share/cic-notify/alembic/
|
||||||
|
|
||||||
|
WORKDIR /root
|
13
apps/cic-notify/docker/Dockerfile.service
Normal file
13
apps/cic-notify/docker/Dockerfile.service
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM grassrootseconomics:cic-notify
|
||||||
|
|
||||||
|
#FROM python:3.8.6-alpine
|
||||||
|
|
||||||
|
#RUN apk update && \
|
||||||
|
# apk add gnupg libpq
|
||||||
|
|
||||||
|
#COPY --from=0 /usr/local/ /usr/local/
|
||||||
|
#COPY --from=0 /root/ /root/
|
||||||
|
|
||||||
|
#RUN apk add bash
|
||||||
|
|
||||||
|
WORKDIR /root
|
3
apps/cic-notify/docker/db.sh
Normal file
3
apps/cic-notify/docker/db.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
migrate.py -c /usr/local/etc/cic-notify --migrations-dir /usr/local/share/cic-notify/alembic -vv
|
5
apps/cic-notify/docker/start_tasker.sh
Executable file
5
apps/cic-notify/docker/start_tasker.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
. ./db.sh
|
||||||
|
|
||||||
|
/usr/local/bin/cic-notify-tasker -vv $@
|
5
apps/cic-notify/requirements.txt
Normal file
5
apps/cic-notify/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
celery~=4.4.7
|
||||||
|
confini~=0.3.6a1
|
||||||
|
alembic~=1.4.2
|
||||||
|
redis~=3.5.3
|
||||||
|
semver==2.13.0
|
57
apps/cic-notify/scripts/migrate.py
Normal file
57
apps/cic-notify/scripts/migrate.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# standard imports
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# third party imports
|
||||||
|
import alembic
|
||||||
|
from alembic.config import Config as AlembicConfig
|
||||||
|
import confini
|
||||||
|
|
||||||
|
from cic_notify.db import dsn_from_config
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.WARNING)
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
dbdir = os.path.join(rootdir, 'cic_notify', 'db')
|
||||||
|
migrationsdir = os.path.join(dbdir, 'migrations')
|
||||||
|
|
||||||
|
config_dir = os.path.join('/usr/local/etc/cic-notify')
|
||||||
|
|
||||||
|
argparser = argparse.ArgumentParser()
|
||||||
|
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||||
|
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||||
|
argparser.add_argument('--migrations-dir', dest='migrations_dir', default=migrationsdir, type=str, help='path to alembic migrations directory')
|
||||||
|
argparser.add_argument('-v', action='store_true', help='be verbose')
|
||||||
|
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||||
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
if args.vv:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
elif args.v:
|
||||||
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
|
config = confini.Config(args.c, args.env_prefix)
|
||||||
|
config.process()
|
||||||
|
config.censor('PASSWORD', 'DATABASE')
|
||||||
|
#config.censor('PASSWORD', 'SSL')
|
||||||
|
logg.debug('config:\n{}'.format(config))
|
||||||
|
|
||||||
|
migrations_dir = os.path.join(args.migrations_dir, config.get('DATABASE_ENGINE'))
|
||||||
|
if not os.path.isdir(migrations_dir):
|
||||||
|
logg.debug('migrations dir for engine {} not found, reverting to default'.format(config.get('DATABASE_ENGINE')))
|
||||||
|
migrations_dir = os.path.join(args.migrations_dir, 'default')
|
||||||
|
|
||||||
|
# connect to database
|
||||||
|
dsn = dsn_from_config(config)
|
||||||
|
|
||||||
|
|
||||||
|
logg.info('using migrations dir {}'.format(migrations_dir))
|
||||||
|
logg.info('using db {}'.format(dsn))
|
||||||
|
ac = AlembicConfig(os.path.join(migrations_dir, 'alembic.ini'))
|
||||||
|
ac.set_main_option('sqlalchemy.url', dsn)
|
||||||
|
ac.set_main_option('script_location', migrations_dir)
|
||||||
|
|
||||||
|
alembic.command.upgrade(ac, 'head')
|
12
apps/cic-notify/scripts/send_sms.py
Normal file
12
apps/cic-notify/scripts/send_sms.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# local imports
|
||||||
|
import cic_notify
|
||||||
|
|
||||||
|
# third-part imports
|
||||||
|
import celery
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Add configurable backend
|
||||||
|
celery_app = celery.Celery(broker='redis:///', backend='redis:///')
|
||||||
|
|
||||||
|
api = cic_notify.Api('cic-notify')
|
||||||
|
print(api.sms('+25412121212', 'foo'))
|
47
apps/cic-notify/setup.cfg
Normal file
47
apps/cic-notify/setup.cfg
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
[metadata]
|
||||||
|
name = cic-notify
|
||||||
|
version= attr: cic_notify.version.__version_string__
|
||||||
|
description = CIC notifications service
|
||||||
|
author = Louis Holbrook
|
||||||
|
author_email = dev@holbrook.no
|
||||||
|
url = https://gitlab.com/grassrootseconomics/cic-eth
|
||||||
|
keywords =
|
||||||
|
cic
|
||||||
|
cryptocurrency
|
||||||
|
ethereum
|
||||||
|
sms
|
||||||
|
classifiers =
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Operating System :: OS Independent
|
||||||
|
Development Status :: 3 - Alpha
|
||||||
|
Environment :: No Input/Output (Daemon)
|
||||||
|
Intended Audience :: Developers
|
||||||
|
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
||||||
|
Topic :: Internet
|
||||||
|
Topic :: Blockchain :: EVM
|
||||||
|
license = GPL3
|
||||||
|
licence_files =
|
||||||
|
LICENSE.txt
|
||||||
|
|
||||||
|
[options]
|
||||||
|
python_requires = >= 3.6
|
||||||
|
packages =
|
||||||
|
cic_notify
|
||||||
|
cic_notify.db
|
||||||
|
cic_notify.db.models
|
||||||
|
cic_notify.tasks.sms
|
||||||
|
cic_notify.runnable
|
||||||
|
scripts =
|
||||||
|
scripts/migrate.py
|
||||||
|
[options.extras_require]
|
||||||
|
africastalking = africastalking==1.2.3
|
||||||
|
notifylog = psycopg2==2.8.6
|
||||||
|
testing =
|
||||||
|
pytest==6.0.1
|
||||||
|
pytest-celery==0.0.0a1
|
||||||
|
pytest-mock==3.3.1
|
||||||
|
pysqlite3==0.4.3
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
cic-notify-tasker = cic_notify.runnable.tasker:main
|
30
apps/cic-notify/setup.py
Normal file
30
apps/cic-notify/setup.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# standard imports
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
|
||||||
|
|
||||||
|
requirements = []
|
||||||
|
requirements_file = open('requirements.txt', 'r')
|
||||||
|
while True:
|
||||||
|
requirement = requirements_file.readline()
|
||||||
|
if requirement == '':
|
||||||
|
break
|
||||||
|
requirements.append(requirement.rstrip())
|
||||||
|
requirements_file.close()
|
||||||
|
|
||||||
|
test_requirements = []
|
||||||
|
test_requirements_file = open('test_requirements.txt', 'r')
|
||||||
|
while True:
|
||||||
|
test_requirement = test_requirements_file.readline()
|
||||||
|
if test_requirement == '':
|
||||||
|
break
|
||||||
|
test_requirements.append(test_requirement.rstrip())
|
||||||
|
test_requirements_file.close()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
install_requires=requirements,
|
||||||
|
tests_require=test_requirements,
|
||||||
|
)
|
5
apps/cic-notify/test_requirements.txt
Normal file
5
apps/cic-notify/test_requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pytest~=6.0.1
|
||||||
|
pytest-celery~=0.0.0a1
|
||||||
|
pytest-mock~=3.3.1
|
||||||
|
pysqlite3~=0.4.3
|
||||||
|
|
31
apps/cic-notify/tests/conftest.py
Normal file
31
apps/cic-notify/tests/conftest.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# standard imports
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# third party imports
|
||||||
|
import confini
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
root_dir = os.path.dirname(script_dir)
|
||||||
|
sys.path.insert(0, root_dir)
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.db.models.base import SessionBase
|
||||||
|
#from transport.notification import AfricastalkingNotification
|
||||||
|
|
||||||
|
# fixtures
|
||||||
|
from tests.fixtures_config import *
|
||||||
|
from tests.fixtures_celery import *
|
||||||
|
from tests.fixtures_database import *
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
#@pytest.fixture(scope='session')
|
||||||
|
#def africastalking_notification(
|
||||||
|
# load_config,
|
||||||
|
# ):
|
||||||
|
# return AfricastalkingNotificationTransport(load_config)
|
||||||
|
#
|
48
apps/cic-notify/tests/fixtures_celery.py
Normal file
48
apps/cic-notify/tests/fixtures_celery.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# third-party imports
|
||||||
|
import pytest
|
||||||
|
import tempfile
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
logg = logging.getLogger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
# celery fixtures
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def celery_includes():
|
||||||
|
return [
|
||||||
|
'cic_notify.tasks.sms',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def celery_config():
|
||||||
|
bq = tempfile.mkdtemp()
|
||||||
|
bp = tempfile.mkdtemp()
|
||||||
|
rq = tempfile.mkdtemp()
|
||||||
|
logg.debug('celery broker queue {} processed {}'.format(bq, bp))
|
||||||
|
logg.debug('celery backend store {}'.format(rq))
|
||||||
|
yield {
|
||||||
|
'broker_url': 'filesystem://',
|
||||||
|
'broker_transport_options': {
|
||||||
|
'data_folder_in': bq,
|
||||||
|
'data_folder_out': bq,
|
||||||
|
'data_folder_processed': bp,
|
||||||
|
},
|
||||||
|
'result_backend': 'file://{}'.format(rq),
|
||||||
|
}
|
||||||
|
logg.debug('cleaning up celery filesystem backend files {} {} {}'.format(bq, bp, rq))
|
||||||
|
shutil.rmtree(bq)
|
||||||
|
shutil.rmtree(bp)
|
||||||
|
shutil.rmtree(rq)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def celery_worker_parameters():
|
||||||
|
return {
|
||||||
|
# 'queues': ('cic-notify'),
|
||||||
|
}
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def celery_enable_logging():
|
||||||
|
return True
|
20
apps/cic-notify/tests/fixtures_config.py
Normal file
20
apps/cic-notify/tests/fixtures_config.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import pytest
|
||||||
|
import confini
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
root_dir = os.path.dirname(script_dir)
|
||||||
|
logg = logging.getLogger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def load_config():
|
||||||
|
config_dir = os.path.join(root_dir, '.config/test')
|
||||||
|
conf = confini.Config(config_dir, 'CICTEST')
|
||||||
|
conf.process()
|
||||||
|
logg.debug('config {}'.format(conf))
|
||||||
|
return conf
|
48
apps/cic-notify/tests/fixtures_database.py
Normal file
48
apps/cic-notify/tests/fixtures_database.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import pytest
|
||||||
|
import alembic
|
||||||
|
from alembic.config import Config as AlembicConfig
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.db import SessionBase
|
||||||
|
from cic_notify.db import dsn_from_config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def database_engine(
|
||||||
|
load_config,
|
||||||
|
):
|
||||||
|
dsn = dsn_from_config(load_config)
|
||||||
|
SessionBase.connect(dsn)
|
||||||
|
return dsn
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def init_database(
|
||||||
|
load_config,
|
||||||
|
database_engine,
|
||||||
|
):
|
||||||
|
|
||||||
|
rootdir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
dbdir = os.path.join(rootdir, 'cic_notify', 'db')
|
||||||
|
migrationsdir = os.path.join(dbdir, 'migrations', load_config.get('DATABASE_ENGINE'))
|
||||||
|
if not os.path.isdir(migrationsdir):
|
||||||
|
migrationsdir = os.path.join(dbdir, 'migrations', 'default')
|
||||||
|
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
|
||||||
|
ac = AlembicConfig(os.path.join(migrationsdir, 'alembic.ini'))
|
||||||
|
ac.set_main_option('sqlalchemy.url', database_engine)
|
||||||
|
ac.set_main_option('script_location', migrationsdir)
|
||||||
|
|
||||||
|
alembic.command.downgrade(ac, 'base')
|
||||||
|
alembic.command.upgrade(ac, 'head')
|
||||||
|
|
||||||
|
yield session
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
|
34
apps/cic-notify/tests/test_sms.py
Normal file
34
apps/cic-notify/tests/test_sms.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# standard imports
|
||||||
|
import json
|
||||||
|
|
||||||
|
# third party imports
|
||||||
|
import pytest
|
||||||
|
import celery
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_notify.tasks.sms import db
|
||||||
|
from cic_notify.tasks.sms import log
|
||||||
|
|
||||||
|
def test_log_notification(
|
||||||
|
celery_session_worker,
|
||||||
|
):
|
||||||
|
|
||||||
|
recipient = '+25412121212'
|
||||||
|
content = 'bar'
|
||||||
|
s_log = celery.signature('cic_notify.tasks.sms.log.log')
|
||||||
|
t = s_log.apply_async(args=[recipient, content])
|
||||||
|
|
||||||
|
r = t.get()
|
||||||
|
|
||||||
|
|
||||||
|
def test_db_notification(
|
||||||
|
init_database,
|
||||||
|
celery_session_worker,
|
||||||
|
):
|
||||||
|
|
||||||
|
recipient = '+25412121213'
|
||||||
|
content = 'foo'
|
||||||
|
s_db = celery.signature('cic_notify.tasks.sms.db.persist_notification')
|
||||||
|
t = s_db.apply_async(args=[recipient, content])
|
||||||
|
|
||||||
|
r = t.get()
|
@ -404,29 +404,29 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# cic-notify-tasker:
|
cic-notify-tasker:
|
||||||
# image: grassrootseconomics:cic-notify-service
|
image: grassrootseconomics:cic-notify-service
|
||||||
# environment:
|
environment:
|
||||||
# DATABASE_USER: $DATABASE_USER
|
DATABASE_USER: ${DATABASE_USER:-grassroots}
|
||||||
# DATABASE_HOST: $DATABASE_HOST
|
DATABASE_HOST: ${DATABASE_HOST:-postgres}
|
||||||
# DATABASE_PORT: $DATABASE_PORT
|
DATABASE_PORT: ${DATABASE_PORT:-5432}
|
||||||
# DATABASE_PASSWORD: $DATABASE_PASSWORD
|
DATABASE_PASSWORD: ${DATABASE_PASSWORD:-tralala}
|
||||||
# DATABASE_NAME: $DATABASE_NAME_CIC_NOTIFY
|
DATABASE_NAME: ${DATABASE_NAME_CIC_NOTIFY:-cic_notify}
|
||||||
# DATABASE_ENGINE: $DATABASE_ENGINE
|
DATABASE_ENGINE: ${DATABASE_ENGINE:-postgres}
|
||||||
# DATABASE_DRIVER: $DATABASE_DRIVER
|
DATABASE_DRIVER: ${DATABASE_DRIVER:-psycopg2}
|
||||||
# PGPASSWORD: $DATABASE_PASSWORD
|
PGPASSWORD: ${DATABASE_PASSWORD:-tralala}
|
||||||
# CELERY_BROKER_URL: $CELERY_BROKER_URL
|
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis}
|
||||||
# CELERY_RESULT_URL: $CELERY_RESULT_URL
|
CELERY_RESULT_URL: ${CELERY_BROKER_URL:-redis://redis}
|
||||||
# TASKS_AFRICASTALKING: $TASKS_AFRICASTALKING
|
TASKS_AFRICASTALKING: $TASKS_AFRICASTALKING
|
||||||
# TASKS_SMS_DB: $TASKS_SMS_DB
|
TASKS_SMS_DB: $TASKS_SMS_DB
|
||||||
# TASKS_LOG: $TASKS_LOG
|
TASKS_LOG: $TASKS_LOG
|
||||||
# depends_on:
|
depends_on:
|
||||||
# - postgres
|
- postgres
|
||||||
# - redis
|
- redis
|
||||||
# deploy:
|
deploy:
|
||||||
# restart_policy:
|
restart_policy:
|
||||||
# condition: on-failure
|
condition: on-failure
|
||||||
# command: "/root/start_tasker.sh -q cic-notify"
|
command: "/root/start_tasker.sh -q cic-notify"
|
||||||
|
|
||||||
|
|
||||||
# cic-meta-server:
|
# cic-meta-server:
|
||||||
|
Loading…
Reference in New Issue
Block a user