def create_parser(self, prog_name, subcommand): cfg = pulsar.Config(apps=['socket', 'pulse'], exclude=['debug'], description=self.help, version=self.get_version()) parser = cfg.parser() for option in self.option_list: flags = [] if option._short_opts: flags.extend(option._short_opts) if option._long_opts: flags.extend(option._long_opts) type = option.type if type == 'choice': type = None s = Setting(option.dest, flags=flags, choices=option.choices, default=option.default, action=option.action, type=type, nargs=option.nargs, desc=option.help) s.add_argument(parser) return parser
class Command(startproject.Command): option_list = (Setting('name', nargs=1, desc='Extension name.'), Setting('target', ['--target'], desc='directory containing the extension.')) help = ('Creates a Lux project directory structure for the given ' 'project name in the current directory or optionally in the ' 'given directory.') template_type = "extension"
class OdmCommand(lux.Command): option_list = ( Setting('dry_run', ('--dry-run',), action='store_true', default=False, desc='run the command but it doesnt do anything'), Setting('force', ('--force',), action='store_true', default=False, desc='remove pre-existing databases if required'), Setting('apps', nargs='*', desc='appname appname.ModelName ...'), ) args = '[appname appname.ModelName ...]'
def get_parser(self, **params): parser = argparse.ArgumentParser(**params) parser.add_argument('--version', action='version', version=self.get_version(), help="Show version number and exit") config = Setting('config', ('-c', '--config'), default=self.config_module, desc=('python dotted path to a Lux/Pulsar config ' ' file, where settings can be specified.')) config.add_argument(parser, True) for opt in self.default_option_list: opt.add_argument(parser, True) for opt in self.option_list: opt.add_argument(parser, True) return parser
class Command(lux.Command): option_list = (Setting('apps', nargs='*', desc='app app.modelname ...'), ) help = "Flush models in the data server." def __call__(self, argv, **params): return self.run_async(argv, **params) def run(self, argv, dump=True): options = self.options(argv) models = self.app.models if not models: self.logger.info('No model registered') apps = options.apps or None managers = models.flush(include=apps, dryrun=True) if managers: print('') print('Are you sure you want to remove these models?') print('') for manager in sorted(managers, key=lambda x: x._meta.modelkey): backend = manager.backend print('%s from %s' % (manager._meta, backend)) print('') yn = input('yes/no : ') if yn.lower() == 'yes': result = yield models.flush(include=apps) for manager, N in zip(managers, result): print('{0} - removed {1} models'.format(manager._meta, N)) else: print('Nothing done.') else: print('Nothing done. No models selected')
class Command(lux.Command): help = "Generate a secret key." option_list = ( Setting('length', ('--length', ), default=50, type=int, desc=('Secret key length')), Setting('hex', ('--hex', ), default=False, action='store_true', desc=('Hexadecimal string')), ) def run(self, options, **params): key = generate_secret(options.length, options.hex) self.write('Secret key:') self.write(key) self.write('-----------------------------------------------------') return key
class Command(lux.Command): option_list = (Setting('relative_url', ['--relative-url'], action="store_true", default=False, desc='Use relative urls rather than absolute ones. ' 'Useful during development.'), Setting('nominify', ['--nominify'], action="store_true", default=False, desc="Don't use minified media files")) help = "create the static site" def run(self, options): if options.relative_url: self.app.config['SITE_URL'] = '' if options.nominify: self.app.config['MINIFIED_MEDIA'] = False return self.app.extensions['static'].build(self.app)
class Command(lux.Command): option_list = ( Setting('dryrun', ('--dryrun',), action='store_true', default=False, desc=("It does not remove any data, instead it displays " "the number of models which could be removed")), Setting('apps', nargs='*', desc='app app.modelname ...') ) help = "Flush models in the data server." def run(self, options, interactive=True, **params): dryrun = options.dryrun mapper = self.app.mapper() self.write('\nFlush model data\n') if not mapper: return self.write('No model registered') apps = options.apps or None managers_count = yield from mapper.flush(include=apps, dryrun=True) if not managers_count: return self.write('Nothing done. No models selected') if not dryrun: self.write('\nAre you sure you want to remove these models?\n') for manager, N in sorted(managers_count, key=lambda x: x[0]._meta.table_name): self.write('%s - %s' % (manager, plural(N, 'model'))) # if dryrun: self.write('\nNothing done. Dry run') else: self.write('') yn = input('yes/no : ') if interactive else 'yes' if yn.lower() == 'yes': managers_count = yield from mapper.flush(include=apps) for manager, removed in sorted( managers_count, key=lambda x: x[0]._meta.table_name): N = plural(removed, 'model') self.write('{0} - removed {1}'.format(manager, N)) else: self.write('Nothing done.')
class Command(lux.Command): option_list = (Setting('relative_url', ['--relative-url'], action="store_true", default=False, desc='Use relative urls rather than absolute ones. ' 'Useful during development.'), ) help = "create the static site" def run(self, options): if options.relative_url: self.app.config['SITE_URL'] = '' return self.app.extensions['lux.extensions.static'].build(self.app)
class ConsoleParser(object): '''A class for parsing the console inputs.''' help = None option_list = () default_option_list = (Setting('loglevel', ('--log-level', ), meta='LEVEL', default='info', desc='Set the overall log level.', choices=('debug', 'info', 'warning', 'error', 'critical')), Setting('debug', ('--debug', ), action="store_true", default=False, desc='Run in debug mode.')) @property def config_module(self): raise NotImplementedError def get_version(self): raise NotImplementedError def get_parser(self, **params): parser = argparse.ArgumentParser(**params) parser.add_argument('--version', action='version', version=self.get_version(), help="Show version number and exit") config = Setting('config', ('-c', '--config'), default=self.config_module, desc=('python dotted path to a Lux/Pulsar config ' ' file, where settings can be specified.')) config.add_argument(parser, True) for opt in self.default_option_list: opt.add_argument(parser, True) for opt in self.option_list: opt.add_argument(parser, True) return parser
class Command(lux.Command): option_list = ( Setting('nginx', ('-n', '--nginx'), default='http://{0}'.format(platform.node()), desc='Nginx server as http://host or https://host.'), Setting('port', ('-p', '--nginx-port'), type=int, default=80, desc='Nginx server port.'), Setting('server', ('-s', '--server'), default='http://127.0.0.1:8060', desc='Python server as http://host:port.'), Setting('target', ('-t', '--target'), default='', desc='Target path of nginx config file.'), Setting('enabled', ('-e', '--enabled'), action='store_true', default=False, desc=('Save the file in the nginx ' '/etc/nginx/sites-enabled directory.')), ) help = "Creates a nginx config file for a reverse-proxy configuration." def run(self, args, target=None): options = self.options(args) target = target if target is not None else options.target nginx_server = '{0}:{1}'.format(options.nginx, options.port) path = None if options.enabled and os.name == 'posix': path = '/etc/nginx/sites-enabled' target = nginx_reverse_proxy_config(self.app, nginx_server, proxy_server=options.server, target=target, path=path).save() self.write('Created nginx configuration file "%s"', target) self.write('You need to restart nginx.') self.write('in linux /etc/init.d/nginx restart')
class Command(lux.Command): help = "Show parameters." option_list = (Setting('extensions', nargs='*', desc='Extensions to display parameters from.'), ) def run(self, options, **params): display = options.extensions config = self.app.config extensions = self.app.extensions for ext in chain([self.app], extensions.values()): if display and ext.meta.name not in display: continue if ext.meta.config: self.write('\n%s' % ext.meta.name) self.write('#=====================================') for key, value in ext.sorted_config(): self.write('%s: %s' % (key, config[key]))
class Command(lux.Command): help = "Starts a fully-functional Web server using pulsar" option_list = (Setting('nominify', ['--nominify'], action="store_true", default=False, desc="Don't use minified media files"),) def __call__(self, argv, start=True): app = self.app server = self.pulsar_app(argv, wsgi.WSGIServer) if server.cfg.nominify: app.params['MINIFIED_MEDIA'] = False if start and not server.logger: # pragma nocover if not pulsar.get_actor(): clear_logger() app._started = server() app.on_start(server) arbiter = pulsar.arbiter() arbiter.start() return app
class Command(lux.Command): option_list = ( Setting('apps', nargs='*', desc='appname appname.ModelName ...'), Setting('indent', ('--indent', ), default=-1, type=int, desc=('Specifies the indent level to use when' ' using json output.')), Setting('format', ('-f', '--format'), default='json', desc='The data format.'), Setting('target', ('-t', '--target'), default='stdnetdump', desc='Filename.'), Setting('listall', ('-l', '--list-all'), action='store_true', default=False, desc='List all supported formats and exit.'), ) help = ("Output the content of registered models in the data-server into " "a file given an output format. If no application is passed to " "the positional argument list, it looks if the config dictionary " "contains the DUMPDB_EXTENSIONS list, otherwise it dumps all " "registered models.") def __call__(self, argv, **params): return self.run_until_complete(argv, **params) def run(self, options, dump=True): apps = options.apps or self.app.config.get('DUMPDB_EXTENSIONS') models = self.app.extensions['api'].get_router() if not models: self.logger.info('No model registered') else: fname = '%s.%s' % (options.target, options.format) self.logger.info('Aggregating %s data into "%s".' % (options.format, fname)) stream = StringIO() for model in models: # If the model is marked as not serializable, skip it. if not getattr(model, 'serializable', True): continue model.write(stream, options.format) if dump: with open(file_name, 'w') as stream: serializer.write(stream) return serializer def get_models(self, apps): models = self.app.models() if apps: pass # if not getattr(model, 'serializable', True): # continue return models def preprocess(self, options, queries): return queries def listall(self): indent = options.indent if indent == -1: indent = None serializer = odm.get_serializer(options.format, indent=indent) if options.listall: for name in odm.all_serializers(): print(name) exit(0)
class Command(lux.Command): help = 'Create a superuser.' option_list = (Setting('username', ('--username', ), desc='Username'), Setting('password', ('--password', ), desc='Password')) def run(self, options, interactive=False): username = options.username password = options.password if not username or not password: interactive = True request = self.app.wsgi_request() auth_backend = self.app.extensions['lux.extensions.auth'] def_username = get_def_username(request, auth_backend) input_msg = 'Username' if def_username: input_msg += ' (Leave blank to use %s)' % def_username if interactive: # pragma nocover username = None password = None try: # Get a username while not username: username = input(input_msg + ': ') if def_username and username == '': username = def_username if not RE_VALID_USERNAME.match(username): self.write_err('Error: That username is invalid. Use ' 'only letters, digits and underscores.') username = None else: user = auth_backend.get_user(request, username=username) if user is not None: self.write_err( "Error: That username is already taken.\n") username = None # Get a password while 1: if not password: password = getpass.getpass() password2 = getpass.getpass('Password (again): ') if password != password2: self.write_err( "Error: Your passwords didn't match.") password = None continue if password.strip() == '': self.write_err( "Error: Blank passwords aren't allowed.") password = None continue break except KeyboardInterrupt: self.write_err('\nOperation cancelled.') sys.exit(1) user = auth_backend.create_superuser(request, username=username, password=password) if user: self.write("Superuser %s created successfully.\n" % user) else: self.write_err("ERROR: could not create superuser")
class Command(lux.Command): help = "Build the style-sheet file from installed applications" option_list = (Setting('theme', ('--theme', ), default='', desc='Theme to use. Default is lux.'), Setting('variables', ('--variables', ), action='store_true', default=False, desc=('Dump the theme variables as json' ' file for the theme specified')), Setting('cssfile', ('--cssfile', ), default='', desc=('Target path of css file. For example ' '"media/site/site.css". If not provided, ' 'a file called {{ STYLE }}.css will ' 'be created and put in "media/<sitename>" ' 'directory, if available, ' 'otherwise in the local directory.')), Setting('minify', ('--minify', ), action='store_true', default=False, desc='Also create a minified file')) def run(self, options, dump=True, **params): target = options.cssfile app = self.app name = app.meta.name self.theme = options.theme or name if not target and not options.variables: target = self.theme mdir = os.path.join(self.app.meta.path, 'media', name) if os.path.isdir(mdir): target = os.path.join(mdir, target) data = self.render(self.theme, options.variables) if dump: if target: targets = ['%s.css' % target] if options.minify: targets.append('%s.min.css' % target) for minify, target in enumerate(targets): if minify: data = self.minify(options, data) with open(target, 'w') as f: f.write(data) b = convert_bytes(len(data)) self.write('Created %s file. Size %s.' % (target, b)) return targets else: self.write(data) return data def render(self, theme, dump_variables): self.write('Building theme "%s".' % theme) css = Css(app=self.app) return css.dump(theme, dump_variables=dump_variables) def minify(self, options, data): b = convert_bytes(len(data)) self.write('Minimise %s css file via http://cssminifier.com' % b) http = HttpClient(loop=new_event_loop()) response = http.post('http://cssminifier.com/raw', encode_multipart=False, data={'input': data}) if response.status_code == 200: return native_str(response.get_content()) else: response.raise_for_status()
class Command(lux.Command): option_list = (Setting('name', nargs=1, desc='Name of the project.'), Setting('target', ['--target'], desc='directory containing the project.')) help = ('Creates a Lux project directory structure for the given ' 'project name in the current directory or optionally in the ' 'given directory.') template_type = "project" def run(self, argv, name=None, target=None): options = self.options(argv) name = options.name[0] validate_name(name, self.template_type) # Check that the name cannot be imported. try: import_module(name) except ImportError: pass else: raise lux.CommandError("%r conflicts with the name of an existing " "Python module and cannot be used as a " "%s name.\nPlease try another name." % (name, self.template_type)) # # if some directory is given, make sure it's nicely expanded if target is None: top_dir = path.join(os.getcwd(), name) try: os.makedirs(top_dir) except OSError as e: if e.errno == errno.EEXIST: message = "'%s' already exists" % top_dir else: message = e raise lux.CommandError(message) else: top_dir = path.abspath(path.expanduser(target)) if not path.exists(top_dir): raise lux.CommandError("Destination directory '%s' does not " "exist, please create it first." % top_dir) self.build(name, top_dir) self.write('%s "%s" created' % (self.template_type, name)) def add_context(self, context): # Create a random SECRET_KEY hash to put it in the main settings. chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' context['secret_key'] = random_string(chars, 50) def build(self, name, top_dir): base_name = '%s_name' % self.template_type template_dir = path.join(path.dirname(__file__), 'templates', self.template_type) context = {base_name: name} self.add_context(context) prefix_length = len(template_dir) + 1 for root, dirs, files in os.walk(template_dir): path_rest = root[prefix_length:] relative_dir = path_rest.replace(base_name, name) if relative_dir: target_dir = path.join(top_dir, relative_dir) if not path.exists(target_dir): os.mkdir(target_dir) for dirname in dirs[:]: if dirname.startswith('.') or dirname == '__pycache__': dirs.remove(dirname) for filename in files: if filename.endswith(('.pyo', '.pyc', '.py.class')): continue old_path = path.join(root, filename) with open(old_path, 'r') as template_file: content = template_file.read() content = Template(content).substitute(context) new_path = path.join(top_dir, relative_dir, filename.replace(base_name, name)) with open(new_path, 'w') as new_file: new_file.write(content)
class Command(lux.Command): option_list = (Setting('luxname', nargs='?', desc='Name of the project.'), Setting('template', ('--template',), default='default', desc='Site template'), Setting('template_list', ('--template-list',), default=False, action='store_true', desc='List of available site templates') ) help = ('Creates a Lux project directory structure for the given ' 'project name in the current directory or optionally in the ' 'given directory.') def run(self, options): self.template_dir = path.join(path.dirname(__file__), 'templates') if options.template_list: for name in os.listdir(self.template_dir): dir = os.path.join(self.template_dir, name) if os.path.isdir(dir) and not name.startswith('_'): self.write(name) else: template = options.template template_dir = path.join(self.template_dir, template) if not os.path.isdir(template_dir): raise lux.CommandError('Unknown template project "%s"' % template) if not options.luxname: raise lux.CommandError("A project name is required") name = options.luxname validate_name(name) self.target = path.join(os.getcwd(), '%s-project' % name) if path.exists(self.target): raise lux.CommandError("%r conflicts with an existing path" % self.target) # Check that the name cannot be imported. try: import_module(name) except ImportError: pass else: raise lux.CommandError("%r conflicts with the name of an " "existing Python module and cannot be " "used as a %s name.\n" "Please try another name." % (name, self.template_type)) # # if some directory is given, make sure it's nicely expanded try: os.makedirs(self.target) except OSError as e: raise lux.CommandError(str(e)) self.build(options.template, name) self.write('Project "%s" created' % name) def build(self, template, name): base_name = 'project_name' template_dir = path.join(self.template_dir, template) context = {base_name: name, 'secret_key': generate_secret(50)} if template != 'default': default_dir = path.join(self.template_dir, 'default') self._write(name, base_name, default_dir, context) self._write(name, base_name, template_dir, context) def _write(self, name, base_name, template_dir, context): prefix_length = len(template_dir) + 1 for root, dirs, files in os.walk(template_dir): path_rest = root[prefix_length:] relative_dir = path_rest.replace(base_name, name) if relative_dir: target_dir = path.join(self.target, relative_dir) if not path.exists(target_dir): os.mkdir(target_dir) for dirname in dirs[:]: if dirname.startswith('.') or dirname == '__pycache__': dirs.remove(dirname) for filename in files: if filename.endswith(('.pyo', '.pyc', '.py.class')): continue old_path = path.join(root, filename) with open(old_path, 'r') as template_file: content = template_file.read() if not filename.endswith('.html'): content = Template(content).substitute(context) new_path = path.join(self.target, relative_dir, filename.replace(base_name, name)) with open(new_path, 'w') as new_file: new_file.write(content)
class Command(lux.Command): help = 'Alembic commands for migrating database.' commands = [ 'auto', 'branches', 'current', 'downgrade', 'heads', 'history', 'init', 'merge', 'revision', 'show', 'stamp', 'upgrade' ] option_list = ( Setting('command', nargs='*', default=None, desc='Alembic command'), Setting('branch', ('-b', '--branch'), default=None, nargs='?', desc='Branch label for auto, revision and merge command', meta='LABEL'), Setting('list', ('--commands', ), default=None, action='store_true', desc='List available Alembic commands'), Setting('msg', ('-m', '--message'), nargs='?', default=None, desc='Message for auto, revision and merge command'), ) def run(self, opt): ''' Run obvious commands and validate more complex. ''' if opt.list: available = 'Available commands:\n%s' % ', '.join(self.commands) self.write(available) return available if opt.command: cmd = opt.command[0] if cmd not in self.commands: raise CommandError('Unrecognized command %s' % opt.command[0]) if cmd in ('auto', 'revision', 'merge') and not opt.msg: raise CommandError('Missing [-m] parameter for %s' % cmd) self.run_alembic_cmd(opt) return True raise CommandError('Pass [--commands] for available commands') def get_lux_template_directory(self): return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'template') def get_config(self): ''' Programmatically create Alembic config. To determine databases, DATASTORE from project's config file is used. To customize Alembic use MIGRATIONS in you config file. Example: MIGRATIONS = { 'alembic': { 'script_location': '<path>', 'databases': '<db_name1>,<db_name2>', }, '<db_name1>': { 'sqlalchemy.url': 'driver://*****:*****@localhost/dbname', }, '<bd_name2>': { 'sqlalchemy.url': 'driver://*****:*****@localhost/dbname', }, 'logging': { 'path': '<path_to_logging_config>', } } For more information about possible options, please visit Alembic documentation: https://alembic.readthedocs.org/en/latest/index.html ''' from alembic.config import Config # Because we are using custom template, we need to change default # implementation of get_template_directory() to make it pointing # to template stored in lux, we need to do this hack Config.get_template_directory = self.get_lux_template_directory # put migrations in main project dir migration_dir = os.path.join(self.app.meta.path, 'migrations') # set default settings in case where MIGRATIONS is not set alembic_cfg = Config() # where to place alembic env alembic_cfg.set_main_option('script_location', migration_dir) # get database(s) name(s) and location(s) odm = self.app.odm() databases = [] # set section for each found database for name, engine in odm.keys_engines(): if not name: name = 'default' databases.append(name) # url = str(engine.url).replace('+green', '') url = str(engine.url) alembic_cfg.set_section_option(name, 'sqlalchemy.url', url) # put databases in main options alembic_cfg.set_main_option("databases", ','.join(databases)) # create empty logging section to avoid raising errors in env.py alembic_cfg.set_section_option('logging', 'path', '') # obtain the metadata required for `auto` command self.get_metadata(alembic_cfg) # get rest of settings from project config. This may overwrite # already existing options (especially if different migration dir # is provided) cfg = self.app.config.get('MIGRATIONS') if cfg: for section in cfg.keys(): for key, value in cfg[section].items(): if section == 'alembic': alembic_cfg.set_main_option(key, value) else: alembic_cfg.set_section_option(section, key, value) return alembic_cfg def run_alembic_cmd(self, opt): ''' Logic for running different Alembic commands. ''' from alembic import command as alembic_cmd config = self.get_config() # command consume any number of parameters but first is command name cmd = opt.command.pop(0) # init command needs to point to lux template, not alembic default if cmd == 'init': dirname = config.get_main_option('script_location') # line 63 will be executed in: # https://github.com/zzzeek/alembic/blob/master/alembic/command.py # since we do not use any *.ini file, we simply silence error # about referenced before assignment as it have no negative impact. try: alembic_cmd.init(config, dirname, template='lux') except UnboundLocalError: # pragma nocover pass # merge required two revision name elif cmd == 'merge': if len(opt.command) != 2: raise CommandError('Command %s required revisions id.' % cmd) alembic_cmd.merge(config, opt.command, message=opt.msg, branch_label=opt.branch) elif cmd == 'revision': alembic_cmd.revision(config, message=opt.msg, branch_label=opt.branch) # auto command is a shortcut for `revision --autogenerate` elif cmd == 'auto': alembic_cmd.revision(config, autogenerate=True, message=opt.msg, branch_label=opt.branch) # this commands required revision name, but do not take any message or # branch labels elif cmd in ('show', 'stamp', 'upgrade', 'downgrade'): if len(opt.command) != 1: raise CommandError('Command %s required revision id' % cmd) getattr(alembic_cmd, cmd)(config, *opt.command) else: # execute commands without any additional params getattr(alembic_cmd, cmd)(config) def get_metadata(self, config): ''' MetaData object stored in odm extension contains aggregated data from all databases defined in project. This function splits the data to correspond with related database only. ''' from sqlalchemy import MetaData odm = self.app.odm() metadata = {} for key, db_engine in odm.keys_engines(): if not key: key = 'default' metadata[key] = meta = MetaData() for table, engine in odm.binds.items(): if engine == db_engine: table.tometadata(meta) config.metadata = metadata