Merge branch 'cic-notify-build' into 'master'
Cic notify build See merge request grassrootseconomics/cic-internal-integration!10
This commit is contained in:
		
						commit
						5bfc3e5796
					
				@ -3,6 +3,7 @@ include:
 | 
			
		||||
  - local: 'apps/contract-migration/.gitlab-ci.yml'
 | 
			
		||||
  - local: 'apps/cic-eth/.gitlab-ci.yml'
 | 
			
		||||
  - local: 'apps/cic-ussd/.gitlab-ci.yml'
 | 
			
		||||
  - local: 'apps/cic-notify/.gitlab-ci.yml'
 | 
			
		||||
 | 
			
		||||
stages:
 | 
			
		||||
  - 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"]
 | 
			
		||||
	path = apps/cic-cache
 | 
			
		||||
	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:
 | 
			
		||||
#     image: grassrootseconomics:cic-notify-service
 | 
			
		||||
#     environment:
 | 
			
		||||
#       DATABASE_USER: $DATABASE_USER
 | 
			
		||||
#       DATABASE_HOST: $DATABASE_HOST
 | 
			
		||||
#       DATABASE_PORT: $DATABASE_PORT
 | 
			
		||||
#       DATABASE_PASSWORD: $DATABASE_PASSWORD
 | 
			
		||||
#       DATABASE_NAME: $DATABASE_NAME_CIC_NOTIFY
 | 
			
		||||
#       DATABASE_ENGINE: $DATABASE_ENGINE
 | 
			
		||||
#       DATABASE_DRIVER: $DATABASE_DRIVER
 | 
			
		||||
#       PGPASSWORD: $DATABASE_PASSWORD
 | 
			
		||||
#       CELERY_BROKER_URL: $CELERY_BROKER_URL
 | 
			
		||||
#       CELERY_RESULT_URL: $CELERY_RESULT_URL
 | 
			
		||||
#       TASKS_AFRICASTALKING: $TASKS_AFRICASTALKING
 | 
			
		||||
#       TASKS_SMS_DB: $TASKS_SMS_DB
 | 
			
		||||
#       TASKS_LOG: $TASKS_LOG
 | 
			
		||||
#     depends_on:
 | 
			
		||||
#       - postgres
 | 
			
		||||
#       - redis
 | 
			
		||||
#     deploy:
 | 
			
		||||
#       restart_policy:
 | 
			
		||||
#         condition: on-failure
 | 
			
		||||
#     command: "/root/start_tasker.sh -q cic-notify"
 | 
			
		||||
  cic-notify-tasker:
 | 
			
		||||
    image: grassrootseconomics:cic-notify-service
 | 
			
		||||
    environment:
 | 
			
		||||
      DATABASE_USER: ${DATABASE_USER:-grassroots}
 | 
			
		||||
      DATABASE_HOST: ${DATABASE_HOST:-postgres}
 | 
			
		||||
      DATABASE_PORT: ${DATABASE_PORT:-5432}
 | 
			
		||||
      DATABASE_PASSWORD: ${DATABASE_PASSWORD:-tralala}
 | 
			
		||||
      DATABASE_NAME: ${DATABASE_NAME_CIC_NOTIFY:-cic_notify}
 | 
			
		||||
      DATABASE_ENGINE: ${DATABASE_ENGINE:-postgres}
 | 
			
		||||
      DATABASE_DRIVER: ${DATABASE_DRIVER:-psycopg2}
 | 
			
		||||
      PGPASSWORD: ${DATABASE_PASSWORD:-tralala}
 | 
			
		||||
      CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis}
 | 
			
		||||
      CELERY_RESULT_URL: ${CELERY_BROKER_URL:-redis://redis}
 | 
			
		||||
      TASKS_AFRICASTALKING: $TASKS_AFRICASTALKING
 | 
			
		||||
      TASKS_SMS_DB: $TASKS_SMS_DB
 | 
			
		||||
      TASKS_LOG: $TASKS_LOG
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - postgres
 | 
			
		||||
      - redis
 | 
			
		||||
    deploy:
 | 
			
		||||
      restart_policy:
 | 
			
		||||
        condition: on-failure
 | 
			
		||||
    command: "/root/start_tasker.sh -q cic-notify"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#   cic-meta-server:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user