queryframe 0.1.0__tar.gz

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 (82) hide show
  1. queryframe-0.1.0/.github/workflows/ci.yml +48 -0
  2. queryframe-0.1.0/.github/workflows/release.yml +31 -0
  3. queryframe-0.1.0/.gitignore +40 -0
  4. queryframe-0.1.0/CONTRIBUTING.md +30 -0
  5. queryframe-0.1.0/LICENSE +21 -0
  6. queryframe-0.1.0/PKG-INFO +360 -0
  7. queryframe-0.1.0/README.md +298 -0
  8. queryframe-0.1.0/chart_shoe_me_a_pie_chart_.html +14 -0
  9. queryframe-0.1.0/demo.py +107 -0
  10. queryframe-0.1.0/examples/advanced_viz.py +44 -0
  11. queryframe-0.1.0/examples/custom_provider.py +40 -0
  12. queryframe-0.1.0/examples/local_ollama.py +33 -0
  13. queryframe-0.1.0/examples/quickstart.py +28 -0
  14. queryframe-0.1.0/pyproject.toml +99 -0
  15. queryframe-0.1.0/src/queryframe/__init__.py +15 -0
  16. queryframe-0.1.0/src/queryframe/cache/__init__.py +0 -0
  17. queryframe-0.1.0/src/queryframe/cache/disk.py +135 -0
  18. queryframe-0.1.0/src/queryframe/cache/hasher.py +33 -0
  19. queryframe-0.1.0/src/queryframe/cache/memory.py +104 -0
  20. queryframe-0.1.0/src/queryframe/core/__init__.py +0 -0
  21. queryframe-0.1.0/src/queryframe/core/accessor.py +65 -0
  22. queryframe-0.1.0/src/queryframe/core/config.py +96 -0
  23. queryframe-0.1.0/src/queryframe/core/engine.py +280 -0
  24. queryframe-0.1.0/src/queryframe/core/result.py +95 -0
  25. queryframe-0.1.0/src/queryframe/core/schema.py +99 -0
  26. queryframe-0.1.0/src/queryframe/llm/__init__.py +0 -0
  27. queryframe-0.1.0/src/queryframe/llm/anthropic.py +63 -0
  28. queryframe-0.1.0/src/queryframe/llm/base.py +39 -0
  29. queryframe-0.1.0/src/queryframe/llm/gemini.py +74 -0
  30. queryframe-0.1.0/src/queryframe/llm/lmstudio.py +76 -0
  31. queryframe-0.1.0/src/queryframe/llm/ollama.py +135 -0
  32. queryframe-0.1.0/src/queryframe/llm/openai.py +67 -0
  33. queryframe-0.1.0/src/queryframe/llm/prompt/__init__.py +0 -0
  34. queryframe-0.1.0/src/queryframe/llm/prompt/builder.py +134 -0
  35. queryframe-0.1.0/src/queryframe/llm/prompt/compressor.py +47 -0
  36. queryframe-0.1.0/src/queryframe/llm/prompt/templates.py +92 -0
  37. queryframe-0.1.0/src/queryframe/llm/registry.py +113 -0
  38. queryframe-0.1.0/src/queryframe/memory/__init__.py +0 -0
  39. queryframe-0.1.0/src/queryframe/memory/context.py +38 -0
  40. queryframe-0.1.0/src/queryframe/memory/conversation.py +70 -0
  41. queryframe-0.1.0/src/queryframe/py.typed +0 -0
  42. queryframe-0.1.0/src/queryframe/sandbox/__init__.py +0 -0
  43. queryframe-0.1.0/src/queryframe/sandbox/executor.py +136 -0
  44. queryframe-0.1.0/src/queryframe/sandbox/restricted.py +108 -0
  45. queryframe-0.1.0/src/queryframe/sandbox/timeout.py +47 -0
  46. queryframe-0.1.0/src/queryframe/sandbox/validator.py +127 -0
  47. queryframe-0.1.0/src/queryframe/utils/__init__.py +0 -0
  48. queryframe-0.1.0/src/queryframe/utils/dataframe.py +33 -0
  49. queryframe-0.1.0/src/queryframe/utils/errors.py +83 -0
  50. queryframe-0.1.0/src/queryframe/utils/logger.py +18 -0
  51. queryframe-0.1.0/src/queryframe/viz/__init__.py +0 -0
  52. queryframe-0.1.0/src/queryframe/viz/altair_renderer.py +215 -0
  53. queryframe-0.1.0/src/queryframe/viz/base.py +34 -0
  54. queryframe-0.1.0/src/queryframe/viz/chart_types.py +84 -0
  55. queryframe-0.1.0/src/queryframe/viz/matplotlib_renderer.py +217 -0
  56. queryframe-0.1.0/src/queryframe/viz/plotly_renderer.py +236 -0
  57. queryframe-0.1.0/src/queryframe/viz/selector.py +128 -0
  58. queryframe-0.1.0/src/queryframe/viz/style.py +94 -0
  59. queryframe-0.1.0/src/queryframe/viz/theme.py +126 -0
  60. queryframe-0.1.0/tests/__init__.py +0 -0
  61. queryframe-0.1.0/tests/conftest.py +78 -0
  62. queryframe-0.1.0/tests/e2e/__init__.py +0 -0
  63. queryframe-0.1.0/tests/e2e/test_live.py +356 -0
  64. queryframe-0.1.0/tests/integration/__init__.py +0 -0
  65. queryframe-0.1.0/tests/integration/test_engine.py +86 -0
  66. queryframe-0.1.0/tests/integration/test_sandbox.py +87 -0
  67. queryframe-0.1.0/tests/unit/__init__.py +0 -0
  68. queryframe-0.1.0/tests/unit/test_accessor.py +135 -0
  69. queryframe-0.1.0/tests/unit/test_cache.py +118 -0
  70. queryframe-0.1.0/tests/unit/test_compressor.py +171 -0
  71. queryframe-0.1.0/tests/unit/test_config.py +73 -0
  72. queryframe-0.1.0/tests/unit/test_conversation.py +57 -0
  73. queryframe-0.1.0/tests/unit/test_dataframe_utils.py +112 -0
  74. queryframe-0.1.0/tests/unit/test_disk_cache.py +135 -0
  75. queryframe-0.1.0/tests/unit/test_matplotlib_renderer.py +430 -0
  76. queryframe-0.1.0/tests/unit/test_plotly_renderer.py +354 -0
  77. queryframe-0.1.0/tests/unit/test_prompt_builder.py +86 -0
  78. queryframe-0.1.0/tests/unit/test_result.py +260 -0
  79. queryframe-0.1.0/tests/unit/test_schema.py +85 -0
  80. queryframe-0.1.0/tests/unit/test_selector.py +225 -0
  81. queryframe-0.1.0/tests/unit/test_style.py +306 -0
  82. queryframe-0.1.0/tests/unit/test_validator.py +235 -0
