def test_overlay(self): # +ChainConfig updates configs successively conf = ChainConfig() conf.a = AttrDict() conf.b = AttrDict() conf.a.x = 1 conf.a.y = 2 conf.b.x = 2 eq_(+conf, {'x': 2, 'y': 2}) conf.b.x = None eq_(+conf, {'y': 2})
def test_merge(self): # Config files are loaded and merged unlink(self.temp) conf = ChainConfig([ ('a', PathConfig(self.a)), ('b', PathConfig(self.b))]) eq_(+conf, PathConfig(self.final))
def test_attrdict(self): # ChainConfig is an AttrDict conf = ChainConfig(a=AttrDict(), b=AttrDict()) conf.a.x = 1 conf.a.y = 2 eq_(conf, {'a': {'x': 1, 'y': 2}, 'b': {}}) conf.b.x = 3 conf.b.y = 4 eq_(conf, {'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'y': 4}})
def test_chain_update(self): # Chained config files are changed on update # Set up a configuration with 2 files -- conf1.test and conf2.test. with self.conf1.open(mode='w', encoding='utf-8') as handle: yaml.dump({'url': {}}, handle) with self.conf2.open(mode='w', encoding='utf-8') as handle: yaml.dump({'url': {'a': 1}}, handle) conf = ChainConfig() conf.conf1 = PathConfig(self.conf1) conf.conf2 = PathConfig(self.conf2) eq_(+conf, {'url': {'a': 1}}) # Change conf2.test and ensure that its original contents are replaced, # not just merged with previous value with self.conf2.open(mode='w', encoding='utf-8') as handle: yaml.dump({'url': {'b': 10}}, handle) eq_(+conf, {'url': {'b': 10}})
def test_default(self): # Missing, empty or malformed config files return an empty AttrDict conf = ChainConfig([ ('missing', PathConfig(self.missing)), ('error', PathConfig(self.error)), ('empty', PathConfig(self.empty)), ('string', PathConfig(self.string)), ]) eq_(+conf, AttrDict())
def test_import(self): # Check if config files are imported conf_imp = ChainConfig(conf=PathConfig(self.imp)) conf_b = ChainConfig(conf=PathConfig(self.b)) # When temp is missing, config matches b unlink(self.temp) eq_(+conf_imp, +conf_b) # Once temp file is created, it is automatically imported data = AttrDict(a=1, b=2) with self.temp.open('w') as out: yaml.dump(dict(data), out) result = +conf_b result.update(data) eq_(+conf_imp, result) # Once removed, it no longer used unlink(self.temp) eq_(+conf_imp, +conf_b)
def test_update(self): # Config files are updated on change conf = ChainConfig(temp=PathConfig(self.temp)) # When the file is missing, config is empty unlink(self.temp) eq_(+conf, {}) # When the file is blank, config is empty with self.temp.open('w') as out: out.write('') eq_(+conf, {}) # Once created, it is automatically reloaded data = AttrDict(a=1, b=2) with self.temp.open('w') as out: yaml.dump(dict(data), out) eq_(+conf, data) # Deleted file is detected self.temp.unlink() eq_(+conf, {})
exe_path = which(exe) if exe_path is not None: cmd = cmd.format(FILE=setup_file, EXE=exe_path) app_log.info('Running %s', cmd) _run_console(cmd, cwd=target) break else: app_log.warning('Skipping %s. No %s found', setup_file, exe) app_dir = Path(variables.get('GRAMEXDATA')) / 'apps' if not app_dir.exists(): app_dir.mkdir(parents=True) # Get app configuration by chaining apps.yaml in gramex + app_dir + command line apps_config = ChainConfig() apps_config['base'] = PathConfig(gramex.paths['source'] / 'apps.yaml') user_conf_file = app_dir / 'apps.yaml' apps_config['user'] = PathConfig( user_conf_file) if user_conf_file.exists() else AttrDict() app_keys = { 'url': 'URL / filename of a ZIP file to install', 'cmd': 'Command used to install file', 'dir': 'Sub-directory under "url" to run from (optional)', 'contentdir': 'Strip root directory with a single child (optional, default=True)', 'target': 'Local directory where the app is installed', 'installed': 'Additional installation information about the app', 'run': 'Runtime keyword arguments for the app', }
import os import sys import json import yaml import logging import logging.config import tornado.ioloop from pathlib import Path from orderedattrdict import AttrDict from gramex.config import ChainConfig, PathConfig, app_log, variables, setup_variables from gramex.config import ioloop_running, prune_keys, setup_secrets paths = AttrDict() # Paths where configurations are stored conf = AttrDict() # Final merged configurations config_layers = ChainConfig() # Loads all configurations. init() updates it appconfig = AttrDict() # Final app configuration paths['source'] = Path( __file__).absolute().parent # Where gramex source code is paths['base'] = Path('.') # Where gramex is run from callbacks = {} # Services callbacks # Populate __version__ from release.json with (paths['source'] / 'release.json').open() as _release_file: release = json.load(_release_file, object_pairs_hook=AttrDict) __version__ = release.info.version _sys_path = list(sys.path) # Preserve original sys.path
def test_variables(self): # Templates interpolate string variables # Create configuration with 2 layers and a subdirectory import conf = +ChainConfig( base=PathConfig(self.chain.base), child=PathConfig(self.chain.child), ) # Custom variables are deleted after use ok_('variables' not in conf) for key in ['base', 'child', 'subdir']: # {.} maps to YAML file's directory eq_(conf['%s_DOT' % key], str(self.chain[key].parent)) # $YAMLPATH maps to YAML file's directory eq_(conf['%s_YAMLPATH' % key], str(self.chain[key].parent)) # $YAMLURL is the relative path to YAML file's directory eq_(conf['%s_YAMLURL' % key], conf['%s_YAMLURL_EXPECTED' % key]) # Environment variables are present by default eq_(conf['%s_HOME' % key], os.environ.get('HOME', '')) # Non-existent variables map to '' eq_(conf['%s_NONEXISTENT' % key], os.environ.get('NONEXISTENT', '')) # Custom variables are applied eq_(conf['%s_THIS' % key], key) # Custom variables are inherited. Defaults do not override eq_(conf['%s_ROOT' % key], conf.base_ROOT) # Default variables are set eq_(conf['%s_DEFAULT' % key], key) # Functions run and override values eq_(conf['%s_FUNCTION' % key], key) # Default functions "underride" values eq_(conf['%s_DEFAULT_FUNCTION' % key], 'base') # Functions can use variables using gramex.config.variables eq_(conf['%s_FUNCTION_VAR' % key], conf.base_ROOT + key) # Derived variables eq_(conf['%s_DERIVED' % key], '%s/derived' % key) # $URLROOT is the frozen to base $YAMLURL eq_(conf['%s_YAMLURL_VAR' % key], conf['%s_YAMLURL_VAR_EXPECTED' % key]) # $GRAMEXPATH is the gramex path gramex_path = os.path.dirname(inspect.getfile(gramex)) eq_(conf['%s_GRAMEXPATH' % key], gramex_path) # $GRAMEXAPPS is the gramex apps path eq_(conf['%s_GRAMEXAPPS' % key], os.path.join(gramex_path, 'apps')) # $GRAMEXHOST is the socket.gethostname eq_(conf['%s_GRAMEXHOST' % key], socket.gethostname()) # Imports do not override, but do setdefault eq_(conf['path'], str(self.chain['base'].parent)) eq_(conf['subpath'], str(self.chain['subdir'].parent)) # Check if variable types are preserved eq_(conf['numeric'], 1) eq_(conf['boolean'], True) eq_(conf['object'], {'x': 1}) eq_(conf['list'], [1, 2]) # Check if variables of different types are string substituted eq_(conf['numeric_subst'], '/1') eq_(conf['boolean_subst'], '/True') # Actually, conf['object_subst'] is "/AttrDict([('x', 1)])". Let's not test that. # eq_(conf['object_subst'], "/{'x': 1}") eq_(conf['list_subst'], '/[1, 2]') # Check condition variables for key, val in conf['conditions'].items(): eq_('is-' + key, val)
import os import pytest import re import requests import time import gramex.cache from fnmatch import fnmatch from lxml.html import document_fromstring from selenium import webdriver from selenium.common.exceptions import NoSuchElementException from six import string_types from tornado.web import create_signed_value from gramex.config import ChainConfig, PathConfig, objectpath, variables # Get Gramex conf from current directory gramex_conf = ChainConfig() gramex_conf['source'] = PathConfig(os.path.join(variables['GRAMEXPATH'], 'gramex.yaml')) gramex_conf['base'] = PathConfig('gramex.yaml') secret = objectpath(+gramex_conf, 'app.settings.cookie_secret') drivers = {} default = object() class ChromeConf(dict): def __init__(self, **conf): self['goog:chromeOptions'] = {'args': ['--no-sandbox']} for key, val in conf.items(): getattr(self, key)(val) def headless(self, val): if val: