okstra 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/runtime/BUILD.json +2 -2
  3. package/runtime/agents/SKILL.md +3 -3
  4. package/runtime/agents/workers/report-writer-worker.md +45 -67
  5. package/runtime/bin/okstra-render-final-report.py +101 -0
  6. package/runtime/bin/okstra-render-report-views.py +17 -10
  7. package/runtime/bin/okstra-token-usage.py +3 -1
  8. package/runtime/python/okstra_ctl/final_report_schema.py +253 -0
  9. package/runtime/python/okstra_ctl/render_final_report.py +201 -0
  10. package/runtime/python/okstra_ctl/report_views.py +108 -305
  11. package/runtime/python/okstra_token_usage/__init__.py +5 -1
  12. package/runtime/python/okstra_token_usage/cli.py +66 -36
  13. package/runtime/python/okstra_token_usage/report.py +148 -65
  14. package/runtime/python/okstra_vendor/__init__.py +37 -0
  15. package/runtime/python/okstra_vendor/jinja2/__init__.py +38 -0
  16. package/runtime/python/okstra_vendor/jinja2/_identifier.py +6 -0
  17. package/runtime/python/okstra_vendor/jinja2/async_utils.py +99 -0
  18. package/runtime/python/okstra_vendor/jinja2/bccache.py +408 -0
  19. package/runtime/python/okstra_vendor/jinja2/compiler.py +1998 -0
  20. package/runtime/python/okstra_vendor/jinja2/constants.py +20 -0
  21. package/runtime/python/okstra_vendor/jinja2/debug.py +191 -0
  22. package/runtime/python/okstra_vendor/jinja2/defaults.py +48 -0
  23. package/runtime/python/okstra_vendor/jinja2/environment.py +1672 -0
  24. package/runtime/python/okstra_vendor/jinja2/exceptions.py +166 -0
  25. package/runtime/python/okstra_vendor/jinja2/ext.py +870 -0
  26. package/runtime/python/okstra_vendor/jinja2/filters.py +1873 -0
  27. package/runtime/python/okstra_vendor/jinja2/idtracking.py +318 -0
  28. package/runtime/python/okstra_vendor/jinja2/lexer.py +868 -0
  29. package/runtime/python/okstra_vendor/jinja2/loaders.py +693 -0
  30. package/runtime/python/okstra_vendor/jinja2/meta.py +112 -0
  31. package/runtime/python/okstra_vendor/jinja2/nativetypes.py +130 -0
  32. package/runtime/python/okstra_vendor/jinja2/nodes.py +1206 -0
  33. package/runtime/python/okstra_vendor/jinja2/optimizer.py +48 -0
  34. package/runtime/python/okstra_vendor/jinja2/parser.py +1049 -0
  35. package/runtime/python/okstra_vendor/jinja2/py.typed +0 -0
  36. package/runtime/python/okstra_vendor/jinja2/runtime.py +1062 -0
  37. package/runtime/python/okstra_vendor/jinja2/sandbox.py +436 -0
  38. package/runtime/python/okstra_vendor/jinja2/tests.py +256 -0
  39. package/runtime/python/okstra_vendor/jinja2/utils.py +766 -0
  40. package/runtime/python/okstra_vendor/jinja2/visitor.py +92 -0
  41. package/runtime/python/okstra_vendor/markupsafe/__init__.py +396 -0
  42. package/runtime/python/okstra_vendor/markupsafe/_native.py +8 -0
  43. package/runtime/python/okstra_vendor/markupsafe/py.typed +0 -0
  44. package/runtime/schemas/final-report-v1.0.schema.json +1391 -0
  45. package/runtime/skills/okstra-report-writer/SKILL.md +29 -28
  46. package/runtime/templates/reports/final-report.template.md +370 -411
  47. package/runtime/templates/reports/report.css +12 -6
  48. package/runtime/validators/lib/fixtures.sh +7 -7
  49. package/runtime/validators/validate-report-views.py +24 -153
  50. package/runtime/validators/validate-run.py +102 -19
  51. package/src/install.mjs +20 -1
