├── .gitignore ├── LICENSE ├── README.md ├── integration_tests ├── app.py ├── requirements.txt └── templates │ └── index.html ├── poetry.lock ├── pychartjs ├── __init__.py ├── charts.py ├── colors.py ├── datasets.py ├── enums.py ├── options.py ├── plugins.py └── scales.py ├── pyproject.toml └── tests ├── test_charts.py ├── test_colors.py ├── test_datasets.py ├── test_options.py ├── test_plugin.py └── test_scales.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ali Tavallaie 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pychartjs 2 | 3 | Pychartjs is a Python library for generating dynamic charts using Chart.js and integrating them into web applications. The library is built on top of the [Trunco](https://github.com/tavallaie/trunco) framework, which provides a component-based architecture to make it easy to create and manage various chart types. 4 | 5 | ## Features 6 | 7 | - Support for all major chart types: Line, Bar, Pie, Doughnut, Radar, PolarArea, Bubble, and Scatter. 8 | - Integration with various web frameworks including Robyn, Django, FastAPI, and other Python web frameworks. 9 | - Dynamic chart rendering with context variables. 10 | - Flexible and customizable chart options. 11 | 12 | ## Project Structure 13 | 14 | ``` 15 | pychartjs/ 16 | ├── pychartjs/ 17 | │ ├── __init__.py 18 | │ ├── charts.py 19 | │ ├── datasets.py 20 | │ ├── enums.py 21 | │ ├── options.py 22 | │ ├── scales.py 23 | │ ├── plugins.py 24 | │ ├── colors.py 25 | ├── tests/ 26 | │ ├── test_charts.py 27 | │ ├── test_datasets.py 28 | │ ├── test_options.py 29 | │ ├── test_scales.py 30 | │ ├── test_plugins.py 31 | │ ├── test_colors.py 32 | ├── integration_tests/ 33 | │ ├── app.py 34 | │ └── templates/ 35 | │ └── index.html 36 | ├── LICENSE 37 | ├── README.md 38 | ├── poetry.lock 39 | └── pyproject.toml 40 | ``` 41 | 42 | - **`pychartjs/`**: Contains the core library code, including charts, datasets, enums, options, and scales. 43 | - **`tests/`**: Unit tests for the various components of the library. 44 | - **`integration_tests/`**: A sample application that demonstrates how to integrate `pychartjs` with a web framework like Robyn. 45 | 46 | ## Installation 47 | 48 | 1. **Install with Poetry:** 49 | 50 | If you are using Poetry for dependency management, you can add `pychartjs` to your project with: 51 | 52 | ```bash 53 | poetry add pychartjs 54 | ``` 55 | 56 | 2. **Alternatively, install with pip:** 57 | 58 | If you're using pip, you can install `pychartjs` with: 59 | 60 | ```bash 61 | pip install pychartjs 62 | ``` 63 | 64 | 65 | ## Table of Contents 66 | 1. [Installation](#installation) 67 | 2. [Basic Usage](#basic-usage) 68 | 3. [Integration with Web Frameworks](#integration-with-web-frameworks) 69 | - [Robyn](#robyn) 70 | - [Django](#django) 71 | - [FastAPI](#fastapi) 72 | - [Flask](#flask) 73 | 4. [Customizing Your Charts](#customizing-your-charts) 74 | 5. [Working with Options](#working-with-options) 75 | 6. [Using Plugins](#using-plugins) 76 | 7. [Color Schemes and Palettes](#color-schemes-and-palettes) 77 | 8. [Rendering Options](#rendering-options) 78 | 9. [Advanced Features](#advanced-features) 79 | 10. [Contributing](#contributing) 80 | 81 | ## Installation 82 | 83 | ### Poetry 84 | 85 | To install `pychartjs` using Poetry, run the following command: 86 | 87 | ```bash 88 | poetry add pychartjs 89 | ``` 90 | 91 | ### Pip 92 | 93 | Alternatively, if you're using pip, you can install `pychartjs` with: 94 | 95 | ```bash 96 | pip install pychartjs 97 | ``` 98 | 99 | ## Basic Usage 100 | 101 | Here’s a simple example of how to create a line chart using `pychartjs`: 102 | 103 | ```python 104 | from pychartjs.charts import Chart 105 | from pychartjs.datasets import Dataset 106 | from pychartjs.enums import ChartType 107 | 108 | dataset = Dataset( 109 | label="Sales Data", 110 | data=[10, 20, 30, 40, 50], 111 | backgroundColor="rgba(75, 192, 192, 0.2)", 112 | borderColor="rgba(75, 192, 192, 1)", 113 | borderWidth=1, 114 | ) 115 | 116 | chart = Chart( 117 | chart_type=ChartType.LINE, 118 | datasets=[dataset], 119 | labels=["January", "February", "March", "April", "May"] 120 | ) 121 | 122 | # Render the chart as HTML 123 | chart_html = chart.render() 124 | print(chart_html) 125 | ``` 126 | 127 | ## Integration with Web Frameworks 128 | 129 | ### Robyn 130 | 131 | Here’s how you can use `pychartjs` with the Robyn framework: 132 | 133 | ```python 134 | from robyn import Robyn 135 | from pychartjs.charts import Chart 136 | from pychartjs.datasets import Dataset 137 | from pychartjs.enums import ChartType 138 | from robyn.templating import JinjaTemplate 139 | 140 | app = Robyn(__file__) 141 | 142 | template = JinjaTemplate("templates") 143 | 144 | @app.get("/") 145 | async def get_chart(request): 146 | dataset = Dataset( 147 | label="Sales Data", 148 | data=[10, 20, 30, 40, 50], 149 | backgroundColor="rgba(75, 192, 192, 0.2)", 150 | borderColor="rgba(75, 192, 192, 1)", 151 | borderWidth=1, 152 | ) 153 | chart = Chart( 154 | chart_type=ChartType.LINE, 155 | datasets=[dataset], 156 | labels=["January", "February", "March", "April", "May"] 157 | ) 158 | charts_html = chart.render() 159 | return template.render_template("index.html", charts_html=charts_html) 160 | 161 | if __name__ == "__main__": 162 | app.start(port=8080) 163 | ``` 164 | 165 | ### Django 166 | 167 | To integrate `pychartjs` with Django, follow these steps: 168 | 169 | 1. **Install Django if you haven't already:** 170 | 171 | ```bash 172 | pip install django 173 | ``` 174 | 175 | 2. **Create a Django view and template:** 176 | 177 | ```python 178 | # views.py 179 | from django.shortcuts import render 180 | from pychartjs.charts import Chart 181 | from pychartjs.datasets import Dataset 182 | from pychartjs.enums import ChartType 183 | 184 | def chart_view(request): 185 | dataset = Dataset( 186 | label="Sales Data", 187 | data=[10, 20, 30, 40, 50], 188 | backgroundColor="rgba(75, 192, 192, 0.2)", 189 | borderColor="rgba(75, 192, 192, 1)", 190 | borderWidth=1, 191 | ) 192 | chart = Chart( 193 | chart_type=ChartType.LINE, 194 | datasets=[dataset], 195 | labels=["January", "February", "March", "April", "May"] 196 | ) 197 | charts_html = chart.render() 198 | return render(request, "chart.html", {"charts_html": charts_html}) 199 | ``` 200 | 201 | 3. **Create the template:** 202 | 203 | ```html 204 | 205 | 206 | 207 | 208 | 209 | 210 | Chart Page 211 | 212 | 213 | 214 |

My Sales Chart

215 |
216 | {{ charts_html|safe }} 217 |
218 | 219 | 220 | ``` 221 | 222 | 4. **Add the view to your `urls.py`:** 223 | 224 | ```python 225 | # urls.py 226 | from django.urls import path 227 | from .views import chart_view 228 | 229 | urlpatterns = [ 230 | path('chart/', chart_view, name='chart-view'), 231 | ] 232 | ``` 233 | 234 | ### FastAPI 235 | 236 | Here’s how to set up `pychartjs` with FastAPI: 237 | 238 | ```python 239 | from fastapi import FastAPI 240 | from fastapi.responses import HTMLResponse 241 | from pychartjs.charts import Chart 242 | from pychartjs.datasets import Dataset 243 | from pychartjs.enums import ChartType 244 | 245 | app = FastAPI() 246 | 247 | @app.get("/", response_class=HTMLResponse) 248 | async def get_chart(): 249 | dataset = Dataset( 250 | label="Sales Data", 251 | data=[10, 20, 30, 40, 50], 252 | backgroundColor="rgba(75, 192, 192, 0.2)", 253 | borderColor="rgba(75, 192, 192, 1)", 254 | borderWidth=1, 255 | ) 256 | chart = Chart( 257 | chart_type=ChartType.LINE, 258 | datasets=[dataset], 259 | labels=["January", "February", "March", "April", "May"] 260 | ) 261 | charts_html = chart.render() 262 | return f""" 263 | 264 | 265 | 266 | 267 | 268 | Chart Page 269 | 270 | 271 | 272 |

My Sales Chart

273 |
274 | {charts_html} 275 |
276 | 277 | 278 | """ 279 | 280 | if __name__ == "__main__": 281 | import uvicorn 282 | uvicorn.run(app, host="0.0.0.0", port=8000) 283 | ``` 284 | 285 | ### Flask 286 | 287 | To integrate `pychartjs` with Flask: 288 | 289 | 1. **Install Flask if you haven't already:** 290 | 291 | ```bash 292 | pip install flask 293 | ``` 294 | 295 | 2. **Create a Flask app:** 296 | 297 | ```python 298 | from flask import Flask, render_template 299 | 300 | 301 | from pychartjs.charts import Chart 302 | from pychartjs.datasets import Dataset 303 | from pychartjs.enums import ChartType 304 | 305 | app = Flask(__name__) 306 | 307 | @app.route("/") 308 | def get_chart(): 309 | dataset = Dataset( 310 | label="Sales Data", 311 | data=[10, 20, 30, 40, 50], 312 | backgroundColor="rgba(75, 192, 192, 0.2)", 313 | borderColor="rgba(75, 192, 192, 1)", 314 | borderWidth=1, 315 | ) 316 | chart = Chart( 317 | chart_type=ChartType.LINE, 318 | datasets=[dataset], 319 | labels=["January", "February", "March", "April", "May"] 320 | ) 321 | charts_html = chart.render() 322 | return render_template("chart.html", charts_html=charts_html) 323 | 324 | if __name__ == "__main__": 325 | app.run(debug=True) 326 | ``` 327 | 328 | 3. **Create the template:** 329 | 330 | ```html 331 | 332 | 333 | 334 | 335 | 336 | 337 | Chart Page 338 | 339 | 340 | 341 |

My Sales Chart

342 |
343 | {{ charts_html|safe }} 344 |
345 | 346 | 347 | ``` 348 | 349 | ## Customizing Your Charts 350 | 351 | `pychartjs` allows you to customize various aspects of your charts, including colors, labels, tooltips, and more. For example, you can modify the background color and border color of your datasets or adjust the chart's options to change its appearance. 352 | 353 | ### Example: Customizing a Bar Chart 354 | 355 | ```python 356 | dataset = Dataset( 357 | label="Revenue", 358 | data=[15, 25, 35, 45, 55], 359 | backgroundColor="rgba(153, 102, 255, 0.2)", 360 | borderColor="rgba(153, 102, 255, 1)", 361 | borderWidth=1, 362 | ) 363 | 364 | chart = Chart( 365 | chart_type=ChartType.BAR, 366 | datasets=[dataset], 367 | labels=["January", "February", "March", "April", "May"] 368 | ) 369 | ``` 370 | 371 | ## Working with Options 372 | 373 | `pychartjs` allows you to define and customize chart options to control various aspects of your chart’s appearance and behavior. 374 | 375 | ### Example: Setting Chart Options 376 | 377 | ```python 378 | from pychartjs.options import ChartOptions, Legend, Title 379 | 380 | options = ChartOptions( 381 | responsive=True, 382 | maintainAspectRatio=False, 383 | legend=Legend(display=True, position="top"), 384 | title=Title(display=True, text="Monthly Sales Data") 385 | ) 386 | 387 | chart = Chart( 388 | chart_type=ChartType.BAR, 389 | datasets=[dataset], 390 | labels=["January", "February", "March", "April", "May"], 391 | options=options 392 | ) 393 | ``` 394 | 395 | ## Using Plugins 396 | 397 | `pychartjs` supports adding and configuring Chart.js plugins to extend the functionality of your charts. 398 | 399 | ### Example: Using a Plugin 400 | 401 | ```python 402 | from pychartjs.plugins import Plugin 403 | 404 | plugin = Plugin(name="custom-plugin", options={"option1": "value1"}) 405 | 406 | chart = Chart( 407 | chart_type=ChartType.BAR, 408 | datasets=[dataset], 409 | labels=["January", "February", "March", "April", "May"], 410 | plugins=[plugin] 411 | ) 412 | ``` 413 | 414 | ## Color Schemes and Palettes 415 | 416 | `pychartjs` provides support for color schemes and palettes to enhance the visual appeal of your charts. You can define custom colors or use pre-defined color schemes. 417 | 418 | ### Example: Applying a Color Palette 419 | 420 | ```python 421 | from pychartjs.colors import ColorPalette 422 | 423 | palette = ColorPalette( 424 | backgroundColors=[ 425 | "rgba(255, 99, 132, 0.2)", 426 | "rgba(54, 162, 235, 0.2)", 427 | "rgba(255, 206, 86, 0.2)", 428 | "rgba(75, 192, 192, 0.2)", 429 | "rgba(153, 102, 255, 0.2)", 430 | "rgba(255, 159, 64, 0.2)" 431 | ], 432 | borderColors=[ 433 | "rgba(255, 99, 132, 1)", 434 | "rgba(54, 162, 235, 1)", 435 | "rgba(255, 206, 86, 1)", 436 | "rgba(75, 192, 192, 1)", 437 | "rgba(153, 102, 255, 1)", 438 | "rgba(255, 159, 64, 1)" 439 | ] 440 | ) 441 | 442 | dataset = Dataset( 443 | label="Revenue", 444 | data=[15, 25, 35, 45, 55], 445 | backgroundColor=palette.backgroundColors, 446 | borderColor=palette.borderColors, 447 | borderWidth=1, 448 | ) 449 | 450 | chart = Chart( 451 | chart_type=ChartType.BAR, 452 | datasets=[dataset], 453 | labels=["January", "February", "March", "April", "May"] 454 | ) 455 | ``` 456 | 457 | ## Rendering Options 458 | 459 | `pychartjs` allows you to control how your charts are rendered in your web application, including dynamic rendering based on context variables. 460 | 461 | ### Example: Rendering with Context Variables 462 | 463 | ```python 464 | context = {"year": 2023, "data": [10, 20, 30, 40, 50]} 465 | 466 | chart = Chart( 467 | chart_type=ChartType.LINE, 468 | datasets=[Dataset(label=f"Sales Data {context['year']}", data=context['data'])], 469 | labels=["January", "February", "March", "April", "May"] 470 | ) 471 | 472 | chart_html = chart.render(context=context) 473 | ``` 474 | 475 | ## Advanced Features 476 | 477 | `pychartjs` also supports advanced features like mixed charts, custom scales, and event-driven interactions. You can extend the capabilities of your charts by leveraging these advanced features. 478 | 479 | ### Example: Creating a Mixed Chart 480 | 481 | ```python 482 | from pychartjs.enums import ChartType 483 | 484 | datasets = [ 485 | Dataset(type=ChartType.BAR, label="Bar Dataset", data=[10, 20, 30]), 486 | Dataset(type=ChartType.LINE, label="Line Dataset", data=[15, 25, 35]) 487 | ] 488 | 489 | chart = Chart( 490 | chart_type=ChartType.MIXED, 491 | datasets=datasets, 492 | labels=["January", "February", "March"] 493 | ) -------------------------------------------------------------------------------- /integration_tests/app.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | # Add the root directory of the project to the Python path 5 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) 6 | from dataclasses import dataclass, field 7 | from typing import List 8 | from robyn import Robyn 9 | from robyn.templating import JinjaTemplate 10 | from pychartjs.charts import Chart 11 | from pychartjs.datasets import Dataset 12 | from pychartjs.enums import ChartType 13 | from pychartjs.options import ChartOptions, Legend 14 | from trunco import Component 15 | 16 | app = Robyn(__file__) 17 | 18 | # Configure the template engine 19 | template = JinjaTemplate(os.path.join(os.path.dirname(__file__), "templates")) 20 | 21 | 22 | @dataclass 23 | class Page(Component): 24 | charts: List[Chart] = field(default_factory=list) 25 | 26 | def __post_init__(self): 27 | # Add all chart types to the page 28 | self.charts.extend( 29 | [ 30 | self.create_chart(ChartType.LINE, "Line"), 31 | self.create_chart(ChartType.BAR, "Bar"), 32 | self.create_chart(ChartType.PIE, "Pie"), 33 | self.create_chart(ChartType.DOUGHNUT, "Doughnut"), 34 | self.create_chart(ChartType.RADAR, "Radar"), 35 | self.create_chart(ChartType.POLAR_AREA, "PolarArea"), 36 | self.create_chart(ChartType.BUBBLE, "Bubble"), 37 | self.create_chart(ChartType.SCATTER, "Scatter"), 38 | ] 39 | ) 40 | 41 | def create_chart(self, chart_type: ChartType, name: str) -> Chart: 42 | dataset = Dataset( 43 | label=f"{name} Sales Data {{year}}", 44 | data=[10, 20, 30, 40, 50], 45 | backgroundColor="rgba(75, 192, 192, 0.2)", 46 | borderColor="rgba(75, 192, 192, 1)", 47 | borderWidth=1, 48 | ) 49 | 50 | return Chart( 51 | chart_type=chart_type, 52 | datasets=[dataset], 53 | labels=["January", "February", "March", "April", "May"], 54 | options=ChartOptions(responsive=True, legend=Legend(display=True)), 55 | ) 56 | 57 | def render(self, context=None): 58 | # Render all charts with the provided context 59 | rendered_charts = [chart.render(context) for chart in self.charts] 60 | 61 | # Combine all rendered chart HTML into a single string 62 | combined_html = "\n".join(rendered_charts) 63 | 64 | return combined_html 65 | 66 | 67 | @app.get("/") 68 | async def chart_page(request): 69 | # Create the page with all chart types 70 | page = Page() 71 | 72 | # Render the charts HTML with context 73 | context = {"year": "2023"} 74 | charts_html = page.render(context=context) 75 | 76 | # Debug output to verify HTML content 77 | print("Generated charts_html:\n", charts_html) 78 | 79 | # Directly return the rendered template with charts_html 80 | return template.render_template("index.html", charts_html=charts_html) 81 | 82 | 83 | # Run the app 84 | if __name__ == "__main__": 85 | app.start(host="0.0.0.0", port=8080) 86 | -------------------------------------------------------------------------------- /integration_tests/requirements.txt: -------------------------------------------------------------------------------- 1 | robyn 2 | -------------------------------------------------------------------------------- /integration_tests/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Chart Page 9 | 10 | 11 | 12 |

My Sales Chart

13 | 14 |
15 | {{ charts_html | safe }} 16 |
17 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "dill" 5 | version = "0.3.8" 6 | description = "serialize all of Python" 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, 11 | {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, 12 | ] 13 | 14 | [package.extras] 15 | graph = ["objgraph (>=1.7.2)"] 16 | profile = ["gprof2dot (>=2022.7.29)"] 17 | 18 | [[package]] 19 | name = "inquirerpy" 20 | version = "0.3.4" 21 | description = "Python port of Inquirer.js (A collection of common interactive command-line user interfaces)" 22 | optional = false 23 | python-versions = ">=3.7,<4.0" 24 | files = [ 25 | {file = "InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4"}, 26 | {file = "InquirerPy-0.3.4.tar.gz", hash = "sha256:89d2ada0111f337483cb41ae31073108b2ec1e618a49d7110b0d7ade89fc197e"}, 27 | ] 28 | 29 | [package.dependencies] 30 | pfzy = ">=0.3.1,<0.4.0" 31 | prompt-toolkit = ">=3.0.1,<4.0.0" 32 | 33 | [package.extras] 34 | docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] 35 | 36 | [[package]] 37 | name = "jinja2" 38 | version = "3.1.4" 39 | description = "A very fast and expressive template engine." 40 | optional = false 41 | python-versions = ">=3.7" 42 | files = [ 43 | {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, 44 | {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, 45 | ] 46 | 47 | [package.dependencies] 48 | MarkupSafe = ">=2.0" 49 | 50 | [package.extras] 51 | i18n = ["Babel (>=2.7)"] 52 | 53 | [[package]] 54 | name = "markupsafe" 55 | version = "2.1.5" 56 | description = "Safely add untrusted strings to HTML/XML markup." 57 | optional = false 58 | python-versions = ">=3.7" 59 | files = [ 60 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, 61 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, 62 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, 63 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, 64 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, 65 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, 66 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, 67 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, 68 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, 69 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, 70 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, 71 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, 72 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, 73 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, 74 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, 75 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, 76 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, 77 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, 78 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, 79 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, 80 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, 81 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, 82 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, 83 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, 84 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, 85 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, 86 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, 87 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, 88 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, 89 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, 90 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, 91 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, 92 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, 93 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, 94 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, 95 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, 96 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, 97 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, 98 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, 99 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, 100 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, 101 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, 102 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, 103 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, 104 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, 105 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, 106 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, 107 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, 108 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, 109 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, 110 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, 111 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, 112 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, 113 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, 114 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, 115 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, 116 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, 117 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, 118 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, 119 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, 120 | ] 121 | 122 | [[package]] 123 | name = "multiprocess" 124 | version = "0.70.14" 125 | description = "better multiprocessing and multithreading in python" 126 | optional = false 127 | python-versions = ">=3.7" 128 | files = [ 129 | {file = "multiprocess-0.70.14-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560a27540daef4ce8b24ed3cc2496a3c670df66c96d02461a4da67473685adf3"}, 130 | {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_i686.whl", hash = "sha256:bfbbfa36f400b81d1978c940616bc77776424e5e34cb0c94974b178d727cfcd5"}, 131 | {file = "multiprocess-0.70.14-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:89fed99553a04ec4f9067031f83a886d7fdec5952005551a896a4b6a59575bb9"}, 132 | {file = "multiprocess-0.70.14-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:40a5e3685462079e5fdee7c6789e3ef270595e1755199f0d50685e72523e1d2a"}, 133 | {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_i686.whl", hash = "sha256:44936b2978d3f2648727b3eaeab6d7fa0bedf072dc5207bf35a96d5ee7c004cf"}, 134 | {file = "multiprocess-0.70.14-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:e628503187b5d494bf29ffc52d3e1e57bb770ce7ce05d67c4bbdb3a0c7d3b05f"}, 135 | {file = "multiprocess-0.70.14-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0d5da0fc84aacb0e4bd69c41b31edbf71b39fe2fb32a54eaedcaea241050855c"}, 136 | {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_i686.whl", hash = "sha256:6a7b03a5b98e911a7785b9116805bd782815c5e2bd6c91c6a320f26fd3e7b7ad"}, 137 | {file = "multiprocess-0.70.14-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:cea5bdedd10aace3c660fedeac8b087136b4366d4ee49a30f1ebf7409bce00ae"}, 138 | {file = "multiprocess-0.70.14-py310-none-any.whl", hash = "sha256:7dc1f2f6a1d34894c8a9a013fbc807971e336e7cc3f3ff233e61b9dc679b3b5c"}, 139 | {file = "multiprocess-0.70.14-py37-none-any.whl", hash = "sha256:93a8208ca0926d05cdbb5b9250a604c401bed677579e96c14da3090beb798193"}, 140 | {file = "multiprocess-0.70.14-py38-none-any.whl", hash = "sha256:6725bc79666bbd29a73ca148a0fb5f4ea22eed4a8f22fce58296492a02d18a7b"}, 141 | {file = "multiprocess-0.70.14-py39-none-any.whl", hash = "sha256:63cee628b74a2c0631ef15da5534c8aedbc10c38910b9c8b18dcd327528d1ec7"}, 142 | {file = "multiprocess-0.70.14.tar.gz", hash = "sha256:3eddafc12f2260d27ae03fe6069b12570ab4764ab59a75e81624fac453fbf46a"}, 143 | ] 144 | 145 | [package.dependencies] 146 | dill = ">=0.3.6" 147 | 148 | [[package]] 149 | name = "nestd" 150 | version = "0.3.1" 151 | description = "A package to extract your nested functions!" 152 | optional = false 153 | python-versions = ">=3.7,<4.0" 154 | files = [ 155 | {file = "nestd-0.3.1-py3-none-any.whl", hash = "sha256:9a27d65e0676477acf50f1dcadb15755eb85076983e5abaffc94b0f4e26eef62"}, 156 | {file = "nestd-0.3.1.tar.gz", hash = "sha256:1b946d2bbc16df67ca169b8cb59794ec70b54d4a56a017e3d1c092d364d22a39"}, 157 | ] 158 | 159 | [[package]] 160 | name = "orjson" 161 | version = "3.9.15" 162 | description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" 163 | optional = false 164 | python-versions = ">=3.8" 165 | files = [ 166 | {file = "orjson-3.9.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a"}, 167 | {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04"}, 168 | {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4"}, 169 | {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75"}, 170 | {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab"}, 171 | {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58"}, 172 | {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99"}, 173 | {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe"}, 174 | {file = "orjson-3.9.15-cp310-none-win32.whl", hash = "sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7"}, 175 | {file = "orjson-3.9.15-cp310-none-win_amd64.whl", hash = "sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb"}, 176 | {file = "orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2"}, 177 | {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f"}, 178 | {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc"}, 179 | {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1"}, 180 | {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5"}, 181 | {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde"}, 182 | {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404"}, 183 | {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357"}, 184 | {file = "orjson-3.9.15-cp311-none-win32.whl", hash = "sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7"}, 185 | {file = "orjson-3.9.15-cp311-none-win_amd64.whl", hash = "sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8"}, 186 | {file = "orjson-3.9.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73"}, 187 | {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a"}, 188 | {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7"}, 189 | {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e"}, 190 | {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d"}, 191 | {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494"}, 192 | {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068"}, 193 | {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda"}, 194 | {file = "orjson-3.9.15-cp312-none-win_amd64.whl", hash = "sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2"}, 195 | {file = "orjson-3.9.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25"}, 196 | {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1"}, 197 | {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c"}, 198 | {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6"}, 199 | {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a"}, 200 | {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40"}, 201 | {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7"}, 202 | {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1"}, 203 | {file = "orjson-3.9.15-cp38-none-win32.whl", hash = "sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5"}, 204 | {file = "orjson-3.9.15-cp38-none-win_amd64.whl", hash = "sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b"}, 205 | {file = "orjson-3.9.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e"}, 206 | {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180"}, 207 | {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd"}, 208 | {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb"}, 209 | {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262"}, 210 | {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790"}, 211 | {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b"}, 212 | {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10"}, 213 | {file = "orjson-3.9.15-cp39-none-win32.whl", hash = "sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a"}, 214 | {file = "orjson-3.9.15-cp39-none-win_amd64.whl", hash = "sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7"}, 215 | {file = "orjson-3.9.15.tar.gz", hash = "sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061"}, 216 | ] 217 | 218 | [[package]] 219 | name = "pfzy" 220 | version = "0.3.4" 221 | description = "Python port of the fzy fuzzy string matching algorithm" 222 | optional = false 223 | python-versions = ">=3.7,<4.0" 224 | files = [ 225 | {file = "pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96"}, 226 | {file = "pfzy-0.3.4.tar.gz", hash = "sha256:717ea765dd10b63618e7298b2d98efd819e0b30cd5905c9707223dceeb94b3f1"}, 227 | ] 228 | 229 | [package.extras] 230 | docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] 231 | 232 | [[package]] 233 | name = "prompt-toolkit" 234 | version = "3.0.47" 235 | description = "Library for building powerful interactive command lines in Python" 236 | optional = false 237 | python-versions = ">=3.7.0" 238 | files = [ 239 | {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, 240 | {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, 241 | ] 242 | 243 | [package.dependencies] 244 | wcwidth = "*" 245 | 246 | [[package]] 247 | name = "robyn" 248 | version = "0.58.2" 249 | description = "A Super Fast Async Python Web Framework with a Rust runtime." 250 | optional = false 251 | python-versions = "*" 252 | files = [ 253 | {file = "robyn-0.58.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:137f8cf5357569bca4c6012e83ad0c1043e7dc6518bee2d2108acfe7ece9fbeb"}, 254 | {file = "robyn-0.58.2-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:0443dded57abe4de9a99c0d17641b3799402c6709f4445ed8ff20c083f6b2541"}, 255 | {file = "robyn-0.58.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e669ab8135b777c7c17eb78f82f6e92537b5ce4f9b0c55f8c5afb09067716482"}, 256 | {file = "robyn-0.58.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0bb92569fb5f51319a32fc060b01bebb7cb7410abe619f6057482d40b01acded"}, 257 | {file = "robyn-0.58.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d14715646c0f86971062d835fdffc06ef3ac7d36fd85e28395f6dd5782f9425"}, 258 | {file = "robyn-0.58.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03f16fb443761d1f6f453eafc0d2216aca41278bb0618d7c587536c4feb77105"}, 259 | {file = "robyn-0.58.2-cp310-none-win32.whl", hash = "sha256:0a1d09deba7e4c356f50972b48f8e86c4ccf624c0a26e38ef31cdb1b5ea53aa5"}, 260 | {file = "robyn-0.58.2-cp310-none-win_amd64.whl", hash = "sha256:a5b53e1d84a49fb8518f59a79835528d922321c23e21a76281c0180766c9fed5"}, 261 | {file = "robyn-0.58.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:92718fcbe28def92c3cab94b41e7384b3c4172b92b87956926a61fbdeaca077a"}, 262 | {file = "robyn-0.58.2-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:015fa064504baa6724b5db7cfa74b34053f1455253df49692d4cda71444f00ae"}, 263 | {file = "robyn-0.58.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c83ffbe99a310f298f693a09189dad074114382b71f01aeb08be4444a6102d8"}, 264 | {file = "robyn-0.58.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4205e03b8f2a747f50b46b5abc4f790eb06e2b447e578a95bfaa76b234599b4"}, 265 | {file = "robyn-0.58.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ace19085c71eae9d81ead8345f2478479ba15bccee0a9e6875c10648594c207"}, 266 | {file = "robyn-0.58.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:750dfbe3d1ef8bac2b1f1b95c46a3eb9c600ccf6f6fdb348bb4c4dc1937c31a8"}, 267 | {file = "robyn-0.58.2-cp311-none-win32.whl", hash = "sha256:be1413e4c6dc1b1da1433b37099ac681a16240f87ddca118dddaa0dc19dedb13"}, 268 | {file = "robyn-0.58.2-cp311-none-win_amd64.whl", hash = "sha256:706aeeec1af7df244017a63a458f4029a2897ad4cd7bd97bd74d3b0271615c3f"}, 269 | {file = "robyn-0.58.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:324ee8212782fdb1c164bb51f28d2a7e00e55dd3e820a46bdf906d129a521d42"}, 270 | {file = "robyn-0.58.2-cp312-cp312-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:ff8d7ac36679c5e1e3a643bfcd49005dc3881353f9a759d6cee1ce2a3f97adc0"}, 271 | {file = "robyn-0.58.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f149d0d7ae667bd91fee40a19e964e3c15637026dae07bb3ad4f63fa797a43ba"}, 272 | {file = "robyn-0.58.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98201bcff5dc28f50f2490b04fad393fae22316c059f58bd9e613c3c5f47aea6"}, 273 | {file = "robyn-0.58.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29ce6d657808eb5325faf61991e58e836ddc29cfa3e8061b12181d1bf2cb9dcf"}, 274 | {file = "robyn-0.58.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a54d2ad3e780ae2cd6ea96f96e7cb56150642d9e839136a5e990fd2a74df80db"}, 275 | {file = "robyn-0.58.2-cp312-none-win32.whl", hash = "sha256:1d1cff7ee0dff9e7ce1dfcb6ad0dd92c9060f01866bd3d45fedbf60f8db78952"}, 276 | {file = "robyn-0.58.2-cp312-none-win_amd64.whl", hash = "sha256:8c4ed7aac8d0ea85c1f58116b5428b4b2f69374ede0765c97de46a7ee5d54240"}, 277 | {file = "robyn-0.58.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:852c33b1e23e247fd7205a83d88058fe2bd2288870da8899b88f0549bb55ed2d"}, 278 | {file = "robyn-0.58.2-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:3d5bb96f124ff6e227d9d1b76af0424108f4f0228cd1931db513ec8d21390c3c"}, 279 | {file = "robyn-0.58.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87823f98357a822c2b47270da4ada34f629c1dac8899820e7ab5e315170cecfd"}, 280 | {file = "robyn-0.58.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76a7e3bfe2916a02a115b201439b7b36244e96e8dd125be89ff58ee2cb23f4bb"}, 281 | {file = "robyn-0.58.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91b71637f8438dcb6ed2f3b7af4f95bf37e3d1b244f37239ae506e397cc5a382"}, 282 | {file = "robyn-0.58.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c6b090d9258eba378a188923bc968a6cfa8524129cd11ca9328a5a9446cdb1"}, 283 | {file = "robyn-0.58.2-cp38-none-win32.whl", hash = "sha256:e20a7e65fbfa80a5d7d0d2ff9e7e3238666d438ddf0e27210118af37eeeee876"}, 284 | {file = "robyn-0.58.2-cp38-none-win_amd64.whl", hash = "sha256:b62fde9703498ee9c5a7190a838f85b7f4abdb9e893d71e2bf74d8b0a410a565"}, 285 | {file = "robyn-0.58.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:19f1b6c75640ef17a796e0acae017b1e98eaded3a4f7587c456e6cd568c2c371"}, 286 | {file = "robyn-0.58.2-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:ade45f4dc1f569160d918e325e35cb7d4a8319cdc9ddedf7c96cccfc0760bee8"}, 287 | {file = "robyn-0.58.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b65c79133d616ac74e3b331a851f1b71576cc6792b6cc5f3fe59b3d02885f"}, 288 | {file = "robyn-0.58.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:74bebffc40e2f21ec5fc3075ff5202101b8fc16942e2f3cc3c1b931aefdad433"}, 289 | {file = "robyn-0.58.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffaf0a1c2c212ec374820c6fd3f691b63e4172a0e5712c76877c5bae36cf2a91"}, 290 | {file = "robyn-0.58.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9abbc04ed54414f1c729b380cb438b6f30433d807b8fbe6aa116a6496e678ed"}, 291 | {file = "robyn-0.58.2-cp39-none-win32.whl", hash = "sha256:6fa2f32349523789c0333af1be1d08a50c49a8cae525e1f43b89ea95c164e608"}, 292 | {file = "robyn-0.58.2-cp39-none-win_amd64.whl", hash = "sha256:64c8ccfe8f8684dbb22cbe8d70aa65d3c49ee3e39167c70ca5e35fc5ec6125a2"}, 293 | {file = "robyn-0.58.2.tar.gz", hash = "sha256:406b4bd5380c20721af8da4c4d736406b09923998a3118606f84648b16743129"}, 294 | ] 295 | 296 | [package.dependencies] 297 | inquirerpy = "0.3.4" 298 | multiprocess = "0.70.14" 299 | nestd = "0.3.1" 300 | orjson = "3.9.15" 301 | rustimport = "1.3.4" 302 | uvloop = {version = ">=0.19.0,<0.20.0", markers = "sys_platform != \"win32\" and platform_python_implementation == \"CPython\" and platform_machine != \"armv7l\""} 303 | watchdog = "4.0.1" 304 | 305 | [package.extras] 306 | templating = ["jinja2 (==3.0.1)"] 307 | 308 | [[package]] 309 | name = "rustimport" 310 | version = "1.3.4" 311 | description = "Import Rust files directly from Python!" 312 | optional = false 313 | python-versions = "*" 314 | files = [ 315 | {file = "rustimport-1.3.4-py3-none-any.whl", hash = "sha256:f2b931ff4e0fa931028066a7dacaae449b1a4601fe7a553c35f3dd63aba97ce0"}, 316 | {file = "rustimport-1.3.4.tar.gz", hash = "sha256:ba80e3c28af07ba3910ad395613d01f9e421bfb59fbb1ac050e2b5d9b78b4980"}, 317 | ] 318 | 319 | [package.dependencies] 320 | toml = ">=0.10.2" 321 | 322 | [[package]] 323 | name = "toml" 324 | version = "0.10.2" 325 | description = "Python Library for Tom's Obvious, Minimal Language" 326 | optional = false 327 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 328 | files = [ 329 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 330 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 331 | ] 332 | 333 | [[package]] 334 | name = "trunco" 335 | version = "0.2.1" 336 | description = "An agile, powerful Python framework for creating dynamic UIs with seamless web integration." 337 | optional = false 338 | python-versions = "<4.0,>=3.8" 339 | files = [ 340 | {file = "trunco-0.2.1-py3-none-any.whl", hash = "sha256:4958bc34f2cc951390e12d4814232931f46469e5cc5fe76beb18c485ed062511"}, 341 | {file = "trunco-0.2.1.tar.gz", hash = "sha256:56545238bb43970d56cd6228a4b64e587215e4641f916b729fcc8c4645d20e14"}, 342 | ] 343 | 344 | [[package]] 345 | name = "uvloop" 346 | version = "0.19.0" 347 | description = "Fast implementation of asyncio event loop on top of libuv" 348 | optional = false 349 | python-versions = ">=3.8.0" 350 | files = [ 351 | {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, 352 | {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, 353 | {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, 354 | {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, 355 | {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, 356 | {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, 357 | {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, 358 | {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, 359 | {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, 360 | {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, 361 | {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, 362 | {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, 363 | {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, 364 | {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, 365 | {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, 366 | {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, 367 | {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, 368 | {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, 369 | {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, 370 | {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, 371 | {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, 372 | {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, 373 | {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, 374 | {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, 375 | {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, 376 | {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, 377 | {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, 378 | {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, 379 | {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, 380 | {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, 381 | {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, 382 | ] 383 | 384 | [package.extras] 385 | docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] 386 | test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] 387 | 388 | [[package]] 389 | name = "watchdog" 390 | version = "4.0.1" 391 | description = "Filesystem events monitoring" 392 | optional = false 393 | python-versions = ">=3.8" 394 | files = [ 395 | {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, 396 | {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, 397 | {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, 398 | {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, 399 | {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, 400 | {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, 401 | {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, 402 | {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, 403 | {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, 404 | {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, 405 | {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, 406 | {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, 407 | {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, 408 | {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, 409 | {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, 410 | {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, 411 | {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, 412 | {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, 413 | {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, 414 | {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, 415 | {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, 416 | {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, 417 | {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, 418 | {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, 419 | {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, 420 | {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, 421 | {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, 422 | {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, 423 | {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, 424 | {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, 425 | {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, 426 | {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, 427 | ] 428 | 429 | [package.extras] 430 | watchmedo = ["PyYAML (>=3.10)"] 431 | 432 | [[package]] 433 | name = "wcwidth" 434 | version = "0.2.13" 435 | description = "Measures the displayed width of unicode strings in a terminal" 436 | optional = false 437 | python-versions = "*" 438 | files = [ 439 | {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, 440 | {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, 441 | ] 442 | 443 | [metadata] 444 | lock-version = "2.0" 445 | python-versions = "^3.8" 446 | content-hash = "d63812bf6ded9e6735639d6381e088c7afa34f2b830197b75e491f92c243b6ab" 447 | -------------------------------------------------------------------------------- /pychartjs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavallaie/pychartjs/6c1d7316fe92c7c15dbcd783f6cf1e9c629ee451/pychartjs/__init__.py -------------------------------------------------------------------------------- /pychartjs/charts.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import List, Dict, Optional 3 | from pychartjs.datasets import Dataset 4 | from pychartjs.scales import Scale 5 | from pychartjs.options import ChartOptions 6 | from pychartjs.plugins import Plugin 7 | from pychartjs.enums import ChartType 8 | from trunco import Component 9 | 10 | 11 | @dataclass 12 | class Chart(Component): 13 | chart_type: Optional[ChartType] = None # Allow None for mixed types 14 | datasets: List[Dataset] = field(default_factory=list) 15 | options: Optional[ChartOptions] = field(default_factory=ChartOptions) 16 | labels: List[str] = field(default_factory=list) 17 | scales: List[Scale] = field(default_factory=list) 18 | plugins: List[Plugin] = field(default_factory=list) 19 | 20 | def render(self, context: Optional[Dict] = None) -> str: 21 | """Render the Chart.js component as an HTML string, with context substitution.""" 22 | # Apply context substitution to labels and datasets 23 | if context: 24 | rendered_labels = [label.format(**context) for label in self.labels] 25 | rendered_datasets = [ 26 | Dataset( 27 | label=dataset.label.format(**context), 28 | data=dataset.data, 29 | backgroundColor=dataset.backgroundColor, 30 | borderColor=dataset.borderColor, 31 | borderWidth=dataset.borderWidth, 32 | fill=dataset.fill, 33 | order=dataset.order, 34 | tension=dataset.tension, 35 | pointStyle=dataset.pointStyle, 36 | hoverBackgroundColor=dataset.hoverBackgroundColor, 37 | steppedLine=dataset.steppedLine, 38 | pointRadius=dataset.pointRadius, 39 | pointHoverRadius=dataset.pointHoverRadius, 40 | pointHoverBackgroundColor=dataset.pointHoverBackgroundColor, 41 | ).to_dict() 42 | for dataset in self.datasets 43 | ] 44 | else: 45 | rendered_labels = self.labels 46 | rendered_datasets = [dataset.to_dict() for dataset in self.datasets] 47 | 48 | chart_data = { 49 | "type": self.chart_type.value if self.chart_type else None, 50 | "data": { 51 | "labels": rendered_labels, 52 | "datasets": rendered_datasets, 53 | }, 54 | "options": self.options.to_dict(), 55 | "plugins": [plugin.to_dict() for plugin in self.plugins], 56 | "scales": { 57 | scale.scale_type.value: scale.to_dict() for scale in self.scales 58 | }, 59 | } 60 | 61 | import json 62 | 63 | chart_data_json = json.dumps(chart_data) 64 | 65 | chart_script = f""" 66 | var ctx = document.getElementById('{self.id}').getContext('2d'); 67 | new Chart(ctx, {chart_data_json}); 68 | """ 69 | 70 | self.add_custom_script(chart_script) 71 | 72 | return f'' + self.render_custom_scripts() 73 | -------------------------------------------------------------------------------- /pychartjs/colors.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | 5 | @dataclass 6 | class ColorScheme: 7 | backgroundColors: List[str] 8 | borderColors: List[str] 9 | 10 | def apply_to_datasets(self, datasets: List): 11 | num_colors = len(self.backgroundColors) 12 | for i, dataset in enumerate(datasets): 13 | # Rotate the color scheme if there are more datasets than colors 14 | dataset.backgroundColor = self.backgroundColors[i % num_colors] 15 | dataset.borderColor = self.borderColors[i % num_colors] 16 | 17 | 18 | # Example color scheme with at least seven colors 19 | basic_color_scheme = ColorScheme( 20 | backgroundColors=[ 21 | "rgba(255, 99, 132, 0.2)", # Red 22 | "rgba(54, 162, 235, 0.2)", # Blue 23 | "rgba(255, 206, 86, 0.2)", # Yellow 24 | "rgba(75, 192, 192, 0.2)", # Green 25 | "rgba(153, 102, 255, 0.2)", # Purple 26 | "rgba(255, 159, 64, 0.2)", # Orange 27 | "rgba(199, 199, 199, 0.2)", # Grey 28 | ], 29 | borderColors=[ 30 | "rgba(255, 99, 132, 1)", # Red 31 | "rgba(54, 162, 235, 1)", # Blue 32 | "rgba(255, 206, 86, 1)", # Yellow 33 | "rgba(75, 192, 192, 1)", # Green 34 | "rgba(153, 102, 255, 1)", # Purple 35 | "rgba(255, 159, 64, 1)", # Orange 36 | "rgba(199, 199, 199, 1)", # Grey 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /pychartjs/datasets.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Union, Optional, Dict 3 | 4 | 5 | @dataclass 6 | class Dataset: 7 | label: str 8 | data: List[Union[int, float, Dict[str, Union[int, float]]]] 9 | backgroundColor: Optional[Union[str, List[str]]] = None 10 | borderColor: Optional[Union[str, List[str]]] = None 11 | borderWidth: Optional[int] = None 12 | fill: Optional[Union[bool, str]] = None # Filler Plugin support 13 | order: Optional[int] = 0 # Support for drawing order 14 | tension: Optional[float] = None 15 | pointStyle: Optional[str] = None 16 | hoverBackgroundColor: Optional[str] = None 17 | steppedLine: Optional[bool] = None 18 | pointRadius: Optional[int] = None 19 | pointHoverRadius: Optional[int] = None 20 | pointHoverBackgroundColor: Optional[str] = None 21 | 22 | def to_dict(self) -> Dict: 23 | """Convert the dataset into a dictionary for Chart.js.""" 24 | return { 25 | "label": self.label, 26 | "data": self.data, 27 | "backgroundColor": self.backgroundColor, 28 | "borderColor": self.borderColor, 29 | "borderWidth": self.borderWidth, 30 | "fill": self.fill, 31 | "order": self.order, 32 | "tension": self.tension, 33 | "pointStyle": self.pointStyle, 34 | "hoverBackgroundColor": self.hoverBackgroundColor, 35 | "steppedLine": self.steppedLine, 36 | "pointRadius": self.pointRadius, 37 | "pointHoverRadius": self.pointHoverRadius, 38 | "pointHoverBackgroundColor": self.pointHoverBackgroundColor, 39 | } 40 | -------------------------------------------------------------------------------- /pychartjs/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ChartType(Enum): 5 | BAR = "bar" 6 | LINE = "line" 7 | PIE = "pie" 8 | DOUGHNUT = "doughnut" 9 | RADAR = "radar" 10 | POLAR_AREA = "polarArea" 11 | BUBBLE = "bubble" 12 | SCATTER = "scatter" 13 | 14 | 15 | class ScaleType(Enum): 16 | LINEAR = "linear" 17 | LOGARITHMIC = "logarithmic" 18 | TIME = "time" 19 | CATEGORY = "category" 20 | 21 | 22 | class Position(Enum): 23 | TOP = "top" 24 | LEFT = "left" 25 | BOTTOM = "bottom" 26 | RIGHT = "right" 27 | 28 | 29 | class InteractionMode(Enum): 30 | POINT = "point" 31 | NEAREST = "nearest" 32 | INDEX = "index" 33 | DATASET = "dataset" 34 | X = "x" 35 | Y = "y" 36 | 37 | 38 | class AnimationEasing(Enum): 39 | EASE_IN_QUAD = "easeInQuad" 40 | EASE_OUT_QUAD = "easeOutQuad" 41 | EASE_IN_OUT_QUAD = "easeInOutQuad" 42 | EASE_IN_CUBIC = "easeInCubic" 43 | EASE_OUT_CUBIC = "easeOutCubic" 44 | EASE_IN_OUT_CUBIC = "easeInOutCubic" 45 | -------------------------------------------------------------------------------- /pychartjs/options.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Optional, Dict 3 | from pychartjs.enums import Position, InteractionMode, AnimationEasing 4 | 5 | 6 | @dataclass 7 | class Legend: 8 | display: Optional[bool] = True 9 | position: Optional[Position] = Position.TOP 10 | labels: Optional[Dict] = None 11 | 12 | def to_dict(self) -> Dict: 13 | return { 14 | "display": self.display, 15 | "position": self.position.value, 16 | "labels": self.labels, 17 | } 18 | 19 | 20 | @dataclass 21 | class Tooltip: 22 | enabled: Optional[bool] = True 23 | mode: Optional[InteractionMode] = InteractionMode.NEAREST 24 | intersect: Optional[bool] = True 25 | callbacks: Optional[Dict[str, str]] = None # Custom callbacks for tooltip 26 | 27 | def to_dict(self) -> Dict: 28 | tooltip_dict = { 29 | "enabled": self.enabled, 30 | "mode": self.mode.value, 31 | "intersect": self.intersect, 32 | } 33 | if self.callbacks: 34 | tooltip_dict["callbacks"] = self.callbacks 35 | return tooltip_dict 36 | 37 | 38 | @dataclass 39 | class Title: 40 | display: Optional[bool] = False 41 | text: Optional[str] = None 42 | position: Optional[Position] = Position.TOP 43 | 44 | def to_dict(self) -> Dict: 45 | return { 46 | "display": self.display, 47 | "text": self.text, 48 | "position": self.position.value, 49 | } 50 | 51 | 52 | @dataclass 53 | class Animation: 54 | duration: Optional[int] = 1000 55 | easing: Optional[AnimationEasing] = AnimationEasing.EASE_IN_OUT_CUBIC 56 | onComplete: Optional[str] = None # Custom callback on animation complete 57 | onProgress: Optional[str] = None # Custom callback on animation progress 58 | 59 | def to_dict(self) -> Dict: 60 | animation_dict = { 61 | "duration": self.duration, 62 | "easing": self.easing.value, 63 | } 64 | if self.onComplete: 65 | animation_dict["onComplete"] = self.onComplete 66 | if self.onProgress: 67 | animation_dict["onProgress"] = self.onProgress 68 | return animation_dict 69 | 70 | 71 | @dataclass 72 | class Layout: 73 | padding: Optional[Dict[str, int]] = None 74 | 75 | def to_dict(self) -> Dict: 76 | return { 77 | "padding": self.padding, 78 | } 79 | 80 | 81 | @dataclass 82 | class Interaction: 83 | mode: Optional[InteractionMode] = InteractionMode.NEAREST 84 | intersect: Optional[bool] = True 85 | 86 | def to_dict(self) -> Dict: 87 | return { 88 | "mode": self.mode.value, 89 | "intersect": self.intersect, 90 | } 91 | 92 | 93 | @dataclass 94 | class ChartOptions: 95 | responsive: Optional[bool] = True 96 | maintainAspectRatio: Optional[bool] = True 97 | legend: Optional[Legend] = field(default_factory=Legend) 98 | tooltip: Optional[Tooltip] = field(default_factory=Tooltip) 99 | title: Optional[Title] = field(default_factory=Title) 100 | animation: Optional[Animation] = field(default_factory=Animation) 101 | layout: Optional[Layout] = field(default_factory=Layout) 102 | interaction: Optional[Interaction] = field(default_factory=Interaction) 103 | 104 | def to_dict(self) -> Dict: 105 | return { 106 | "responsive": self.responsive, 107 | "maintainAspectRatio": self.maintainAspectRatio, 108 | "legend": self.legend.to_dict(), 109 | "tooltip": self.tooltip.to_dict(), 110 | "title": self.title.to_dict(), 111 | "animation": self.animation.to_dict(), 112 | "layout": self.layout.to_dict(), 113 | "interaction": self.interaction.to_dict(), 114 | } 115 | -------------------------------------------------------------------------------- /pychartjs/plugins.py: -------------------------------------------------------------------------------- 1 | # pychartjs/plugins.py 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional, Dict 5 | 6 | 7 | @dataclass 8 | class DecimationOptions: 9 | enabled: bool = True 10 | algorithm: str = "lttb" # 'lttb' or 'min-max' 11 | samples: int = 1000 12 | 13 | def to_dict(self) -> Dict: 14 | return { 15 | "enabled": self.enabled, 16 | "algorithm": self.algorithm, 17 | "samples": self.samples, 18 | } 19 | 20 | 21 | @dataclass 22 | class Plugin: 23 | plugin_name: str 24 | options: Optional[Dict] = None 25 | beforeInit: Optional[str] = None 26 | afterDraw: Optional[str] = None 27 | 28 | def to_dict(self) -> Dict: 29 | plugin_dict = { 30 | "plugin_name": self.plugin_name, 31 | "options": self.options, 32 | } 33 | if self.beforeInit: 34 | plugin_dict["beforeInit"] = self.beforeInit 35 | if self.afterDraw: 36 | plugin_dict["afterDraw"] = self.afterDraw 37 | return plugin_dict 38 | -------------------------------------------------------------------------------- /pychartjs/scales.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Optional, Dict 3 | from pychartjs.enums import ScaleType, Position 4 | 5 | 6 | @dataclass 7 | class GridLines: 8 | color: Optional[str] = None 9 | lineWidth: Optional[int] = None 10 | drawBorder: Optional[bool] = None 11 | drawOnChartArea: Optional[bool] = None 12 | drawTicks: Optional[bool] = None 13 | 14 | def to_dict(self) -> Dict: 15 | return { 16 | "color": self.color, 17 | "lineWidth": self.lineWidth, 18 | "drawBorder": self.drawBorder, 19 | "drawOnChartArea": self.drawOnChartArea, 20 | "drawTicks": self.drawTicks, 21 | } 22 | 23 | 24 | @dataclass 25 | class Scale: 26 | scale_type: ScaleType 27 | display: Optional[bool] = True 28 | position: Optional[Position] = Position.LEFT 29 | gridLines: Optional[GridLines] = field(default_factory=GridLines) 30 | 31 | def to_dict(self) -> Dict: 32 | """Convert the scale into a dictionary for Chart.js.""" 33 | return { 34 | "type": self.scale_type.value, 35 | "display": self.display, 36 | "position": self.position.value, 37 | "gridLines": self.gridLines.to_dict(), 38 | } 39 | 40 | 41 | @dataclass 42 | class LinearScale(Scale): 43 | beginAtZero: Optional[bool] = False 44 | min: Optional[int] = None 45 | max: Optional[int] = None 46 | stepSize: Optional[int] = None 47 | 48 | def to_dict(self) -> Dict: 49 | data = super().to_dict() 50 | data.update( 51 | { 52 | "beginAtZero": self.beginAtZero, 53 | "min": self.min, 54 | "max": self.max, 55 | "stepSize": self.stepSize, 56 | } 57 | ) 58 | return data 59 | 60 | 61 | @dataclass 62 | class LogarithmicScale(Scale): 63 | min: Optional[int] = None 64 | max: Optional[int] = None 65 | 66 | def to_dict(self) -> Dict: 67 | data = super().to_dict() 68 | data.update( 69 | { 70 | "min": self.min, 71 | "max": self.max, 72 | } 73 | ) 74 | return data 75 | 76 | 77 | @dataclass 78 | class TimeScale(Scale): 79 | time_format: Optional[str] = None 80 | tooltipFormat: Optional[str] = None 81 | unit: Optional[str] = None 82 | stepSize: Optional[int] = None 83 | displayFormats: Optional[Dict] = None 84 | 85 | def to_dict(self) -> Dict: 86 | data = super().to_dict() 87 | data.update( 88 | { 89 | "time": { 90 | "parser": self.time_format, 91 | "tooltipFormat": self.tooltipFormat, 92 | "unit": self.unit, 93 | "stepSize": self.stepSize, 94 | "displayFormats": self.displayFormats, 95 | }, 96 | } 97 | ) 98 | return data 99 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pychartjs" 3 | version = "1.0.0" 4 | description = "git@github.com:tavallaie/pychartjs.git" 5 | authors = ["Ali Tavallaie "] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.8" 11 | trunco = "^0.2.1" 12 | 13 | 14 | [tool.poetry.group.dev.dependencies] 15 | robyn = "^0.58.2" 16 | jinja2 = "^3.1.4" 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /tests/test_charts.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pychartjs.charts import Chart 3 | from pychartjs.datasets import Dataset 4 | from pychartjs.enums import ChartType 5 | from pychartjs.options import ChartOptions, Legend, Title, Tooltip, Animation 6 | 7 | 8 | class TestChart(unittest.TestCase): 9 | def test_chart_creation(self): 10 | chart = Chart( 11 | chart_type=ChartType.BAR, 12 | datasets=[Dataset(label="Test Dataset", data=[10, 20, 30])], 13 | labels=["Jan", "Feb", "Mar"], 14 | ) 15 | self.assertEqual(chart.chart_type, ChartType.BAR) 16 | self.assertEqual(len(chart.datasets), 1) 17 | self.assertEqual(chart.datasets[0].label, "Test Dataset") 18 | self.assertEqual(chart.labels, ["Jan", "Feb", "Mar"]) 19 | 20 | def test_chart_render_basic(self): 21 | chart = Chart( 22 | chart_type=ChartType.LINE, 23 | datasets=[Dataset(label="Test Dataset", data=[10, 20, 30])], 24 | labels=["Jan", "Feb", "Mar"], 25 | ) 26 | rendered_html = chart.render() 27 | self.assertIn('", rendered_html) 29 | self.assertIn("new Chart(", rendered_html) 30 | 31 | def test_chart_render_with_options(self): 32 | chart = Chart( 33 | chart_type=ChartType.LINE, 34 | datasets=[Dataset(label="Test Dataset", data=[10, 20, 30])], 35 | labels=["Jan", "Feb", "Mar"], 36 | options=ChartOptions( 37 | responsive=False, 38 | legend=Legend(display=False), 39 | title=Title(display=True, text="Test Title"), 40 | tooltip=Tooltip(enabled=True), 41 | animation=Animation(duration=500), 42 | ), 43 | ) 44 | rendered_html = chart.render() 45 | self.assertIn("new Chart(", rendered_html) 46 | self.assertIn('"responsive": false', rendered_html) 47 | self.assertIn('"display": false', rendered_html) # Legend 48 | self.assertIn('"text": "Test Title"', rendered_html) # Title 49 | self.assertIn('"duration": 500', rendered_html) # Animation 50 | 51 | def test_chart_with_context(self): 52 | context = {"year": "2023", "month": "April"} 53 | chart = Chart( 54 | chart_type=ChartType.LINE, 55 | datasets=[Dataset(label="Sales Data {year}", data=[10, 20, 30])], 56 | labels=["Jan", "Feb", "{month}"], 57 | ) 58 | rendered_html = chart.render(context=context) 59 | self.assertIn("Sales Data 2023", rendered_html) # Check dataset label context 60 | self.assertIn('["Jan", "Feb", "April"]', rendered_html) # Check labels context 61 | 62 | def test_chart_multiple_datasets(self): 63 | chart = Chart( 64 | chart_type=ChartType.BAR, 65 | datasets=[ 66 | Dataset(label="Dataset 1", data=[10, 20, 30]), 67 | Dataset(label="Dataset 2", data=[15, 25, 35]), 68 | ], 69 | labels=["Jan", "Feb", "Mar"], 70 | ) 71 | rendered_html = chart.render() 72 | self.assertIn('"label": "Dataset 1"', rendered_html) 73 | self.assertIn('"label": "Dataset 2"', rendered_html) 74 | self.assertIn('"data": [10, 20, 30]', rendered_html) 75 | self.assertIn('"data": [15, 25, 35]', rendered_html) 76 | 77 | def test_chart_with_custom_scripts(self): 78 | chart = Chart( 79 | chart_type=ChartType.LINE, 80 | datasets=[Dataset(label="Test Dataset", data=[10, 20, 30])], 81 | labels=["Jan", "Feb", "Mar"], 82 | ) 83 | chart.add_custom_script("console.log('Custom Script');") 84 | rendered_html = chart.render() 85 | self.assertIn("