diff --git a/.travis.yml b/.travis.yml index c2deec4..0b3b1a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: python -env: - - OGN_CONFIG_MODULE='config/test.py' - python: - 3.5 - 3.6 - - 3.7-dev + - 3.7 + - 3.8-dev addons: postgresql: "9.6" diff --git a/app/__init__.py b/app/__init__.py index e89f39c..2cb3fad 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -7,21 +7,32 @@ from celery import Celery from app.flask_celery import make_celery -# Initialize Flask -app = Flask(__name__) +bootstrap = Bootstrap() +db = SQLAlchemy() +cache = Cache() -# Load the configuration -app.config.from_object('app.config.default') -app.config.from_envvar("OGN_CONFIG_MODULE", silent=True) +def create_app(config_name='development'): + # Initialize Flask + app = Flask(__name__) -# Initialize other things -bootstrap = Bootstrap(app) -db = SQLAlchemy(app) -migrate = Migrate(app, db) -cache = Cache(app) -celery = make_celery(app) + # Load the configuration + if config_name == 'testing': + app.config.from_object('app.config.test') + else: + app.config.from_object('app.config.default') + app.config.from_envvar("OGN_CONFIG_MODULE", silent=True) -from app.main import bp as bp_main -app.register_blueprint(bp_main) + # Initialize other things + bootstrap.init_app(app) + db.init_app(app) + cache.init_app(app) + + #migrate = Migrate(app, db) + #celery = make_celery(app) + + from app.main import bp as bp_main + app.register_blueprint(bp_main) -from app import commands + #from app import commands + + return app diff --git a/app/collect/database.py b/app/collect/database.py index f66d1f9..df1e661 100644 --- a/app/collect/database.py +++ b/app/collect/database.py @@ -2,12 +2,11 @@ from sqlalchemy import distinct from sqlalchemy.sql import null, and_, func, not_, case from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql import insert +from flask import current_app from app.model import Country, DeviceInfo, DeviceInfoOrigin, AircraftBeacon, ReceiverBeacon, Device, Receiver from app.utils import get_ddb, get_flarmnet -from app import app - def upsert(session, model, rows, update_cols): """Insert rows in model. On conflicting update columns if new value IS NOT NULL.""" @@ -46,7 +45,7 @@ def import_ddb(session, logger=None): """Import registered devices from the DDB.""" if logger is None: - logger = app.logger + logger = current_app.logger logger.info("Import registered devices fom the DDB...") counter = update_device_infos(session, DeviceInfoOrigin.OGN_DDB) @@ -60,7 +59,7 @@ def update_country_code(session, logger=None): """Update country code in receivers table if None.""" if logger is None: - logger = app.logger + logger = current_app.logger update_receivers = ( session.query(Receiver) diff --git a/app/collect/logbook.py b/app/collect/logbook.py index ae59494..a4774b7 100644 --- a/app/collect/logbook.py +++ b/app/collect/logbook.py @@ -1,18 +1,17 @@ from sqlalchemy import and_, or_, insert, update, exists, between from sqlalchemy.sql import func, null from sqlalchemy.sql.expression import true, false +from flask import current_app from app.model import TakeoffLanding, Logbook, AircraftBeacon from app.utils import date_to_timestamps -from app import app - def update_entries(session, date, logger=None): """Add/update logbook entries.""" if logger is None: - logger = app.logger + logger = current_app.logger logger.info("Compute logbook.") @@ -167,7 +166,7 @@ def update_max_altitudes(session, date, logger=None): """Add max altitudes in logbook when flight is complete (takeoff and landing).""" if logger is None: - logger = app.logger + logger = current_app.logger logger.info("Update logbook max altitude.") diff --git a/app/collect/ognrange.py b/app/collect/ognrange.py index a6a20f6..cb1bcc2 100644 --- a/app/collect/ognrange.py +++ b/app/collect/ognrange.py @@ -1,18 +1,17 @@ from sqlalchemy import Date from sqlalchemy import and_, insert, update, exists, between from sqlalchemy.sql import func, null +from flask import current_app from app.model import AircraftBeacon, ReceiverCoverage from app.utils import date_to_timestamps -from app import app - def update_entries(session, date, logger=None): """Create receiver coverage stats for Melissas ognrange.""" if logger is None: - logger = app.logger + logger = current_app.logger logger.info("Compute receiver coverages.") diff --git a/app/collect/stats.py b/app/collect/stats.py index 2312820..8e1d352 100644 --- a/app/collect/stats.py +++ b/app/collect/stats.py @@ -1,12 +1,11 @@ +from flask import current_app from sqlalchemy import insert, distinct, between, literal from sqlalchemy.sql import null, and_, func, or_, update from sqlalchemy.sql.expression import case from app.model import AircraftBeacon, DeviceStats, Country, CountryStats, ReceiverStats, ReceiverBeacon, RelationStats, Receiver, Device - from app.utils import date_to_timestamps -from app import app # 40dB@10km is enough for 640km MAX_PLAUSIBLE_QUALITY = 40 @@ -16,7 +15,7 @@ def create_device_stats(session, date, logger=None): """Add/update device stats.""" if logger is None: - logger = app.logger + logger = current_app.logger (start, end) = date_to_timestamps(date) @@ -83,7 +82,7 @@ def create_receiver_stats(session, date, logger=None): """Add/update receiver stats.""" if logger is None: - logger = app.logger + logger = current_app.logger (start, end) = date_to_timestamps(date) @@ -155,7 +154,7 @@ def create_receiver_stats(session, date, logger=None): def create_country_stats(session, date, logger=None): if logger is None: - logger = app.logger + logger = current_app.logger (start, end) = date_to_timestamps(date) @@ -181,7 +180,7 @@ def update_device_stats_jumps(session, date, logger=None): """Update device stats jumps.""" if logger is None: - logger = app.logger + logger = current_app.logger (start, end) = date_to_timestamps(date) @@ -237,7 +236,7 @@ def create_relation_stats(session, date, logger=None): """Add/update relation stats.""" if logger is None: - logger = app.logger + logger = current_app.logger (start, end) = date_to_timestamps(date) @@ -274,7 +273,7 @@ def update_qualities(session, date, logger=None): """Calculate relative qualities of receivers and devices.""" if logger is None: - logger = app.logger + logger = current_app.logger # Calculate avg quality of devices dev_sq = session.query(RelationStats.device_id, func.avg(RelationStats.quality).label("quality")).filter(RelationStats.date == date).group_by(RelationStats.device_id).subquery() @@ -339,7 +338,7 @@ def update_receivers(session, logger=None): """Update receivers with stats.""" if logger is None: - logger = app.logger + logger = current_app.logger receiver_stats = ( session.query( @@ -394,7 +393,7 @@ def update_devices(session, logger=None): """Update devices with stats.""" if logger is None: - logger = app.logger + logger = current_app.logger device_stats = ( session.query( diff --git a/app/collect/takeoff_landings.py b/app/collect/takeoff_landings.py index a69078f..996a4b7 100644 --- a/app/collect/takeoff_landings.py +++ b/app/collect/takeoff_landings.py @@ -1,19 +1,18 @@ from datetime import timedelta +from flask import current_app from sqlalchemy import and_, or_, insert, between, exists from sqlalchemy.sql import func, null from sqlalchemy.sql.expression import case from app.model import AircraftBeacon, TakeoffLanding, Airport -from app import app - def update_entries(session, start, end, logger=None): """Compute takeoffs and landings.""" if logger is None: - logger = app.logger + logger = current_app.logger logger.info("Compute takeoffs and landings.") diff --git a/app/commands/__init__.py b/app/commands/__init__.py index ff3abc5..7d6777b 100644 --- a/app/commands/__init__.py +++ b/app/commands/__init__.py @@ -1,5 +1,3 @@ -from app import app - from .database import user_cli as database_cli from .export import user_cli as export_cli from .flights import user_cli as flights_cli @@ -7,9 +5,10 @@ from .gateway import user_cli as gateway_cli from .logbook import user_cli as logbook_cli from .stats import user_cli as stats_cli -app.cli.add_command(database_cli) -app.cli.add_command(export_cli) -app.cli.add_command(flights_cli) -app.cli.add_command(gateway_cli) -app.cli.add_command(logbook_cli) -app.cli.add_command(stats_cli) +def register(app): + app.cli.add_command(database_cli) + app.cli.add_command(export_cli) + app.cli.add_command(flights_cli) + app.cli.add_command(gateway_cli) + app.cli.add_command(logbook_cli) + app.cli.add_command(stats_cli) diff --git a/app/commands/database.py b/app/commands/database.py index 8025b5d..fa054de 100644 --- a/app/commands/database.py +++ b/app/commands/database.py @@ -1,3 +1,4 @@ +from flask import current_app from flask.cli import AppGroup import click @@ -8,7 +9,6 @@ from app.collect.database import update_device_infos, update_country_code from app.model import * from app.utils import get_airports, get_days -from app import app from app import db user_cli = AppGroup("database") @@ -36,8 +36,8 @@ def get_database_days(start, end): @user_cli.command("info") def info(): - print(app.config) - print(app.config["SQLALCHEMY_DATABASE_URI"]) + print(current_app.config) + print(current_app.config["SQLALCHEMY_DATABASE_URI"]) @user_cli.command("init") diff --git a/app/commands/gateway.py b/app/commands/gateway.py index d880808..e67bb51 100644 --- a/app/commands/gateway.py +++ b/app/commands/gateway.py @@ -1,11 +1,10 @@ +from flask import current_app from flask.cli import AppGroup import click from ogn.client import AprsClient from app.gateway.bulkimport import ContinuousDbFeeder -from app import app - user_cli = AppGroup("gateway") user_cli.help = "Connection to APRS servers." @@ -21,14 +20,14 @@ def run(aprs_user="anon-dev"): print("aprs_user must be a string of 3-9 characters.") return - app.logger.warning("Start ogn gateway") + current_app.logger.warning("Start ogn gateway") client = AprsClient(aprs_user) client.connect() try: client.run(callback=saver.add, autoreconnect=True) except KeyboardInterrupt: - app.logger.warning("\nStop ogn gateway") + current_app.logger.warning("\nStop ogn gateway") saver.flush() client.disconnect() diff --git a/app/gateway/bulkimport.py b/app/gateway/bulkimport.py index b319a85..872d193 100644 --- a/app/gateway/bulkimport.py +++ b/app/gateway/bulkimport.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta from io import StringIO +from flask import current_app from flask.cli import AppGroup import click from tqdm import tqdm @@ -13,7 +14,6 @@ from app.utils import open_file from app.gateway.process_tools import * from app import db -from app import app user_cli = AppGroup("bulkimport") user_cli.help = "Tools for accelerated data import." @@ -91,16 +91,16 @@ def string_to_message(raw_string, reference_date): try: message = parse(raw_string, reference_date) except NotImplementedError as e: - app.logger.error("No parser implemented for message: {}".format(raw_string)) + current_app.logger.error("No parser implemented for message: {}".format(raw_string)) return None except ParseError as e: - app.logger.error("Parsing error with message: {}".format(raw_string)) + current_app.logger.error("Parsing error with message: {}".format(raw_string)) return None except TypeError as e: - app.logger.error("TypeError with message: {}".format(raw_string)) + current_app.logger.error("TypeError with message: {}".format(raw_string)) return None except Exception as e: - app.logger.error("Other Exception with string: {}".format(raw_string)) + current_app.logger.error("Other Exception with string: {}".format(raw_string)) return None # update reference receivers and distance to the receiver @@ -160,7 +160,7 @@ class ContinuousDbFeeder: self.receiver_buffer.write(complete_message) self.receiver_buffer.write("\n") else: - app.logger.error("Ignore beacon_type: {}".format(message["beacon_type"])) + current_app.logger.error("Ignore beacon_type: {}".format(message["beacon_type"])) return if datetime.utcnow() - self.last_flush >= timedelta(seconds=20): @@ -243,7 +243,7 @@ class FileDbFeeder: self.receiver_buffer.write(complete_message) self.receiver_buffer.write("\n") else: - app.logger.error("Ignore beacon_type: {}".format(message["beacon_type"])) + current_app.logger.error("Ignore beacon_type: {}".format(message["beacon_type"])) return def prepare(self): diff --git a/deployment/supervisor/celerybeatd.conf b/deployment/supervisor/celerybeatd.conf index e8f423e..6d50664 100644 --- a/deployment/supervisor/celerybeatd.conf +++ b/deployment/supervisor/celerybeatd.conf @@ -1,6 +1,5 @@ [program:celerybeat] -environment=OGN_CONFIG_MODULE='config/default.py' -command=/home/pi/venv/bin/celery -A app.collect beat -l info +command=/home/pi/ogn-python/venv/bin/celery -A app.collect beat -l info directory=/home/pi/ogn-python user=pi diff --git a/deployment/supervisor/celeryd.conf b/deployment/supervisor/celeryd.conf index 41f8a0d..09643b6 100644 --- a/deployment/supervisor/celeryd.conf +++ b/deployment/supervisor/celeryd.conf @@ -1,5 +1,4 @@ [program:celery] -environment=OGN_CONFIG_MODULE='config/default.py' command=/home/pi/ogn-python/venv/bin/celery -A app.collect worker -l info directory=/home/pi/ogn-python diff --git a/deployment/supervisor/flower.conf b/deployment/supervisor/flower.conf index 4f0b89c..8db94ff 100644 --- a/deployment/supervisor/flower.conf +++ b/deployment/supervisor/flower.conf @@ -1,5 +1,4 @@ [program:flower] -environment=OGN_CONFIG_MODULE='config/default.py' command=/home/pi/ogn-python/venv/bin/celery flower -A app.celery --port=5555 -l info directory=/home/pi/ogn-python diff --git a/deployment/supervisor/gunicorn.conf b/deployment/supervisor/gunicorn.conf index 6c1ef6a..c41ff19 100644 --- a/deployment/supervisor/gunicorn.conf +++ b/deployment/supervisor/gunicorn.conf @@ -1,5 +1,4 @@ [program:gunicorn] -environment=OGN_CONFIG_MODULE='config/default.py' command=/home/pi/ogn-python/venv/bin/gunicorn --bind :5000 ogn_python:app directory=/home/pi/ogn-python diff --git a/deployment/supervisor/ogn-feeder.conf b/deployment/supervisor/ogn-feeder.conf index c3712fe..f1634e2 100644 --- a/deployment/supervisor/ogn-feeder.conf +++ b/deployment/supervisor/ogn-feeder.conf @@ -1,7 +1,6 @@ [program:ogn-feeder] -environment=OGN_CONFIG_MODULE='config/default.py' command=/home/pi/ogn-python/venv/bin/python -m flask gateway run -directory=/home/pi/ogn_python +directory=/home/pi/ogn-python user=pi stderr_logfile=/var/log/supervisor/ogn-feeder.log diff --git a/ogn_python.py b/ogn_python.py index 74929ed..901f0b8 100644 --- a/ogn_python.py +++ b/ogn_python.py @@ -1,5 +1,13 @@ -from app import app +import os +from flask_migrate import Migrate +from app import create_app, db +from app.commands import register -if __name__ == '__main__': - app.run() +app = create_app(os.getenv('FLASK_CONFIG') or 'default') +migrate = Migrate(app, db) +register(app) + +@app.shell_context_processor +def make_shell_context(): + return dict(app=app, db=db) \ No newline at end of file diff --git a/tests/base.py b/tests/base.py index 9d81e03..35de2a2 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,36 +1,18 @@ import unittest -import os - -os.environ["OGN_CONFIG_MODULE"] = "config/test.py" - -from app import db # noqa: E402 +from app import create_app, db class TestBaseDB(unittest.TestCase): - @classmethod - def setUpClass(cls): - db.session.execute("CREATE EXTENSION IF NOT EXISTS postgis;") - db.session.commit() - db.drop_all() + def setUp(self): + self.app = create_app('testing') + self.app_context = self.app.app_context() + self.app_context.push() db.create_all() - def setUp(self): - pass - def tearDown(self): - db.session.execute( - """ - DELETE FROM aircraft_beacons; - DELETE FROM receiver_beacons; - DELETE FROM takeoff_landings; - DELETE FROM logbook; - DELETE FROM receiver_coverages; - DELETE FROM device_stats; - DELETE FROM receiver_stats; - DELETE FROM receivers; - DELETE FROM devices; - """ - ) + db.session.remove() + db.drop_all() + self.app_context.pop() if __name__ == "__main__": diff --git a/tests/collect/test_logbook.py b/tests/collect/test_logbook.py index 62020cf..2b1d847 100644 --- a/tests/collect/test_logbook.py +++ b/tests/collect/test_logbook.py @@ -9,6 +9,8 @@ from app.collect.logbook import update_entries class TestLogbook(TestBaseDB): def setUp(self): + super().setUp() + # Create basic data and insert self.dd0815 = Device(address="DD0815") self.dd4711 = Device(address="DD4711") diff --git a/tests/commands/test_database.py b/tests/commands/test_database.py index f82e39d..89ab601 100644 --- a/tests/commands/test_database.py +++ b/tests/commands/test_database.py @@ -1,17 +1,16 @@ import unittest import os -from tests.base import TestBaseDB, db - +from flask import current_app from app.model import DeviceInfo from app.commands.database import import_file -from app import app +from tests.base import TestBaseDB, db class TestDatabase(TestBaseDB): def test_import_ddb_file(self): - runner = app.test_cli_runner() + runner = current_app.test_cli_runner() result = runner.invoke(import_file, [os.path.dirname(__file__) + "/../custom_ddb.txt"]) self.assertEqual(result.exit_code, 0)