stackraise 0.1.0__py3-none-any.whl

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 (52) hide show
  1. stackraise/__init__.py +6 -0
  2. stackraise/ai/__init__.py +2 -0
  3. stackraise/ai/rpa.py +380 -0
  4. stackraise/ai/toolset.py +227 -0
  5. stackraise/app.py +23 -0
  6. stackraise/auth/__init__.py +2 -0
  7. stackraise/auth/model.py +24 -0
  8. stackraise/auth/service.py +240 -0
  9. stackraise/ctrl/__init__.py +4 -0
  10. stackraise/ctrl/change_stream.py +40 -0
  11. stackraise/ctrl/crud_controller.py +63 -0
  12. stackraise/ctrl/file_storage.py +68 -0
  13. stackraise/db/__init__.py +11 -0
  14. stackraise/db/adapter.py +60 -0
  15. stackraise/db/collection.py +292 -0
  16. stackraise/db/cursor.py +229 -0
  17. stackraise/db/document.py +282 -0
  18. stackraise/db/exceptions.py +9 -0
  19. stackraise/db/id.py +79 -0
  20. stackraise/db/index.py +84 -0
  21. stackraise/db/persistence.py +238 -0
  22. stackraise/db/pipeline.py +245 -0
  23. stackraise/db/protocols.py +141 -0
  24. stackraise/di.py +36 -0
  25. stackraise/event.py +150 -0
  26. stackraise/inflection.py +28 -0
  27. stackraise/io/__init__.py +3 -0
  28. stackraise/io/imap_client.py +400 -0
  29. stackraise/io/smtp_client.py +102 -0
  30. stackraise/logging.py +22 -0
  31. stackraise/model/__init__.py +11 -0
  32. stackraise/model/core.py +16 -0
  33. stackraise/model/dto.py +12 -0
  34. stackraise/model/email_message.py +88 -0
  35. stackraise/model/file.py +154 -0
  36. stackraise/model/name_email.py +45 -0
  37. stackraise/model/query_filters.py +231 -0
  38. stackraise/model/time_range.py +285 -0
  39. stackraise/model/validation.py +8 -0
  40. stackraise/templating/__init__.py +4 -0
  41. stackraise/templating/exceptions.py +23 -0
  42. stackraise/templating/image/__init__.py +2 -0
  43. stackraise/templating/image/model.py +51 -0
  44. stackraise/templating/image/processor.py +154 -0
  45. stackraise/templating/parser.py +156 -0
  46. stackraise/templating/pptx/__init__.py +3 -0
  47. stackraise/templating/pptx/pptx_engine.py +204 -0
  48. stackraise/templating/pptx/slide_renderer.py +181 -0
  49. stackraise/templating/tracer.py +57 -0
  50. stackraise-0.1.0.dist-info/METADATA +37 -0
  51. stackraise-0.1.0.dist-info/RECORD +52 -0
  52. stackraise-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,204 @@
