def test_raise(tmp_directory): p = Placeholder("{% raise 'some error message' %}") with pytest.raises(TemplateRuntimeError) as excinfo: p.render({}) assert str(excinfo.value) == 'some error message'
def test_macros_with_template_environment(env_init, path_to_test_pkg): env = env_init(path_to_test_pkg) # this template contains a macro placeholder = Placeholder(env.get_template('query.sql')) placeholder.render({}) assert str(placeholder) == 'SELECT * FROM table'
def test_error_if_missing_upstream(): p = Placeholder('SELECT * FROM {{upstream["name"]}}') upstream = Upstream({'a': 1}, name='task') with pytest.raises(UpstreamKeyError) as excinfo: p.render({'upstream': upstream}) assert ('Cannot obtain upstream dependency "name" for task "task"' in str(excinfo.value))
def test_error_if_no_upstream(): p = Placeholder('SELECT * FROM {{upstream["name"]}}') upstream = Upstream({}, name='task') with pytest.raises(UpstreamKeyError) as excinfo: p.render({'upstream': upstream}) msg = ('Cannot obtain upstream dependency "name". ' 'Task "task" has no upstream dependencies') assert msg == str(excinfo.value)
class GenericSource(PlaceholderSource): """ Generic source, the simplest type of source, it does not perform any kind of parsing nor validation Prameters --------- value: str The value for this source Notes ----- value is directly passed to ploomber.templates.Placeholder, which means pathlib.Path objects are read and str are converted to jinja2.Template objects, for more details see Placeholder documentation """ def __init__(self, value, hot_reload=False, optional=None, required=None): self._primitive = value # rename, and make it private self._placeholder = Placeholder(value, hot_reload=hot_reload, required=required) self._post_init_validation(self._placeholder) self._optional = optional self._required = required def render(self, params): if params.get('upstream'): with params.get('upstream'): self._placeholder.render(params, optional=self._optional) else: self._placeholder.render(params, optional=self._optional) self._post_render_validation(str(self._placeholder), params) return self @property def doc(self): return None @property def extension(self): return None def _post_render_validation(self, rendered_value, params): pass def _post_init_validation(self, value): pass def extract_upstream(self): return StringExtractor(self._placeholder._raw).extract_upstream()
def test_hot_reload_with_with_path(tmp_directory): query_path = Path(tmp_directory, 'simple_query.sql') query_path.write_text('SELECT * FROM {{tag}}') placeholder = Placeholder(Path(query_path), hot_reload=True) placeholder.render({'tag': 'table'}) assert str(placeholder) == 'SELECT * FROM table' query_path.write_text('SELECT * FROM {{tag}} WHERE x = 10') placeholder.render({'tag': 'table'}) assert str(placeholder) == 'SELECT * FROM table WHERE x = 10'
class PlaceholderSource(abc.Source): """ Source concrete class for the ones that use a Placeholder """ def __init__(self, value, hot_reload=False): self._primitive = value # rename, and make it private self._placeholder = Placeholder(value, hot_reload=hot_reload) self._post_init_validation(self._placeholder) @property def primitive(self): return self._primitive # TODO: rename to params @property def variables(self): return self._placeholder.variables def render(self, params): if params.get('upstream'): with params.get('upstream'): self._placeholder.render(params) else: self._placeholder.render(params) self._post_render_validation(str(self._placeholder), params) return self @property def loc(self): return self._placeholder.path def __str__(self): return str(self._placeholder) def __repr__(self): repr_ = "{}({})".format( type(self).__name__, self._placeholder.best_repr(shorten=True)) if self._placeholder.path: repr_ += ' Loaded from: {}'.format(self._placeholder.path) return repr_ @property def name(self): if self._placeholder.path is not None: # filename without extension(e.g., plot.py -> plot) return self._placeholder.path.stem
def execute(self, code): """Run code """ fd, path_to_tmp = tempfile.mkstemp() os.close(fd) Path(path_to_tmp).write_text(code) run_template = Placeholder(self.run_template) # we need quoting to make windows paths keep their \\ separators when # running shlex.split source = run_template.render( dict(path_to_code=shlex.quote(path_to_tmp))) res = subprocess.run(shlex.split(source), **self.subprocess_run_kwargs) Path(path_to_tmp).unlink() stdout = res.stdout.decode('utf-8') stderr = res.stderr.decode('utf-8') if res.returncode != 0: # log source code without expanded params self._logger.info( ('{} returned stdout: ' '{}\nstderr: {}\n' 'exit status {}').format(code, stdout, stderr, res.returncode)) raise RuntimeError( ('Error executing code.\nReturned stdout: ' '{}\nstderr: {}\n' 'exit status {}').format(stdout, stderr, res.returncode)) else: self._logger.info(('Finished running {}. stdout: {},' ' stderr: {}').format(self, stdout, stderr))
def test_hot_reload_with_template_env(env_init, path_to_test_pkg): query_path = Path(path_to_test_pkg, 'templates', 'query.sql') query_original = query_path.read_text() env = env_init(path_to_test_pkg) placeholder = Placeholder(env.get_template('query.sql'), hot_reload=True) placeholder.render({}) assert str(placeholder) == 'SELECT * FROM table' # use a macro to make sure the template loader is correctly initialized query = ('{% import "macros.sql" as m %}SELECT * FROM {{m.my_macro()}}' ' WHERE x = 10') query_path.write_text(query) placeholder.render({}) assert str(placeholder) == 'SELECT * FROM table WHERE x = 10' # revert query to their original value query_path.write_text(query_original)
def execute(self, code): """Run code """ ftp = self.connection.open_sftp() path_remote = self.path_to_directory + self._random_name() fd, path_to_tmp = tempfile.mkstemp() os.close(fd) Path(path_to_tmp).write_text(code) ftp.put(path_to_tmp, path_remote) ftp.close() run_template = Placeholder(self.run_template) source = run_template.render(dict(path_to_code=path_remote)) # stream stdout. related: https://stackoverflow.com/q/31834743 # using pty is not ideal, fabric has a clean implementation for this # worth checking out stdin, stdout, stderr = self.connection.exec_command(source, get_pty=True) for line in iter(stdout.readline, ""): self._logger.info('(STDOUT): {}'.format(line)) returncode = stdout.channel.recv_exit_status() stdout = ''.join(stdout) stderr = ''.join(stderr) if returncode != 0: # log source code without expanded params self._logger.info( '%s returned stdout: ' '%s and stderr: %s ' 'and exit status %s', code, stdout, stderr, returncode) raise subprocess.CalledProcessError(returncode, code) else: self._logger.info('Finished running %s. stdout: %s,' ' stderr: %s', self, stdout, stderr) return {'returncode': returncode, 'stdout': stdout, 'stderr': stderr}
def test_strict_templates_initialized_from_jinja_template(path_to_assets): path = str(path_to_assets / 'templates') env = Environment(loader=FileSystemLoader(path), undefined=StrictUndefined) st = Placeholder(env.get_template('template.sql')) assert st.render({'file': 1})
def test_init_placeholder_with_placeholder(): t = Placeholder('{{file}}') tt = Placeholder(t) assert tt.render({'file': 'some file'})