class Command(LuxCommand): help = 'Create a superuser.' option_list = (Setting('username', ('--username', ), desc='Username'), ) def run(self, options, interactive=False): username = options.username if not username: interactive = True request = self.app.wsgi_request() auth_backend = self.app.auth_backend if interactive: # pragma nocover try: # Get a username username = input('username: '******'username "%s" is not available' % username) token = auth_backend.create_token(request, user) if token: self.write("token created successfully.\n") self.write(token.id.hex) else: raise CommandError("Could not create token") return token.id.hex
class Command(LuxCommand): help = 'Create a superuser.' option_list = (Setting('username', ('--username', ), desc='Username'), ) def run(self, options, interactive=False): username = options.username if not username: interactive = True request = self.app.wsgi_request() auth_backend = self.app.auth_backend auth_backend.request(request.environ) if interactive: # pragma nocover def_username = get_def_username(request, auth_backend) input_msg = 'Username' if def_username: input_msg += ' (Leave blank to use %s)' % def_username while not username: username = input(input_msg + ': ') if def_username and username == '': username = def_username user = auth_backend.get_user(request, username=username) if user is None: raise CommandError('user %s not available' % username) token = auth_backend.create_token(request, user) self.write('Token: %s' % as_hex(token.id)) return token
class Command(LuxCommand): help = "Generate a secret key." option_list = ( Setting('length', ('--length', ), default=64, 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, hexadecimal=options.hex) self.write('Secret key:') self.write(key) self.write('-----------------------------------------------------') return key
class Command(LuxCommand): 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( 'models', nargs='+', desc='model1 model2 ... Use * to include all models')) help = "Flush models in the data servers" def run(self, options, interactive=True, yn='yes'): if options.models[0] == '*': models = list(self.app.models) else: models = [] for model in options.models: if model in self.app.models: models.append(model) if not models: return self.write('Nothing done. No models') # self.write('\nAre you sure you want to remove these models?\n') for model in sorted(models): self.write('%s' % model) # if options.dryrun: self.write('\nNothing done. Dry run') else: self.write('') yn = input('yes/no : ') if interactive else yn if yn.lower() == 'yes': request = self.app.wsgi_request() for model in models: model = self.app.models[model] with model.session(request) as session: N = model.query(request, session).delete() self.write('{0} - removed {1}'.format(model, N)) else: self.write('Nothing done')
class Command(LuxCommand): help = "Clear Sessions" option_list = (Setting('app_name', nargs='?', desc=('Optional app name. If omitted the default ' 'application name is used (APP_NAME)')), ) def run(self, options, **params): result = session_backend(self.app).clear(options.app_name) self.write('Clear %d sessions' % result) return result
class Command(LuxCommand): help = "Clear Cache" option_list = ( Setting('prefix', nargs='?', desc=('Optional cache prefix. If omitted the default ' 'application prefix is used (APP_NAME)')), ) def run(self, options, **params): cache = self.app.cache_server result = cache.clear(options.prefix) self.write('Clear %d keys' % result) return result
class Command(LuxCommand): help = "Stop a running server" option_list = ( Setting('timeout', ('--timeout',), default=10, type=int, desc=('Timeout for waiting SIGTERM stop')), ) pulsar_config_include = ('log_level', 'log_handlers', 'debug', 'config', 'pid_file') def run(self, options, **params): app = self.app pid_file = options.pid_file if pid_file: if os.path.isfile(pid_file): pid = Pidfile(pid_file).read() if not pid: raise CommandError('No pid in pid file %s' % pid_file) else: raise CommandError('Could not located pid file %s' % pid_file) else: raise CommandError('Pid file not available') try: self.kill(pid, signal.SIGTERM) except ProcessLookupError: raise CommandError('Process %d does not exist' % pid) from None start = time.time() while time.time() - start < options.timeout: if os.path.isfile(pid_file): time.sleep(0.2) else: app.write('Process %d terminated' % pid) return 0 app.write_err('Could not terminate process after %d seconds' % options.timeout) self.kill(pid, signal.SIGKILL) app.write_err('Processed %d killed' % pid) return 1 def kill(self, pid, sig): # pragma nocover os.kill(pid, sig)
class Command(LuxCommand): 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(LuxCommand): help = 'Regenerate an application token' option_list = ( Setting('app_id', nargs='*', desc='Application ID'), ) def run(self, options, **params): auth_backend = self.app.auth_backend request = self.app.wsgi_request(urlargs={}, app_handler=True) request.cache.auth_backend = auth_backend model = self.app.models['applications'] app_id = options.app_id or request.config['MASTER_APPLICATION_ID'] if not app_id: raise CommandError('app_id not available') try: app_domain = model.get_instance(request, id=app_id) except Http404: raise CommandError('app_id %s not available' % app_id) from None token = model.regenerate_token(request, app_domain) self.write(token) return token
class Command(LuxCommand): option_list = ( Setting('base_url', ['--base-url'], nargs=1, desc='Static website base url'), Setting('static_path', ['--static-path'], desc='Path where to install files'), ) help = "create a static site" async def run(self, options): try: from bs4 import BeautifulSoup as bs except ImportError: raise CommandError( 'Requires BeautifulSoup: pip install beautifulsoup4') from None path = options.static_path if not path: path = os.getcwd() if not options.base_url: raise CommandError('specify base url with --base-url flag') base = options.base_url[0] self.app.config['CACHE_SERVER'] = 'static://' self.bs = bs self.http = HttpTestClient(self.app.callable, wsgi=self.app) self.files = {} urls = ['%s/sitemap.xml' % base] self.app.cms.set_priority = False await self.build_from_rurls(path, base, urls) self.app.cms.set_priority = True self.app.cache_server.clear() self.files = {} await self.build_from_rurls(path, base, urls) async def build_from_rurls(self, path, base, urls): await gather(*[self.build(path, base, url) for url in urls]) async def build(self, path, base, url): if url in self.files: self.app.logger.warning('Url %s already processed', url) return self.files[url] = None response = await self.http.get(url) response.raise_for_status() urlp = urlparse(url) # # save file html = response.text() rel_path = urlp.path if not rel_path: rel_path = 'index' if url.endswith('.xml'): soup = self.bs(response.content, 'html.parser') urls = [] for u in chain(soup.findAll('sitemap'), soup.findAll('url')): loc = u.find('loc') if not loc: continue url = loc.string if url.startswith(base): if not self.app.cms.set_priority or url.endswith('.xml'): urls.append(url) if urls: await self.build_from_rurls(path, base, urls) else: rel_path = '%s.html' % rel_path bits = [r for r in rel_path.split('/') if r] filename = os.path.join(path, *bits) dirname = os.path.dirname(filename) if not os.path.isdir(dirname): os.makedirs(dirname) with open(filename, 'w') as fp: fp.write(html) self.files[url] = filename
class Command(LuxCommand): 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. ''' from alembic import util try: if opt.list: available = 'Alembic 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') except util.CommandError as exc: raise CommandError(str(exc)) 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
import pulsar from pulsar.apps import wsgi from pulsar.utils.log import clear_logger from lux.core import LuxCommand, Setting nominify = Setting('nominify', ['--nominify'], action="store_true", default=False, desc="Don't use minified media files") class Command(LuxCommand): help = "Starts a fully-functional Web server using pulsar" option_list = (nominify, ) wsgiApp = wsgi.WSGIServer def __call__(self, argv, start=True, get_app=False): self.app.callable.command = self.name app = self.app server = self.pulsar_app(argv, self.wsgiApp) 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()
class Command(LuxCommand): option_list = (Setting('luxname', nargs='?', desc='Name of the project.'), ) 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): if not options.luxname: raise CommandError("A project name is required") name = options.luxname validate_name(name) target = path.join(os.getcwd(), '%s-project' % name) if path.exists(target): raise CommandError("%r conflicts with an existing path" % target) # Check that the name cannot be imported. try: import_module(name) except ImportError: pass else: raise CommandError("%r conflicts with the name of an " "existing Python module and cannot be " "used as a name.\n" "Please try another name." % name) # # if some directory is given, make sure it's nicely expanded try: os.makedirs(target) except OSError as e: raise CommandError(str(e)) from e self.build(name, target) self.write('Project "%s" created' % name) return target def build(self, name, target): render = self.app.template_engine() base_name = 'project_name' context = {base_name: name, 'secret_key': generate_secret(50)} 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(target, relative_dir) if not path.exists(target_dir): os.mkdir(target_dir) for dirname in dirs[:]: if skipfile(dirname): dirs.remove(dirname) for filename in files: if (skipfile(filename) or 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 = render(content, context) new_path = path.join(target, relative_dir, filename.replace(base_name, name)) with open(new_path, 'w') as new_file: new_file.write(content)
class Command(LuxCommand): help = 'Create a superuser.' option_list = (Setting('username', ('--username', ), desc='Username'), Setting('password', ('--password', ), desc='Password'), Setting('email', ('--email', ), desc='Email')) def run(self, options, interactive=False): username = options.username password = options.password email = options.email if not username or not password or not email: interactive = True request = self.app.wsgi_request() auth_backend = self.app.auth_backend auth_backend.request(request.environ) if interactive: # pragma nocover def_username = get_def_username(request, auth_backend) input_msg = 'Username' if def_username: input_msg += ' (Leave blank to use %s)' % def_username username = None email = 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 while not email: email = input('Email: ') try: email = normalise_email(email) except Exception: self.write_err('Error: That email is invalid.') email = None else: user = auth_backend.get_user(request, email=email) if user is not None: self.write_err( "Error: That email is already taken.") email = 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.') return user = auth_backend.create_superuser(request, username=username, email=normalise_email(email), password=password) if user: self.write("Superuser %s created successfully.\n" % user.username) else: self.write_err("ERROR: could not create superuser") return user