diff --git a/README.md b/README.md index 10d5d0f..bdee940 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,7 @@ cd kilink git clone https://github.com/facundobatista/kilink.git source bin/activate pip install -r requirements.txt -./test -python2 kilink/kilink.py +PYTHONPATH=. python kilink/main.py ``` Or if you prefer to use Docker diff --git a/kilink.wsgi b/kilink.wsgi index 03b6a67..d8ee785 100644 --- a/kilink.wsgi +++ b/kilink.wsgi @@ -1,32 +1,13 @@ -import os import sys sys.path.insert(0, '/home/kilink/.virtualenvs/kilink/lib/python3.8/site-packages') sys.path.insert(0, "/home/kilink/project/production/") -from kilink import backend, main, loghelper -from sqlalchemy import create_engine -from kilink.config import config -config.load_file("/home/kilink/project/production/configs/production.yaml") +from kilink import main, loghelper +from kilink.config import config -# get config data -auth_config = config["db_auth_config"] -auth_file = os.path.abspath(os.path.join(os.path.dirname(__file__), auth_config)) -with open(auth_file) as fh: - vals = [x.strip() for x in fh.readlines()] -auth_data = dict(zip(("user", "pass"), vals)) -engine_data = config["db_engine"].format(**auth_data) - -# log setup -handlers = loghelper.setup_logging(config['log_directory']) -for h in handlers: - main.app.logger.addHandler(h) - -# set up the backend -engine = create_engine(engine_data) -main.kilinkbackend = backend.KilinkBackend(engine) +config.load_config() +loghelper.setup_logging(main.app.logger) application = main.app - - diff --git a/kilink/backend.py b/kilink/backend.py index 25daf69..f967759 100644 --- a/kilink/backend.py +++ b/kilink/backend.py @@ -14,7 +14,7 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, scoped_session -from kilink.config import config +from kilink.config import config, DB_ENGINE_INSTANCE_KEY # what we use for plain text PLAIN_TEXT = 'plain text' @@ -111,11 +111,19 @@ def new_func(self, *a, **k): class KilinkBackend(object): """Backend for Kilink.""" - def __init__(self, db_engine): - Base.metadata.create_all(db_engine) - Session = scoped_session(sessionmaker(autocommit=True)) - self.session = Session(bind=db_engine) + def __init__(self): self._cached_version = None + self._session = None + + @property + def session(self): + if self._session is None: + db_engine = config[DB_ENGINE_INSTANCE_KEY] + Base.metadata.create_all(db_engine) + Session = scoped_session(sessionmaker(autocommit=True)) + self._session = Session(bind=db_engine) + + return self._session def get_version(self): """Return the version, reading it from a file (cached).""" @@ -216,3 +224,5 @@ def build_tree(self, linkode_id): fringe.extend(children) return root_node, len(nodes) + +kilinkbackend = KilinkBackend() diff --git a/kilink/config.py b/kilink/config.py index fd1a6c2..e443077 100644 --- a/kilink/config.py +++ b/kilink/config.py @@ -3,8 +3,17 @@ """Config management.""" +import os import yaml +from sqlalchemy import create_engine + +ENVIRONMENT_KEY = "environment" +PROD_ENVIRONMENT_VALUE = "prod" +UNITTESTING_ENVIRONMENT_VALUE = "unittesting" + +DB_ENGINE_INSTANCE_KEY = "db_engine_instance" + class Config(dict): """Configuration helper class. @@ -17,6 +26,39 @@ def load_file(self, filename): cfg = yaml.safe_load(fh) self.update(cfg) + def load_config(self, environment=PROD_ENVIRONMENT_VALUE): + + if environment == PROD_ENVIRONMENT_VALUE: + self.load_file("/home/kilink/project/production/configs/production.yaml") + db_engine = self._prod_database_engine() + + elif environment == UNITTESTING_ENVIRONMENT_VALUE: + self.load_file("configs/development.yaml") + db_engine = self._unittesting_database_engine() + + else: + # defaults to dev environment + self.load_file("configs/development.yaml") + db_engine = self._dev_database_engine() + + self.update({ENVIRONMENT_KEY: environment, DB_ENGINE_INSTANCE_KEY: db_engine}) + + def _prod_database_engine(self): + auth_config = self.get("db_auth_config") + auth_file = os.path.abspath(os.path.join(os.path.dirname(__file__), auth_config)) + with open(auth_file) as fh: + vals = [x.strip() for x in fh.readlines()] + auth_data = dict(zip(("user", "pass"), vals)) + engine_data = self.get("db_engine").format(**auth_data) + + return create_engine(engine_data) + + def _dev_database_engine(self): + return create_engine(self.get("db_engine"), echo=True) + + def _unittesting_database_engine(self): + return create_engine("sqlite://") + config = Config() diff --git a/kilink/loghelper.py b/kilink/loghelper.py index ef670c9..31373f5 100644 --- a/kilink/loghelper.py +++ b/kilink/loghelper.py @@ -8,6 +8,8 @@ from logging.handlers import TimedRotatingFileHandler as TRFHandler +from kilink.config import config, ENVIRONMENT_KEY, PROD_ENVIRONMENT_VALUE + log_setup_lock = threading.Lock() @@ -46,14 +48,23 @@ def _setup(logdir, verbose): sys.excepthook = exception_handler -def setup_logging(logdir, verbose=False): +def setup_logging(_logger, verbose=False): """Set up the logging. This is thread-safe; it will only call the setup if logger doesn't have handlers already set. """ + logdir = config['log_directory'] + with log_setup_lock: - logger = logging.getLogger('kilink') - if not logger.handlers: + kilink_logger = logging.getLogger('kilink') + if not kilink_logger.handlers: _setup(logdir, verbose) - return logger.handlers + + for h in kilink_logger.handlers: + if config[ENVIRONMENT_KEY] != PROD_ENVIRONMENT_VALUE: + h.setLevel(logging.DEBUG) + _logger.addHandler(h) + + if config[ENVIRONMENT_KEY] != PROD_ENVIRONMENT_VALUE: + _logger.setLevel(logging.DEBUG) diff --git a/kilink/main.py b/kilink/main.py index a84828b..ad89c84 100644 --- a/kilink/main.py +++ b/kilink/main.py @@ -71,7 +71,7 @@ def tools(): @app.route('/version') def version(): """Show the project version, very very simple, just for developers/admin help.""" - return kilinkbackend.get_version() + return backend.kilinkbackend.get_version() # --- API @@ -83,7 +83,7 @@ def api_create(): text_type = request.form.get('text_type', "") logger.debug("API create start; type=%r size=%d", text_type, len(content)) try: - klnk = kilinkbackend.create_kilink(content, text_type) + klnk = backend.kilinkbackend.create_kilink(content, text_type) except backend.KilinkDataTooBigError: logger.debug("Content data too big; on creation") response = make_response() @@ -104,7 +104,7 @@ def api_update(linkode_id): logger.debug("API update start; linkode_id=%r parent=%r type=%r size=%d", linkode_id, parent, text_type, len(content)) try: - klnk = kilinkbackend.update_kilink(parent, content, text_type) + klnk = backend.kilinkbackend.update_kilink(parent, content, text_type) except backend.KilinkNotFoundError: logger.debug("API update done; linkode_id %r not found", linkode_id) response = make_response() @@ -130,10 +130,10 @@ def api_get(linkode_id, revno=None): # the linkode_id to get the info from is the second token linkode_id = revno - klnk = kilinkbackend.get_kilink(linkode_id) + klnk = backend.kilinkbackend.get_kilink(linkode_id) # get the tree - tree, nodeq = kilinkbackend.build_tree(linkode_id) + tree, nodeq = backend.kilinkbackend.build_tree(linkode_id) logger.debug("API get done; type=%r size=%d len_tree=%d", klnk.text_type, len(klnk.content), nodeq) @@ -144,16 +144,9 @@ def api_get(linkode_id, revno=None): if __name__ == "__main__": # load config - config.load_file("configs/development.yaml") - - # log setup - handlers = loghelper.setup_logging(config['log_directory'], verbose=True) - for h in handlers: - app.logger.addHandler(h) - h.setLevel(logging.DEBUG) - app.logger.setLevel(logging.DEBUG) - - # set up the backend - engine = create_engine(config["db_engine"], echo=True) - kilinkbackend = backend.KilinkBackend(engine) + config.load_config(environment="dev") + + # logging setup + loghelper.setup_logging(app.logger, verbose=True) + app.run(debug=True, host='0.0.0.0') diff --git a/tests/test_api.py b/tests/test_api.py index e7af1e5..137360a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -13,7 +13,7 @@ from sqlalchemy import create_engine from kilink import main, backend -from kilink.config import config +from kilink.config import config, UNITTESTING_ENVIRONMENT_VALUE class _ANY(object): @@ -38,9 +38,7 @@ class BaseTestCase(TestCase): def setUp(self): """Set up.""" super(BaseTestCase, self).setUp() - config.load_file("configs/development.yaml") - engine = create_engine("sqlite://") - self.backend = main.kilinkbackend = backend.KilinkBackend(engine) + config.load_config(UNITTESTING_ENVIRONMENT_VALUE) self.app = main.app.test_client() def api_create(self, data, code=201): @@ -79,7 +77,7 @@ def test_create_simple(self): resp = self.api_create(data=datos) - klnk = self.backend.get_kilink(resp["linkode_id"]) + klnk = backend.kilinkbackend.get_kilink(resp["linkode_id"]) self.assertEqual(klnk.content, content) self.assertEqual(klnk.text_type, text_type) self.assertLess(klnk.timestamp, datetime.datetime.utcnow()) @@ -92,7 +90,7 @@ def test_create_error(self): # make it fail! - with patch.object(self.backend, 'create_kilink') as mock: + with patch.object(backend.kilinkbackend, 'create_kilink') as mock: mock.side_effect = ValueError("foo") self.api_create(data=datos, code=500) @@ -102,7 +100,7 @@ def test_create_no_text_type(self): datos = {'content': content} resp = self.api_create(data=datos) - klnk = self.backend.get_kilink(resp["linkode_id"]) + klnk = backend.kilinkbackend.get_kilink(resp["linkode_id"]) self.assertEqual(klnk.content, content) self.assertEqual(klnk.text_type, backend.PLAIN_TEXT) self.assertLess(klnk.timestamp, datetime.datetime.utcnow()) @@ -113,7 +111,7 @@ def test_create_empty_text_type(self): datos = {'content': content, 'text_type': ""} resp = self.api_create(data=datos) - klnk = self.backend.get_kilink(resp["linkode_id"]) + klnk = backend.kilinkbackend.get_kilink(resp["linkode_id"]) self.assertEqual(klnk.content, content) self.assertEqual(klnk.text_type, backend.PLAIN_TEXT) self.assertLess(klnk.timestamp, datetime.datetime.utcnow()) @@ -133,7 +131,7 @@ def test_update_simple(self): resp = self.api_update(linkode_id, data=child_content) revno1 = resp["revno"] - klnk = self.backend.get_kilink(revno1) + klnk = backend.kilinkbackend.get_kilink(revno1) self.assertEqual(klnk.content, u"Moñito") self.assertEqual(klnk.text_type, u"type2") self.assertLess(klnk.timestamp, datetime.datetime.utcnow()) @@ -146,7 +144,7 @@ def test_update_simple(self): resp = self.api_update(linkode_id, data=child_content2) revno2 = resp["revno"] - klnk = self.backend.get_kilink(revno2) + klnk = backend.kilinkbackend.get_kilink(revno2) self.assertEqual(klnk.content, u"Moñito2") self.assertEqual(klnk.text_type, u"type3") self.assertLess(klnk.timestamp, datetime.datetime.utcnow()) diff --git a/tests/test_backend.py b/tests/test_backend.py index 8d99de3..d6ece4c 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -19,7 +19,7 @@ PLAIN_TEXT, _get_unique_id, ) -from kilink.config import config +from kilink.config import config, UNITTESTING_ENVIRONMENT_VALUE, DB_ENGINE_INSTANCE_KEY class BaseTestCase(TestCase): @@ -28,9 +28,9 @@ class BaseTestCase(TestCase): def setUp(self): """Set up.""" super(BaseTestCase, self).setUp() - config.load_file("configs/development.yaml") - self.db_engine = create_engine("sqlite://") - self.bkend = KilinkBackend(self.db_engine) + config.load_config(environment=UNITTESTING_ENVIRONMENT_VALUE) + self.db_engine = config[DB_ENGINE_INSTANCE_KEY] + self.bkend = KilinkBackend() class ContentTestCase(BaseTestCase):