@@ -0,0 +1,48 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install -e ".[dev,plotly,matplotlib]"
28
+
29
+ - name: Lint with ruff
30
+ run: ruff check src/ tests/
31
+
32
+ - name: Type check with mypy
33
+ run: mypy src/queryframe/ --ignore-missing-imports
34
+ continue-on-error: true
35
+
36
+ - name: Run tests
37
+ run: pytest --cov=queryframe --cov-report=term-missing --cov-fail-under=80
38
+
39
+ lint:
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ - uses: actions/setup-python@v5
44
+ with:
45
+ python-version: "3.12"
46
+ - run: pip install ruff
47
+ - run: ruff check src/ tests/
48
+ - run: ruff format --check src/ tests/
@@ -0,0 +1,31 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment: release
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+
24
+ - name: Install build tools
25
+ run: pip install build
26
+
27
+ - name: Build package
28
+ run: python -m build
29
+
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,40 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ *.egg
6
+ dist/
7
+ build/
8
+ .eggs/
9
+ *.so
10
+ .Python
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # IDE
18
+ .idea/
19
+ .vscode/
20
+ *.swp
21
+ *.swo
22
+
23
+ # Testing
24
+ .pytest_cache/
25
+ .coverage
26
+ htmlcov/
27
+ .mypy_cache/
28
+ .ruff_cache/
29
+
30
+ # OS
31
+ .DS_Store
32
+ Thumbs.db
33
+
34
+ # Secrets
35
+ .env
36
+ .env.local
37
+ *.key
38
+
39
+ # QueryFrame cache
40
+ ~/.queryframe/
@@ -0,0 +1,30 @@
1
+ # Contributing to QueryFrame
2
+
3
+ Thanks for your interest in contributing!
4
+
5
+ ## Getting Started
6
+
7
+ 1. Fork the repository
8
+ 2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/queryframe.git`
9
+ 3. Install in dev mode: `pip install -e ".[dev,all]"`
10
+ 4. Create a branch: `git checkout -b feature/your-feature`
11
+
12
+ ## Development Workflow
13
+
14
+ 1. Write tests first (TDD)
15
+ 2. Run tests: `pytest`
16
+ 3. Lint: `ruff check src/ tests/`
17
+ 4. Format: `ruff format src/ tests/`
18
+ 5. Type check: `mypy src/queryframe/`
19
+
20
+ ## Pull Request Guidelines
21
+
22
+ - Keep PRs focused — one feature or fix per PR
23
+ - Include tests for new functionality
24
+ - Maintain 80%+ test coverage
25
+ - Follow existing code style
26
+ - Update documentation if needed
27
+
28
+ ## Security
29
+
30
+ If you discover a security vulnerability, please email security@movargroup.com instead of opening an issue.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Movar Group
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,360 @@
1
+ Metadata-Version: 2.4
2
+ Name: queryframe
3
+ Version: 0.1.0
4
+ Summary: Super fast natural language data visualization and analysis for pandas DataFrames
5
+ Project-URL: Homepage, https://github.com/movar-group/queryframe
6
+ Project-URL: Documentation, https://github.com/movar-group/queryframe#readme
7
+ Project-URL: Repository, https://github.com/movar-group/queryframe
8
+ Project-URL: Issues, https://github.com/movar-group/queryframe/issues
9
+ Author: Movar Group
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,dataframe,llm,natural-language,pandas,visualization
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Scientific/Engineering :: Visualization
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: jinja2>=3.1
25
+ Requires-Dist: pandas>=1.5
26
+ Requires-Dist: xxhash>=3.0
27
+ Provides-Extra: all
28
+ Requires-Dist: altair>=5.0; extra == 'all'
29
+ Requires-Dist: anthropic>=0.30; extra == 'all'
30
+ Requires-Dist: google-genai>=1.0; extra == 'all'
31
+ Requires-Dist: httpx>=0.27; extra == 'all'
32
+ Requires-Dist: matplotlib>=3.5; extra == 'all'
33
+ Requires-Dist: openai>=1.0; extra == 'all'
34
+ Requires-Dist: plotly>=5.0; extra == 'all'
35
+ Requires-Dist: seaborn>=0.12; extra == 'all'
36
+ Provides-Extra: altair
37
+ Requires-Dist: altair>=5.0; extra == 'altair'
38
+ Provides-Extra: anthropic
39
+ Requires-Dist: anthropic>=0.30; extra == 'anthropic'
40
+ Provides-Extra: dev
41
+ Requires-Dist: mypy>=1.8; extra == 'dev'
42
+ Requires-Dist: pandas-stubs>=2.0; extra == 'dev'
43
+ Requires-Dist: pre-commit>=3.0; extra == 'dev'
44
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
45
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
46
+ Requires-Dist: pytest>=7.0; extra == 'dev'
47
+ Requires-Dist: ruff>=0.4; extra == 'dev'
48
+ Provides-Extra: gemini
49
+ Requires-Dist: google-genai>=1.0; extra == 'gemini'
50
+ Provides-Extra: lmstudio
51
+ Requires-Dist: openai>=1.0; extra == 'lmstudio'
52
+ Provides-Extra: matplotlib
53
+ Requires-Dist: matplotlib>=3.5; extra == 'matplotlib'
54
+ Requires-Dist: seaborn>=0.12; extra == 'matplotlib'
55
+ Provides-Extra: ollama
56
+ Requires-Dist: httpx>=0.27; extra == 'ollama'
57
+ Provides-Extra: openai
58
+ Requires-Dist: openai>=1.0; extra == 'openai'
59
+ Provides-Extra: plotly
60
+ Requires-Dist: plotly>=5.0; extra == 'plotly'
61
+ Description-Content-Type: text/markdown
62
+
63
+ # QueryFrame
64
+
65
+ **Super fast natural language data visualization and analysis for pandas DataFrames.**
66
+
67
+ QueryFrame lets you ask questions about your data in plain English and get instant answers, charts, and insights. It's the faster, safer, more flexible alternative to PandasAI.
68
+
69
+ ```python
70
+ import pandas as pd
71
+ import queryframe as qf
72
+
73
+ df = pd.read_csv("sales.csv")
74
+
75
+ # Ask anything
76
+ result = qf.ask(df, "what is the average revenue by region?")
77
+ print(result.data)
78
+
79
+ # Visualize instantly
80
+ result = df.qf.ask("show me a bar chart of sales by product")
81
+ result.show()
82
+
83
+ # Chain queries
84
+ result = qf.ask(df, "top 5 customers by spend").save("top_customers.html")
85
+ ```
86
+
87
+ ## Why QueryFrame over PandasAI?
88
+
89
+ | Feature | QueryFrame | PandasAI |
90
+ |---------|-----------|----------|
91
+ | Speed | Smart caching, minimal prompts | Sends full schema every query |
92
+ | Safety | AST-validated sandbox | Raw `exec()` |
93
+ | Local models | First-class Ollama + LM Studio | Limited support |
94
+ | Visualizations | Auto-selects Plotly/Matplotlib/Altair | Mostly matplotlib |
95
+ | Follow-ups | Conversation memory | Stateless |
96
+ | Token usage | Compressed schemas, 3 sample rows | Verbose, 5 sample rows |
97
+
98
+ ## Installation
99
+
100
+ ```bash
101
+ # Core (no LLM provider included)
102
+ pip install queryframe
103
+
104
+ # With your preferred provider
105
+ pip install queryframe[openai] # OpenAI
106
+ pip install queryframe[anthropic] # Claude
107
+ pip install queryframe[gemini] # Google Gemini
108
+ pip install queryframe[ollama] # Ollama (local)
109
+ pip install queryframe[lmstudio] # LM Studio (local)
110
+
111
+ # With visualization libraries
112
+ pip install queryframe[plotly] # Interactive charts (recommended)
113
+ pip install queryframe[matplotlib] # Static charts (includes seaborn)
114
+ pip install queryframe[altair] # Declarative charts
115
+
116
+ # Everything
117
+ pip install queryframe[all]
118
+ ```
119
+
120
+ ## Quick Start
121
+
122
+ ### 1. Set your API key (cloud providers)
123
+
124
+ ```bash
125
+ export OPENAI_API_KEY="sk-..."
126
+ # or
127
+ export ANTHROPIC_API_KEY="sk-ant-..."
128
+ # or
129
+ export GOOGLE_API_KEY="..."
130
+ ```
131
+
132
+ ### 2. Use it
133
+
134
+ ```python
135
+ import pandas as pd
136
+ import queryframe as qf
137
+
138
+ df = pd.DataFrame({
139
+ "product": ["Laptop", "Phone", "Tablet"],
140
+ "price": [999, 699, 449],
141
+ "units_sold": [150, 500, 200],
142
+ })
143
+
144
+ # Natural language queries
145
+ result = qf.ask(df, "which product generated the most revenue?")
146
+ print(result.data) # The answer
147
+ print(result.code) # Generated pandas code
148
+ print(result.explanation) # Human-readable explanation
149
+
150
+ # Visualizations
151
+ result = qf.ask(df, "bar chart of revenue by product")
152
+ result.show() # Display interactive chart
153
+ result.save("chart.html") # Export
154
+ ```
155
+
156
+ ## Local Models (Ollama / LM Studio)
157
+
158
+ QueryFrame has first-class support for local models — no API keys, no data leaves your machine.
159
+
160
+ ### Ollama
161
+
162
+ ```bash
163
+ # Start Ollama
164
+ ollama serve
165
+
166
+ # Pull a model
167
+ ollama pull llama3.1
168
+ ```
169
+
170
+ ```python
171
+ import queryframe as qf
172
+
173
+ qf.configure(provider="ollama", model="llama3.1")
174
+ result = qf.ask(df, "average sales by region")
175
+ ```
176
+
177
+ ### LM Studio
178
+
179
+ ```python
180
+ import queryframe as qf
181
+
182
+ # LM Studio runs at localhost:1234 by default
183
+ qf.configure(provider="lmstudio")
184
+ result = qf.ask(df, "show me the top 10 products")
185
+ ```
186
+
187
+ ## All Providers
188
+
189
+ ```python
190
+ from queryframe import QueryEngine, QueryFrameConfig
191
+
192
+ # OpenAI
193
+ engine = QueryEngine(config=QueryFrameConfig(
194
+ provider="openai", model="gpt-4o-mini"
195
+ ))
196
+
197
+ # Anthropic Claude
198
+ engine = QueryEngine(config=QueryFrameConfig(
199
+ provider="anthropic", model="claude-sonnet-4-20250514"
200
+ ))
201
+
202
+ # Google Gemini
203
+ engine = QueryEngine(config=QueryFrameConfig(
204
+ provider="gemini", model="gemini-2.0-flash"
205
+ ))
206
+
207
+ # Ollama
208
+ engine = QueryEngine(config=QueryFrameConfig(
209
+ provider="ollama", model="llama3.1"
210
+ ))
211
+
212
+ # LM Studio
213
+ engine = QueryEngine(config=QueryFrameConfig(
214
+ provider="lmstudio"
215
+ ))
216
+
217
+ # Auto-detect (checks env vars, then local servers)
218
+ engine = QueryEngine() # Just works
219
+ ```
220
+
221
+ ## Visualization
222
+
223
+ QueryFrame auto-selects the best visualization library:
224
+ - **Notebooks** → Plotly (interactive)
225
+ - **Scripts** → Matplotlib (static)
226
+ - **Override** → `qf.ask(df, "...", viz="altair")`
227
+
228
+ Supported chart types: `bar`, `line`, `scatter`, `pie`, `histogram`, `heatmap`, `box`, `area`, `violin`, `treemap`, `funnel`
229
+
230
+ ```python
231
+ # Auto-select
232
+ result = qf.ask(df, "show trend of sales over time") # → line chart
233
+
234
+ # Force specific library
235
+ result = qf.ask(df, "bar chart of revenue", viz="matplotlib")
236
+
237
+ # Re-render with different library
238
+ result = qf.ask(df, "sales by region").viz("altair")
239
+
240
+ # Save to file
241
+ result.save("chart.png") # static image
242
+ result.save("chart.html") # interactive HTML
243
+ ```
244
+
245
+ ## Chainable API
246
+
247
+ ```python
248
+ # Chain operations
249
+ result = (
250
+ qf.ask(df, "total revenue by product")
251
+ .save("revenue.html")
252
+ )
253
+
254
+ # Follow-up queries (uses conversation memory)
255
+ r1 = qf.ask(df, "show me sales by region")
256
+ r2 = r1.ask("now filter to just Q4") # "it" = sales by region
257
+ r3 = r2.ask("which region had the highest?") # context preserved
258
+ ```
259
+
260
+ ## Caching
261
+
262
+ Repeated queries are instant (< 5ms vs 2-5s for LLM calls):
263
+
264
+ ```python
265
+ # First call: hits the LLM (~2s)
266
+ result = qf.ask(df, "average sales")
267
+ print(result.cached) # False
268
+
269
+ # Same query: from cache (~1ms)
270
+ result = qf.ask(df, "average sales")
271
+ print(result.cached) # True
272
+ ```
273
+
274
+ ## Configuration
275
+
276
+ ```python
277
+ import queryframe as qf
278
+
279
+ # Via configure()
280
+ qf.configure(
281
+ provider="openai",
282
+ model="gpt-4o",
283
+ cache_enabled=True,
284
+ viz_mode="plotly", # auto, plotly, matplotlib, altair
285
+ timeout=30, # seconds
286
+ max_retries=2,
287
+ )
288
+
289
+ # Via environment variables
290
+ # QF_PROVIDER=openai
291
+ # QF_MODEL=gpt-4o
292
+ # QF_VIZ=plotly
293
+ # QF_TIMEOUT=30
294
+ # QF_VERBOSE=true
295
+ # QF_LOG_LEVEL=DEBUG
296
+ ```
297
+
298
+ ## Security
299
+
300
+ QueryFrame takes security seriously:
301
+
302
+ 1. **AST Validation** — All LLM-generated code is parsed and validated before execution. Dangerous operations (`import os`, `exec`, `eval`, `open`, etc.) are rejected.
303
+ 2. **Restricted Builtins** — Only safe builtins are available in the sandbox (no `__import__`, `getattr`, `globals`, etc.)
304
+ 3. **Execution Timeout** — Code that runs too long is killed (default: 30s)
305
+ 4. **DataFrame Isolation** — The LLM code operates on a copy of your DataFrame, never the original
306
+ 5. **No Network Access** — Sandboxed code cannot make network requests
307
+
308
+ ## Architecture
309
+
310
+ ```
311
+ df.ask("show me sales by region")
312
+
313
+
314
+ ┌─────────────┐ ┌──────────┐ ┌────────────┐
315
+ │ Cache Check │────▸│ Schema │────▸│ Prompt │
316
+ │ (< 1ms) │ │ Extract │ │ Builder │
317
+ └─────────────┘ └──────────┘ └────────────┘
318
+
319
+
320
+ ┌─────────────┐ ┌──────────┐ ┌────────────┐
321
+ │ Viz Render │◂────│ Sandbox │◂────│ LLM │
322
+ │ (auto-pick) │ │ Execute │ │ Provider │
323
+ └─────────────┘ └──────────┘ └────────────┘
324
+
325
+ ┌────────────┐
326
+ │ QueryResult│
327
+ │ .data │
328
+ │ .chart │
329
+ │ .code │
330
+ └────────────┘
331
+ ```
332
+
333
+ ## Development
334
+
335
+ ```bash
336
+ # Clone
337
+ git clone https://github.com/movar-group/queryframe.git
338
+ cd queryframe
339
+
340
+ # Install in dev mode
341
+ pip install -e ".[dev,all]"
342
+
343
+ # Run tests
344
+ pytest
345
+
346
+ # Lint
347
+ ruff check src/ tests/
348
+ ruff format src/ tests/
349
+
350
+ # Type check
351
+ mypy src/queryframe/
352
+ ```
353
+
354
+ ## License
355
+
356
+ MIT License. See [LICENSE](LICENSE) for details.
357
+
358
+ ## Contributing
359
+
360
+ Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting a PR.