@@ -0,0 +1,112 @@
1
+ """Functions that expose information about templates that might be
2
+ interesting for introspection.
3
+ """
4
+
5
+ import typing as t
6
+
7
+ from . import nodes
8
+ from .compiler import CodeGenerator
9
+ from .compiler import Frame
10
+
11
+ if t.TYPE_CHECKING:
12
+ from .environment import Environment
13
+
14
+
15
+ class TrackingCodeGenerator(CodeGenerator):
16
+ """We abuse the code generator for introspection."""
17
+
18
+ def __init__(self, environment: "Environment") -> None:
19
+ super().__init__(environment, "<introspection>", "<introspection>")
20
+ self.undeclared_identifiers: t.Set[str] = set()
21
+
22
+ def write(self, x: str) -> None:
23
+ """Don't write."""
24
+
25
+ def enter_frame(self, frame: Frame) -> None:
26
+ """Remember all undeclared identifiers."""
27
+ super().enter_frame(frame)
28
+
29
+ for _, (action, param) in frame.symbols.loads.items():
30
+ if action == "resolve" and param not in self.environment.globals:
31
+ self.undeclared_identifiers.add(param)
32
+
33
+
34
+ def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:
35
+ """Returns a set of all variables in the AST that will be looked up from
36
+ the context at runtime. Because at compile time it's not known which
37
+ variables will be used depending on the path the execution takes at
38
+ runtime, all variables are returned.
39
+
40
+ >>> from jinja2 import Environment, meta
41
+ >>> env = Environment()
42
+ >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
43
+ >>> meta.find_undeclared_variables(ast) == {'bar'}
44
+ True
45
+
46
+ .. admonition:: Implementation
47
+
48
+ Internally the code generator is used for finding undeclared variables.
49
+ This is good to know because the code generator might raise a
50
+ :exc:`TemplateAssertionError` during compilation and as a matter of
51
+ fact this function can currently raise that exception as well.
52
+ """
53
+ codegen = TrackingCodeGenerator(ast.environment) # type: ignore
54
+ codegen.visit(ast)
55
+ return codegen.undeclared_identifiers
56
+
57
+
58
+ _ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
59
+ _RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]
60
+
61
+
62
+ def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
63
+ """Finds all the referenced templates from the AST. This will return an
64
+ iterator over all the hardcoded template extensions, inclusions and
65
+ imports. If dynamic inheritance or inclusion is used, `None` will be
66
+ yielded.
67
+
68
+ >>> from jinja2 import Environment, meta
69
+ >>> env = Environment()
70
+ >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
71
+ >>> list(meta.find_referenced_templates(ast))
72
+ ['layout.html', None]
73
+
74
+ This function is useful for dependency tracking. For example if you want
75
+ to rebuild parts of the website after a layout template has changed.
76
+ """
77
+ template_name: t.Any
78
+
79
+ for node in ast.find_all(_ref_types):
80
+ template: nodes.Expr = node.template # type: ignore
81
+
82
+ if not isinstance(template, nodes.Const):
83
+ # a tuple with some non consts in there
84
+ if isinstance(template, (nodes.Tuple, nodes.List)):
85
+ for template_name in template.items:
86
+ # something const, only yield the strings and ignore
87
+ # non-string consts that really just make no sense
88
+ if isinstance(template_name, nodes.Const):
89
+ if isinstance(template_name.value, str):
90
+ yield template_name.value
91
+ # something dynamic in there
92
+ else:
93
+ yield None
94
+ # something dynamic we don't know about here
95
+ else:
96
+ yield None
97
+ continue
98
+ # constant is a basestring, direct template name
99
+ if isinstance(template.value, str):
100
+ yield template.value
101
+ # a tuple or list (latter *should* not happen) made of consts,
102
+ # yield the consts that are strings. We could warn here for
103
+ # non string values
104
+ elif isinstance(node, nodes.Include) and isinstance(
105
+ template.value, (tuple, list)
106
+ ):
107
+ for template_name in template.value:
108
+ if isinstance(template_name, str):
109
+ yield template_name
110
+ # something else we don't care about, we could warn here
111
+ else:
112
+ yield None
@@ -0,0 +1,130 @@
1
+ import typing as t
2
+ from ast import literal_eval
3
+ from ast import parse
4
+ from itertools import chain
5
+ from itertools import islice
6
+ from types import GeneratorType
7
+
8
+ from . import nodes
9
+ from .compiler import CodeGenerator
10
+ from .compiler import Frame
11
+ from .compiler import has_safe_repr
12
+ from .environment import Environment
13
+ from .environment import Template
14
+
15
+
16
+ def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
17
+ """Return a native Python type from the list of compiled nodes. If
18
+ the result is a single node, its value is returned. Otherwise, the
19
+ nodes are concatenated as strings. If the result can be parsed with
20
+ :func:`ast.literal_eval`, the parsed value is returned. Otherwise,
21
+ the string is returned.
22
+
23
+ :param values: Iterable of outputs to concatenate.
24
+ """
25
+ head = list(islice(values, 2))
26
+
27
+ if not head:
28
+ return None
29
+
30
+ if len(head) == 1:
31
+ raw = head[0]
32
+ if not isinstance(raw, str):
33
+ return raw
34
+ else:
35
+ if isinstance(values, GeneratorType):
36
+ values = chain(head, values)
37
+ raw = "".join([str(v) for v in values])
38
+
39
+ try:
40
+ return literal_eval(
41
+ # In Python 3.10+ ast.literal_eval removes leading spaces/tabs
42
+ # from the given string. For backwards compatibility we need to
43
+ # parse the string ourselves without removing leading spaces/tabs.
44
+ parse(raw, mode="eval")
45
+ )
46
+ except (ValueError, SyntaxError, MemoryError):
47
+ return raw
48
+
49
+
50
+ class NativeCodeGenerator(CodeGenerator):
51
+ """A code generator which renders Python types by not adding
52
+ ``str()`` around output nodes.
53
+ """
54
+
55
+ @staticmethod
56
+ def _default_finalize(value: t.Any) -> t.Any:
57
+ return value
58
+
59
+ def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
60
+ return repr("".join([str(v) for v in group]))
61
+
62
+ def _output_child_to_const(
63
+ self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
64
+ ) -> t.Any:
65
+ const = node.as_const(frame.eval_ctx)
66
+
67
+ if not has_safe_repr(const):
68
+ raise nodes.Impossible()
69
+
70
+ if isinstance(node, nodes.TemplateData):
71
+ return const
72
+
73
+ return finalize.const(const) # type: ignore
74
+
75
+ def _output_child_pre(
76
+ self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
77
+ ) -> None:
78
+ if finalize.src is not None:
79
+ self.write(finalize.src)
80
+
81
+ def _output_child_post(
82
+ self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
83
+ ) -> None:
84
+ if finalize.src is not None:
85
+ self.write(")")
86
+
87
+
88
+ class NativeEnvironment(Environment):
89
+ """An environment that renders templates to native Python types."""
90
+
91
+ code_generator_class = NativeCodeGenerator
92
+ concat = staticmethod(native_concat) # type: ignore
93
+
94
+
95
+ class NativeTemplate(Template):
96
+ environment_class = NativeEnvironment
97
+
98
+ def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
99
+ """Render the template to produce a native Python type. If the
100
+ result is a single node, its value is returned. Otherwise, the
101
+ nodes are concatenated as strings. If the result can be parsed
102
+ with :func:`ast.literal_eval`, the parsed value is returned.
103
+ Otherwise, the string is returned.
104
+ """
105
+ ctx = self.new_context(dict(*args, **kwargs))
106
+
107
+ try:
108
+ return self.environment_class.concat( # type: ignore
109
+ self.root_render_func(ctx)
110
+ )
111
+ except Exception:
112
+ return self.environment.handle_exception()
113
+
114
+ async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
115
+ if not self.environment.is_async:
116
+ raise RuntimeError(
117
+ "The environment was not created with async mode enabled."
118
+ )
119
+
120
+ ctx = self.new_context(dict(*args, **kwargs))
121
+
122
+ try:
123
+ return self.environment_class.concat( # type: ignore
124
+ [n async for n in self.root_render_func(ctx)] # type: ignore
125
+ )
126
+ except Exception:
127
+ return self.environment.handle_exception()
128
+
129
+
130
+ NativeEnvironment.template_class = NativeTemplate