1
+ import re
2
+ from typing import Optional, Union
3
+ from pathlib import Path
4
+ from io import BytesIO
5
+
6
+ from pptx import Presentation
7
+ from pptx.slide import Slide
8
+ from pydantic import BaseModel
9
+
10
+ from ..parser import create_jinja_env, evaluate_condition, evaluate_conditional_block, extract_slide_conditional_block, extract_slide_loop
11
+ from .slide_renderer import render_slide
12
+ from ..tracer import Tracer
13
+ from ..exceptions import TemplateNotFoundError, RenderError
14
+ from copy import deepcopy
15
+
16
+ """
17
+ TODO: Implementar soporte para variables de tipo 'of' en los bucles de Jinja2.
18
+ Este operador permite iterar sobre listas y para cada elemento generar
19
+ un slide duplicado con el contexto actualizado.
20
+ """
21
+
22
+
23
+ class PptxTemplateEngine:
24
+
25
+ @staticmethod
26
+ def _resolve_path(ctx: dict, path: str):
27
+ """
28
+ Resuelve rutas 'a.b.c' en dicts/objetos simples.
29
+ """
30
+ cur = ctx
31
+ for part in [p.strip() for p in path.split('.') if p.strip()]:
32
+ if isinstance(cur, dict):
33
+ cur = cur.get(part, None)
34
+ else:
35
+ cur = getattr(cur, part, None)
36
+ if cur is None:
37
+ return None
38
+ return cur
39
+
40
+ @staticmethod
41
+ def render_from_template(
42
+ context: BaseModel,
43
+ template_path: str,
44
+ output_path: Optional[str] = None
45
+ ) -> Union[str, BytesIO]:
46
+ tracer = Tracer()
47
+ tracer.start()
48
+
49
+ template_file = Path(template_path)
50
+ if not template_file.exists():
51
+ raise TemplateNotFoundError(f"Plantilla no encontrada en ruta: {template_path}")
52
+
53
+ try:
54
+ prs = Presentation(template_path)
55
+ # serializar el model pero RESTAURAR objetos ImageData top-level
56
+ context_dict = context.model_dump(by_alias=False, mode="python")
57
+ try:
58
+ # import tardío para evitar ciclos
59
+ from stackraise.templating.image.model import ImageData
60
+ except Exception:
61
+ ImageData = None
62
+
63
+ if ImageData is not None:
64
+ # Restaurar campos top-level que en el BaseModel son instancias ImageData
65
+ for name, raw_value in context.__dict__.items():
66
+ if isinstance(raw_value, ImageData):
67
+ context_dict[name] = raw_value
68
+
69
+ jinja_env = create_jinja_env()
70
+
71
+ slides = list(prs.slides)
72
+ slides_to_remove = []
73
+
74
+ for idx, slide in enumerate(slides):
75
+ loop_found = False
76
+ slide_level_conditional_found = False
77
+
78
+ # Buscar en todas las formas de texto de la slide
79
+ slide_text = ""
80
+ for shape in slide.shapes:
81
+ if shape.has_text_frame:
82
+ slide_text += shape.text_frame.text + " "
83
+
84
+ # Primero verificar si hay un bucle A NIVEL DE SLIDE
85
+ loop_info = extract_slide_loop(slide_text)
86
+ if loop_info:
87
+ loop_var, loop_list, loop_type = loop_info
88
+ loop_found = True
89
+
90
+ #items = context_dict.get(loop_list, []) or []
91
+ items = PptxTemplateEngine._resolve_path(context_dict, loop_list) or []
92
+
93
+ # if loop_type == "of":
94
+ # tracer.log_slide_duplication(idx, len(items))
95
+ # for i, item in enumerate(items):
96
+ # new_slide = PptxTemplateEngine._duplicate_slide(prs, slide)
97
+ # local_context = context_dict.copy()
98
+ # local_context[loop_var] = item
99
+ # render_slide(new_slide, local_context, jinja_env, tracer)
100
+
101
+ # slides_to_remove.append(slide)
102
+ # else:
103
+ # render_slide(slide, context_dict, jinja_env, tracer)
104
+ if loop_type == "of":
105
+ tracer.log_duplication(idx, len(items), "slide")
106
+ for i, item in enumerate(items):
107
+ new_slide = PptxTemplateEngine._duplicate_slide(prs, slide)
108
+ local_context = context_dict.copy()
109
+ local_context[loop_var] = item
110
+ # Evita que Jinja procese el 'for of' en la duplicada
111
+ PptxTemplateEngine._unwrap_slide_for_blocks(new_slide)
112
+ render_slide(new_slide, local_context, jinja_env, tracer)
113
+
114
+ slides_to_remove.append(slide)
115
+ else:
116
+ render_slide(slide, context_dict, jinja_env, tracer)
117
+
118
+ # Si no hay bucle, verificar si hay condicional A NIVEL DE SLIDE
119
+ elif not loop_found:
120
+ conditional_info = extract_slide_conditional_block(slide_text)
121
+ if conditional_info:
122
+ slide_level_conditional_found = True
123
+ try:
124
+ if conditional_info['has_block_structure']:
125
+ # Bloque condicional completo con if/elif/else
126
+ condition_result = evaluate_conditional_block(slide_text, context_dict, jinja_env)
127
+ tracer.log_conditional(idx, f"bloque condicional a nivel slide", condition_result)
128
+ else:
129
+ # Condicional simple
130
+ condition = conditional_info['condition']
131
+ condition_result = evaluate_condition(condition, context_dict, jinja_env)
132
+ tracer.log_conditional(idx, condition, condition_result)
133
+
134
+ if condition_result:
135
+ # Condición verdadera: renderizar la slide
136
+ render_slide(slide, context_dict, jinja_env, tracer)
137
+ else:
138
+ # Condición falsa: marcar slide para eliminación
139
+ slides_to_remove.append(slide)
140
+
141
+ except Exception as e:
142
+ tracer.log_error(f"Error evaluando condición en slide {idx}: {e}")
143
+ # En caso de error, mantener la slide pero sin renderizar
144
+ render_slide(slide, context_dict, jinja_env, tracer)
145
+
146
+ # Si no hay ni bucle ni condicional A NIVEL DE SLIDE, renderizar normalmente
147
+ # (los condicionales a nivel de texto se manejan en render_slide)
148
+ if not loop_found and not slide_level_conditional_found:
149
+ render_slide(slide, context_dict, jinja_env, tracer)
150
+
151
+ # Remover slides marcadas para eliminación
152
+ for slide in slides_to_remove:
153
+ PptxTemplateEngine._remove_slide(prs, slide)
154
+
155
+ if output_path:
156
+ prs.save(output_path)
157
+ tracer.end()
158
+ return str(output_path)
159
+ else:
160
+ pptx_io = BytesIO()
161
+ prs.save(pptx_io)
162
+ pptx_io.seek(0)
163
+ tracer.end()
164
+ return pptx_io
165
+
166
+ except Exception as e:
167
+ tracer.log_error(f"Error en render_from_template: {e}")
168
+ raise RenderError(f"Fallo al renderizar plantilla PPTX: {e}")
169
+
170
+ @staticmethod
171
+ def _duplicate_slide(prs: Presentation, slide: Slide) -> Slide:
172
+ new_slide = prs.slides.add_slide(slide.slide_layout)
173
+ for shape in slide.shapes:
174
+ el = shape.element
175
+ new_el = deepcopy(el)
176
+ new_slide.shapes._spTree.insert_element_before(new_el, 'p:extLst')
177
+ return new_slide
178
+
179
+ @staticmethod
180
+ def _remove_slide(prs: Presentation, slide: Slide):
181
+ slide_id = slide.slide_id
182
+ slides = prs.slides._sldIdLst
183
+ for sld in slides:
184
+ if sld.get("id") == str(slide_id):
185
+ slides.remove(sld)
186
+ break
187
+
188
+ @staticmethod
189
+ def _unwrap_slide_for_blocks(slide: Slide) -> None:
190
+ """Quita etiquetas {% for ... %}/{% endfor %} in-place sin tocar formato."""
191
+ start_tag = re.compile(r"""{%\s*for\s+\w+\s+(?:of|in)\s+.+?\s*%}""")
192
+ end_tag = re.compile(r"""{%\s*endfor\s*%}""")
193
+ for shape in slide.shapes:
194
+ if not getattr(shape, "has_text_frame", False):
195
+ continue
196
+ tf = shape.text_frame
197
+ for p in tf.paragraphs:
198
+ for r in p.runs:
199
+ if not r.text:
200
+ continue
201
+ # Elimina sólo las etiquetas, deja el cuerpo intacto
202
+ txt = start_tag.sub("", r.text)
203
+ txt = end_tag.sub("", txt)
204
+ r.text = txt
@@ -0,0 +1,181 @@
1
+ from pptx.slide import Slide
2
+ from pptx.shapes.base import BaseShape
3
+ from pptx.dml.color import RGBColor
4
+ from stackraise.templating.image.processor import ImageProcessor
5
+ from ..parser import has_image_variables, render_text
6
+ from ..tracer import Tracer
7
+ import re
8
+
9
+ def remove_for_of_blocks(text: str) -> str:
10
+ # Desenrolla bucles a nivel de slide: conserva solo el cuerpo
11
+ #pattern = r"""{%\s*for\s+\w+\s+(?:of|in)\s+\w+\s*%}(.*?){%\s*endfor\s*%}"""
12
+ pattern = r"""{%\s*for\s+\w+\s+(?:of|in)\s+.+?\s*%}(.*?){%\s*endfor\s*%}"""
13
+ return re.sub(pattern, r"\1", text, flags=re.DOTALL)
14
+
15
+ def has_conditional_blocks(text: str) -> bool:
16
+ """
17
+ Verifica si el texto contiene bloques condicionales.
18
+ """
19
+ pattern = r"""{%\s*if\s+.+?\s*%}"""
20
+ return bool(re.search(pattern, text))
21
+
22
+ def has_text_level_loops(text: str) -> bool:
23
+ """
24
+ Verifica si el texto contiene bucles que deben procesarse como texto
25
+ (no como duplicación de slides).
26
+ """
27
+ # Buscar bucles que estén mezclados con otro contenido
28
+ lines = text.strip().split('\n')
29
+ has_loop = False
30
+ has_other_content = False
31
+
32
+ for line in lines:
33
+ line = line.strip()
34
+ if not line:
35
+ continue
36
+ #if re.search(r'{%\s*for\s+\w+\s+(of|in)\s+\w+\s*%}', line):
37
+ if re.search(r'{%\s*for\s+\w+\s+(?:of|in)\s+.+?\s*%}', line):
38
+ has_loop = True
39
+ #elif not re.search(r'{%\s*(for|endfor|if|elif|else|endif)\s*.*%}', line):
40
+ elif not re.search(r'{%\s*(for|endfor|if|elif|else|endif)\s*.*%}', line):
41
+ has_other_content = True
42
+
43
+ return has_loop and has_other_content
44
+
45
+
46
+ def _copy_run_font(dst_run, src_run):
47
+ try:
48
+ dst_font = dst_run.font
49
+ src_font = src_run.font
50
+ dst_font.name = src_font.name
51
+ dst_font.size = src_font.size
52
+ dst_font.bold = src_font.bold
53
+ dst_font.italic = src_font.italic
54
+ dst_font.underline = src_font.underline
55
+ # Color: intenta RGB si existe
56
+ try:
57
+ rgb = src_font.color.rgb
58
+ if rgb is not None:
59
+ dst_font.color.rgb = RGBColor(rgb[0], rgb[1], rgb[2]) if isinstance(rgb, tuple) else rgb
60
+ except Exception:
61
+ try:
62
+ dst_font.color.theme_color = src_font.color.theme_color
63
+ except Exception:
64
+ pass
65
+ except Exception:
66
+ pass
67
+
68
+ def _snapshot_base_style(text_frame):
69
+ base_para = text_frame.paragraphs[0] if text_frame.paragraphs else None
70
+ base_run = base_para.runs[0] if (base_para and base_para.runs) else None
71
+ return base_para, base_run
72
+
73
+ def write_text_preserving_formatting(text_frame, text: str):
74
+ # Captura estilo base antes de limpiar
75
+ base_para_before, base_run_before = _snapshot_base_style(text_frame)
76
+
77
+ # Limpia el contenido (deja un párrafo vacío)
78
+ try:
79
+ text_frame.clear()
80
+ except Exception:
81
+ pass
82
+
83
+ lines = (text or "").splitlines() or [""]
84
+ for idx, line in enumerate(lines):
85
+ para = text_frame.paragraphs[0] if idx == 0 else text_frame.add_paragraph()
86
+
87
+ # Restituye nivel/alineación del párrafo base si existe
88
+ try:
89
+ if base_para_before is not None:
90
+ para.level = base_para_before.level
91
+ para.alignment = base_para_before.alignment
92
+ except Exception:
93
+ pass
94
+
95
+ run = para.runs[0] if para.runs else para.add_run()
96
+ # Copia estilo del run base si existe
97
+ if base_run_before is not None:
98
+ _copy_run_font(run, base_run_before)
99
+ run.text = line
100
+
101
+
102
+ def has_only_simple_variables(text: str) -> bool:
103
+ # Hay variables {{ ... }} pero no bloques {% ... %}
104
+ return ("{{" in text) and ("{%" not in text)
105
+
106
+ def render_runs_preserving_formatting(text_frame, context: dict, jinja_env):
107
+ # Sustituye {{ expr }} dentro de cada run conservando su formato
108
+ var_re = re.compile(r"\{\{\s*(.*?)\s*\}\}")
109
+ for paragraph in text_frame.paragraphs:
110
+ for run in paragraph.runs:
111
+ original = run.text or ""
112
+ if not original:
113
+ continue
114
+ def repl(m: re.Match) -> str:
115
+ expr = m.group(1)
116
+ try:
117
+ tpl = jinja_env.from_string("{{ " + expr + " }}")
118
+ return str(tpl.render(context))
119
+ except Exception:
120
+ # Si falla, deja el placeholder intacto
121
+ return m.group(0)
122
+ replaced = var_re.sub(repl, original)
123
+ run.text = replaced
124
+
125
+ def render_slide(slide: Slide, context: dict, jinja_env, tracer: Tracer):
126
+ # Primero procesar imágenes (variables ImageData)
127
+ ImageProcessor.process_slide_images(slide, context, tracer)
128
+
129
+ # Luego procesar texto normal
130
+ for shape in slide.shapes:
131
+ if not getattr(shape, "has_text_frame", False):
132
+ continue
133
+
134
+ text_frame = shape.text_frame
135
+ all_text = ""
136
+ for paragraph in text_frame.paragraphs:
137
+ all_text += "".join(run.text for run in paragraph.runs) + "\n"
138
+
139
+ try:
140
+ # Verificar el tipo de contenido
141
+ has_images = has_image_variables(all_text, context)
142
+ has_conditionals = has_conditional_blocks(all_text)
143
+ has_loops = has_text_level_loops(all_text)
144
+
145
+ if has_conditionals or has_loops or has_images:
146
+ # Si hay condicionales, bucles o imágenes, renderizar TODO con Jinja2
147
+ # Contenido complejo: render completo (posible pérdida de formatos finos)
148
+ rendered_text = render_text(all_text, context, jinja_env)
149
+ write_text_preserving_formatting(text_frame, rendered_text or "")
150
+ tracer.log_variable(all_text.strip(), (rendered_text or "").strip())
151
+ else:
152
+ # Sólo variables simples: sustituir por run conservando formato
153
+ render_runs_preserving_formatting(text_frame, context, jinja_env)
154
+ tracer.log_variable(all_text.strip(), (text_frame.text or "").strip())
155
+
156
+ # Limpiar el text_frame y agregar el texto renderizado
157
+ # while len(text_frame.paragraphs) > 1:
158
+ # text_frame._element.remove(text_frame._element[-1])
159
+
160
+ # paragraph = text_frame.paragraphs[0]
161
+ # while len(paragraph.runs) > 1:
162
+ # paragraph._element.remove(paragraph.runs[-1]._r)
163
+
164
+ # paragraph.runs[0].text = rendered_text
165
+
166
+ # Escritura segura: evita IndexError en runs vacíos
167
+
168
+ # try:
169
+ # text_frame.clear() # deja un párrafo vacío
170
+ # except Exception as e:
171
+ # print(f"No se pudo limpiar el text_frame: {e}")
172
+ # pass
173
+ # text_frame.text = rendered_text or ""
174
+
175
+ write_text_preserving_formatting(text_frame, rendered_text or "")
176
+ tracer.log_variable(all_text.strip(), (rendered_text or "").strip())
177
+
178
+ #tracer.log_variable(all_text.strip(), rendered_text.strip())
179
+
180
+ except Exception as e:
181
+ tracer.log_error(f"Error al renderizar texto '{all_text.strip()}': {e}")
@@ -0,0 +1,57 @@
1
+ import logging
2
+ from time import perf_counter
3
+
4
+ logger = logging.getLogger("pptx_template_engine")
5
+ logger.setLevel(logging.DEBUG)
6
+ handler = logging.StreamHandler()
7
+ formatter = logging.Formatter("[PPTX-TEMPLATE] %(levelname)s - %(message)s")
8
+ handler.setFormatter(formatter)
9
+ logger.addHandler(handler)
10
+
11
+
12
+ class Tracer:
13
+ """Tracer genérico para motores de plantillas"""
14
+
15
+ def __init__(self, logger_name: str = "template_engine"):
16
+ self.start_time = None
17
+ self.logger = logging.getLogger(logger_name)
18
+ if not self.logger.handlers:
19
+ self._setup_logger()
20
+
21
+ def _setup_logger(self):
22
+ """Configura el logger si no está configurado"""
23
+ self.logger.setLevel(logging.DEBUG)
24
+ handler = logging.StreamHandler()
25
+ formatter = logging.Formatter(f"[{self.logger.name.upper()}] %(levelname)s - %(message)s")
26
+ handler.setFormatter(formatter)
27
+ self.logger.addHandler(handler)
28
+
29
+ def start(self, engine_type: str = "plantilla"):
30
+ """Inicia el trazado"""
31
+ self.start_time = perf_counter()
32
+ self.logger.info(f"Inicio del render de {engine_type}")
33
+
34
+ def end(self):
35
+ """Finaliza el trazado"""
36
+ duration = perf_counter() - self.start_time
37
+ self.logger.info(f"Render finalizado en {duration:.2f} segundos")
38
+
39
+ def log_variable(self, var: str, value: any):
40
+ """Registra el renderizado de una variable"""
41
+ self.logger.debug(f"Variable '{{ {var} }}' → {value}")
42
+
43
+ def log_duplication(self, item_idx: int, count: int, item_type: str = "elemento"):
44
+ """Registra la duplicación de elementos"""
45
+ self.logger.info(f"{item_type.capitalize()} {item_idx} duplicado {count} veces por bucle")
46
+
47
+ def log_conditional(self, item_idx: int, condition: str, result: bool, item_type: str = "elemento"):
48
+ """Registra la evaluación de condicionales"""
49
+ self.logger.info(f"Condicional '{condition}' evaluada como {result} en {item_type} {item_idx}")
50
+
51
+ def log_error(self, message: str):
52
+ """Registra un error"""
53
+ self.logger.error(message)
54
+
55
+ def log_info(self, message: str):
56
+ """Registra información general"""
57
+ self.logger.info(message)
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: stackraise
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: J.David Luque
6
+ Author-email: jdluque@leitat.org
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: aioimaplib (>=2.0.1,<3.0.0)
13
+ Requires-Dist: aiosmtplib (>=4.0.1,<5.0.0)
14
+ Requires-Dist: docxtpl (>=0.20.0,<0.21.0)
15
+ Requires-Dist: email-validator (>=2.2.0,<3.0.0)
16
+ Requires-Dist: fastapi[standard] (>=0.115.6,<0.116.0)
17
+ Requires-Dist: inflector (>=3.1.1,<4.0.0)
18
+ Requires-Dist: motor (>=3.6.0,<4.0.0)
19
+ Requires-Dist: openai (>=2.15.0,<3.0.0)
20
+ Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
21
+ Requires-Dist: pydantic (>=2.11,<3.0)
22
+ Requires-Dist: pydantic-settings-yaml (>=0.2.0,<0.3.0)
23
+ Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
24
+ Requires-Dist: pymongo (>=4.13.2,<5.0.0)
25
+ Requires-Dist: pypdf2 (>=3.0.1,<4.0.0)
26
+ Requires-Dist: python-pptx (==1.0.2)
27
+ Requires-Dist: python-slugify (>=8.0.4,<9.0.0)
28
+ Requires-Dist: xlsxtpl (>=0.3.1,<0.4.0)
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Nothingad backend
32
+
33
+ ```sh
34
+ just bulkload # carga data/workbook.xlsx en la base de datos
35
+
36
+ just launch # ejecuta el backend
37
+ ```
@@ -0,0 +1,52 @@
1
+ stackraise/__init__.py,sha256=T6LUkt1rdxY_zoFU2CgBaEG0bDBLRy6Nf9uv6d6dnKQ,119
2
+ stackraise/ai/__init__.py,sha256=SXz7CzvgQHiAbnsFvyKBsTmH1QpuXrlZg3o2kDQHLbw,41
3
+ stackraise/ai/rpa.py,sha256=sSZTpW_OlsOR2312cI-ttuh9GsggZoyXxJOZyZY2CT4,12371
4
+ stackraise/ai/toolset.py,sha256=iOIpuin_hpR6WGjj1DqHme2Lb-0HEba-lW-cIOwkCR8,6997
5
+ stackraise/app.py,sha256=toyeotSU_XL2U0Se8mCtp8tM61omehs34jFoBzV52L8,631
6
+ stackraise/auth/__init__.py,sha256=MzHWdMbPciCE17HfZx_Irr-lax8swtL85W_IubFqIGY,64
7
+ stackraise/auth/model.py,sha256=biudWx1IrwvwCgW5R6Hl0VTUPuQpk10my03qYj2SKAM,679
8
+ stackraise/auth/service.py,sha256=A20CwObI3VCXHBuX5eGnzHOiNgD1UhYeOL65aSPGCWw,7783
9
+ stackraise/ctrl/__init__.py,sha256=IDIG0MsDc8nxguMFttkqE_cQtH-FdMull4xd52ypVfo,99
10
+ stackraise/ctrl/change_stream.py,sha256=6QFp_Z1WL168p3IzFeu4mNJ2USaThPp-Qk7JRrxYb68,1180
11
+ stackraise/ctrl/crud_controller.py,sha256=YjN8RiimGfL_sISzjgwl0hjPNqOOmj4AtNPdTaL1-fA,2123
12
+ stackraise/ctrl/file_storage.py,sha256=MZnD45j4DCXVEpS1ricgjLw2-3dQ0NEloCOEUSVODBA,2262
13
+ stackraise/db/__init__.py,sha256=whU36yRPuwhQ45r9H493NCtJZsmkoU1AaDfK1401Bf0,272
14
+ stackraise/db/adapter.py,sha256=wQTbC1m7KKhKl31p847gnqYtVkfMBJR1g2Rj1UF6D78,1570
15
+ stackraise/db/collection.py,sha256=pGd2VrZ06El5e-HnT_Y2rQpbGz6JTD5KJGh7iL65BZ4,10752
16
+ stackraise/db/cursor.py,sha256=CBg2FutsGplst5g3F0UKYloaQC4i_7vq0h9qPicc_ZE,6504
17
+ stackraise/db/document.py,sha256=k1_7qNW9rhbpHK6VQcKmJKhuf9mEHDQB-umpP1P4OH8,9444
18
+ stackraise/db/exceptions.py,sha256=ICrWbW3IsZIOYS0OTI9RROmkmOeAlqKlOLQyNvF3Q-0,308
19
+ stackraise/db/id.py,sha256=cJl45y4HmtpAAVXasnmVtCIRB-gkuHexAAVNzHHF2Es,2426
20
+ stackraise/db/index.py,sha256=UeSa_eAkx2m_eVN2fOnjectXnnqp1tg_36tynDrabBM,2735
21
+ stackraise/db/persistence.py,sha256=qmB1371cluPBdtbJnYbMzFHCODxFDfob07PUe_trEjY,6776
22
+ stackraise/db/pipeline.py,sha256=qthH3b8rvE71r909RABpUpvJVDqyKhKQdrAvDpVX3HE,7223
23
+ stackraise/db/protocols.py,sha256=HCC3ic2MI47wpHJnYbFxwpXaJK2aPgMa9Q0sKGKqAoE,4545
24
+ stackraise/di.py,sha256=eg-nG0GtQZsFs6xyQ3ZKx5sY3EG6TQPe5N8rq8MMixc,921
25
+ stackraise/event.py,sha256=e-B-FYvyZN7xcv9ELOBlfDTrNsEh8taTEKoMDN4FZDQ,4184
26
+ stackraise/inflection.py,sha256=aHPU69PKs9qmv2L4HdY04Uqq6kqPBYa1k6gCKd0OZIo,542
27
+ stackraise/io/__init__.py,sha256=NZn3USTIx9jUAJ0J-KR63wm3HCqErGAl-oUwjgezoW4,84
28
+ stackraise/io/imap_client.py,sha256=zJDdgFqvCsq2IPon9Fs3qbKJSiqfw9D7-s54Wc5T-Po,15096
29
+ stackraise/io/smtp_client.py,sha256=9BCHWPb2hvXhRINPbkX43GbIgLDSZicfWJaV0SXenH0,3243
30
+ stackraise/logging.py,sha256=aAgognVCEjCMuhaApBKBGyc13TKnsRgYu9STIJUzLbY,470
31
+ stackraise/model/__init__.py,sha256=iyO_Q8Pnl-6UQCTV4IrZFx1N1TyeT8TX5TsbINM6OCs,261
32
+ stackraise/model/core.py,sha256=v9TpsUtQucvv9P1jB6NBcb2d4_S0mooujAt0rcbsHcM,415
33
+ stackraise/model/dto.py,sha256=SGm6JZ8JwW27iX5v4Ou7D-b73WpZY9Cb49PxdqK6yNs,277
34
+ stackraise/model/email_message.py,sha256=qZgj4HgLARrwNd34vW27lL5A9PBN1nl6MSZcwEbFnN4,2003
35
+ stackraise/model/file.py,sha256=Fb1yYpQbUgfL0HkJ9YEw0BpqJcpxDIMNKyD0TCYZh-w,5019
36
+ stackraise/model/name_email.py,sha256=_X9QCTqPOA977wvzZzObIHyIZNib63zpE4IKzYfeq5U,1408
37
+ stackraise/model/query_filters.py,sha256=1m25avpPIFPWJ6e_MVS0ilFtTEKUSp4JqGeq1RlpQDM,7391
38
+ stackraise/model/time_range.py,sha256=2uy7ik6u7W-AQT2ZPqIvVPRcMe4uryC94gSnz4awJWQ,8703
39
+ stackraise/model/validation.py,sha256=jdqVutwc7M7qjdtv8VKkYpCpyqSHWe2xSx0sNp1WT3o,255
40
+ stackraise/templating/__init__.py,sha256=S6Na4oFzHdxWM1YKzdPbUHMqeEHNXFjdCr5iHp_AsdI,84
41
+ stackraise/templating/exceptions.py,sha256=MlR9bw6SNu5Incd9CrHgz7iBPCWaAh6sL-efaxE_L0Q,412
42
+ stackraise/templating/image/__init__.py,sha256=jk_WNdl-lg8bGUpFxRhU4nZ7tmmvsxne1KFoX42H9rM,45
43
+ stackraise/templating/image/model.py,sha256=Bk0sGN7mdrgw7bg8YFf8HOhjJbivdmeo1NG-wvC9FZ0,1959
44
+ stackraise/templating/image/processor.py,sha256=Lz6xFXxXvcsdpD7EeaeWbUYD-Z9HZHQRBxWQ5jVpMQ0,6697
45
+ stackraise/templating/parser.py,sha256=5jmBFtn4hkrm8SfLc-fFTqIE2mJmNCqnYf3r7BGSg0Y,5743
46
+ stackraise/templating/pptx/__init__.py,sha256=nfC9DiY6QWGZ83grgInEYbTgZDKBu2DqOlWjSTk_bd8,83
47
+ stackraise/templating/pptx/pptx_engine.py,sha256=Ykb25fkJSvYrAzADPVSgQ_R6v558JzJETkpbo8wTz68,8916
48
+ stackraise/templating/pptx/slide_renderer.py,sha256=5jGisi-_Fy8iTGgszGRObTbqzaOKD_5LdzLw8uEil0E,7149
49
+ stackraise/templating/tracer.py,sha256=tJdBHWDyRVnAgtES26t26elgx8DdNVZKhAnP-ts0FhY,2214
50
+ stackraise-0.1.0.dist-info/METADATA,sha256=zXm3qLkPezeeA8W18ZG3CbTVE6tlkffo53divcuejwI,1243
51
+ stackraise-0.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
52
+ stackraise-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any