├── .gitignore
├── .python-version
├── LICENSE
├── README.md
├── custom.css
├── polars_intro
├── __init__.py
├── download_data.py
├── layouts
│ └── polars_intro.slides.json
└── polars_intro.py
├── pyproject.toml
├── requirements.txt
└── uv.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Data downloaded for this tutorial
2 | data/
3 |
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | share/python-wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 | MANIFEST
31 |
32 | # PyInstaller
33 | # Usually these files are written by a python script from a template
34 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
35 | *.manifest
36 | *.spec
37 |
38 | # Installer logs
39 | pip-log.txt
40 | pip-delete-this-directory.txt
41 |
42 | # Unit test / coverage reports
43 | htmlcov/
44 | .tox/
45 | .nox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | *.py,cover
53 | .hypothesis/
54 | .pytest_cache/
55 | cover/
56 |
57 | # Translations
58 | *.mo
59 | *.pot
60 |
61 | # Django stuff:
62 | *.log
63 | local_settings.py
64 | db.sqlite3
65 | db.sqlite3-journal
66 |
67 | # Flask stuff:
68 | instance/
69 | .webassets-cache
70 |
71 | # Scrapy stuff:
72 | .scrapy
73 |
74 | # Sphinx documentation
75 | docs/_build/
76 |
77 | # PyBuilder
78 | .pybuilder/
79 | target/
80 |
81 | # Jupyter Notebook
82 | .ipynb_checkpoints
83 |
84 | # IPython
85 | profile_default/
86 | ipython_config.py
87 |
88 | # pyenv
89 | # For a library or package, you might want to ignore these files since the code is
90 | # intended to run in multiple environments; otherwise, check them in:
91 | # .python-version
92 |
93 | # pipenv
94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
97 | # install all needed dependencies.
98 | #Pipfile.lock
99 |
100 | # poetry
101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
102 | # This is especially recommended for binary packages to ensure reproducibility, and is more
103 | # commonly ignored for libraries.
104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
105 | #poetry.lock
106 |
107 | # pdm
108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
109 | #pdm.lock
110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
111 | # in version control.
112 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
113 | .pdm.toml
114 | .pdm-python
115 | .pdm-build/
116 |
117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
118 | __pypackages__/
119 |
120 | # Celery stuff
121 | celerybeat-schedule
122 | celerybeat.pid
123 |
124 | # SageMath parsed files
125 | *.sage.py
126 |
127 | # Environments
128 | .env
129 | .venv
130 | env/
131 | venv/
132 | ENV/
133 | env.bak/
134 | venv.bak/
135 |
136 | # Spyder project settings
137 | .spyderproject
138 | .spyproject
139 |
140 | # Rope project settings
141 | .ropeproject
142 |
143 | # mkdocs documentation
144 | /site
145 |
146 | # mypy
147 | .mypy_cache/
148 | .dmypy.json
149 | dmypy.json
150 |
151 | # Pyre type checker
152 | .pyre/
153 |
154 | # pytype static type analyzer
155 | .pytype/
156 |
157 | # Cython debug symbols
158 | cython_debug/
159 |
160 | # PyCharm
161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163 | # and can be added to the global gitignore or merged into this file. For a more nuclear
164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165 | #.idea/
166 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.12
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Ryan Parker
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 | # Intro to [polars](https://pola.rs)
2 | A short demo to introduce the polars dataframe library.
3 |
4 | > [!NOTE]
5 | > You can also [view a static version of this notebook from your browser](https://rparkr.github.io/polars-intro/).
6 |
7 | # Getting started
8 | 1. Clone the repo and move into the folder
9 |
10 | ```shell
11 | git clone https://github.com/rparkr/polars-intro.git
12 | cd polars-intro
13 | ```
14 |
15 | 2. Create a virtual environment and install requirements. I recommend using [uv](https://docs.astral.sh/uv/), but you can also use Python's built-in `venv` module and `pip`:
16 |
17 |
18 | Using uv
19 |
20 | ```shell
21 | uv venv
22 | uv pip install -e .
23 | ```
24 |
25 |
26 |
27 |
28 | Using `venv` and `pip`
29 |
30 | ```shell
31 | python -m venv .venv
32 | source .venv/bin/activate
33 | pip install -e .
34 |
35 | # Or:
36 | # pip install -r requirements.txt
37 | ```
38 |
39 |
40 | 3. Run the app...
41 | ```shell
42 | marimo run polars_intro/polars_intro.py
43 | ```
44 |
45 | 4. Or edit the notebook:
46 | ```shell
47 | marimo edit polars_intro/polars_intro.py
48 | ```
49 |
--------------------------------------------------------------------------------
/custom.css:
--------------------------------------------------------------------------------
1 | /* Custom CSS for theming marimo notebooks */
2 | @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
3 | @import url('https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,100..900;1,100..900&display=swap');
4 | @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
5 |
6 | :root {
7 | --marimo-heading-font: 'Noto Serif', 'sans-serif';
8 | --marimo-text-font: 'Noto Sans', 'sans-serif';
9 | --marimo-monospace-font: 'Noto Sans Mono', 'monospace';
10 | }
11 |
12 | /* Increase paragraph font size and change color */
13 | /* .paragraph {
14 | font-size: 1.2rem;
15 | color: light-dark(rgb(28, 43, 72), rgb(194, 213, 244));
16 | }
17 |
18 | .h1 {
19 | font-weight: bold;
20 | }
21 |
22 | .h2 {
23 | font-weight: lighter;
24 | }
25 |
26 | .h3 {
27 | font-size: smaller;
28 | font-weight: bold;
29 | }
30 |
31 | .h4 {
32 | font-size: smaller;
33 | font-weight: lighter;
34 | } */
--------------------------------------------------------------------------------
/polars_intro/__init__.py:
--------------------------------------------------------------------------------
1 | def main() -> None:
2 | print("Hello from polars-intro!")
3 |
--------------------------------------------------------------------------------
/polars_intro/download_data.py:
--------------------------------------------------------------------------------
1 | """
2 | Asynchronously download data used in this tutorial.
3 | """
4 |
5 | import asyncio
6 | import datetime as dt
7 | import urllib.parse
8 | from pathlib import Path
9 | from zoneinfo import ZoneInfo
10 |
11 | import httpx
12 |
13 |
14 | # Files are published monthly, with a 2-month delay. For simplicity,
15 | # I use a 3-month delay to ensure that the data is available.
16 | def get_data_urls(year: int = None, **kwargs) -> list[str]:
17 | """Get the URLs for all months of Yellow Taxi data in a given year."""
18 | if not year:
19 | year = dt.date.today().year
20 | today = dt.date.today()
21 | assert (year >= 2009) and (year <= today.year), (
22 | f"year must be >= 2009 and <= {today.year}, but {year} was given"
23 | )
24 | if year == today.year:
25 | if today.month <= 3:
26 | print(
27 | "The current year was requested, but data may not yet "
28 | f"be available. Using last year ({today.year - 1}) instead."
29 | )
30 | year = today.year - 1
31 | # If today is Jan set to Oct; if Feb set to Nov; if Mar set to Dec
32 | end_month = today.month + 9
33 | else:
34 | end_month = dt.date.today().month - 3
35 | data_urls = [
36 | f"https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_{year}-{month:0>2d}.parquet"
37 | for month in range(1, end_month + 1)
38 | ]
39 | return data_urls
40 |
41 |
42 | async def download_taxi_data(
43 | urls: list[str] | None = None, save_dir: str = "data", **kwargs
44 | ) -> None:
45 | """
46 | Download NYC Yellow Cab taxi dataset to the "data/" directory in the current working directory.
47 |
48 | Parameters
49 | ----------
50 | urls: list of str
51 | The URLs for which data will be downloaded.
52 |
53 | save_dir: str
54 | The directory where the downloaded data will be saved.
55 | If a relative filepath is provided, this is assumed to be
56 | relative to the current working directory.
57 |
58 | kwargs: keyword-only arguments
59 | `year`: the year for which data will be downloaded,
60 | passed along to get_data_urls()
61 |
62 | Returns
63 | -------
64 | None
65 | """
66 | if not urls:
67 | urls = get_data_urls(year=kwargs.get("year"))
68 | # Ensure the urls variable is a list for consistency
69 | if isinstance(urls, str):
70 | urls = [urls]
71 | # Create the folder for saving the data
72 | if not save_dir:
73 | save_dir = "data"
74 | Path(save_dir).mkdir(parents=True, exist_ok=True)
75 |
76 | async def download_single_file(client, url, save_dir) -> str:
77 | """Download a file from a URL, save it, and return its filepath."""
78 | filepath = Path(save_dir) / Path(url).name
79 | with open(filepath, mode="ba" if not filepath.exists else "bw") as data_file:
80 | async with client.stream("GET", url) as response:
81 | async for chunk in response.aiter_bytes():
82 | data_file.write(chunk)
83 | return filepath
84 |
85 | async with httpx.AsyncClient() as client:
86 | results = await asyncio.gather(
87 | *[download_single_file(client, url, save_dir) for url in urls]
88 | )
89 | total_files = len(urls)
90 | max_digits = len(str(total_files))
91 | for i, result in enumerate(results):
92 | print(
93 | f"{i + 1:>0{max_digits}}/{total_files:>0{max_digits}} | Downloaded file: {result}"
94 | )
95 |
96 |
97 | def download_weather_data(
98 | start_date: dt.date | None = None,
99 | end_date: dt.date | None = None,
100 | latitude: float = 40.7128,
101 | longitude: float = 74.006,
102 | time_zone: str | ZoneInfo = "America/New_York",
103 | save_dir: str = "data",
104 | filename: str = "weather.json",
105 | ) -> str:
106 | """
107 | Download hourly weather data from Open-Meteo.com
108 |
109 | Parameters
110 | ----------
111 | start_date: datetime.date
112 | The start date for historical weather data.
113 | If not set, the first day of the current year
114 | will be used if the current date is after the
115 | third month of the year, otherwise, the start
116 | date will be the first day of the previous year.
117 | This aligns the weather data to the NYC Yellow
118 | Cab Taxi dataset, which has a 2-month delay in
119 | availability (I use a 3-month delay as a buffer).
120 |
121 | end_date: datetime.date
122 | The end date for historical weather data.
123 | If not set, this defaults to the current date if
124 | the current date is after the third month of the
125 | year, otherwise it is the last day of the previous
126 | year.
127 |
128 | latitude: float
129 | The latitude (North/South) coordinate for the location
130 | of interest. Defaults to the coordinate for New York City.
131 |
132 | longitude: float
133 | The longitude (East/West) coordinate for the location
134 | of interest. Defaults to the coordinate for New York City.
135 |
136 | time_zone: str or ZoneInfo object, default = "America/New_York"
137 | The time zone in which the data will be returned. If set to
138 | an emptry string or None, then UTC will be used.
139 |
140 | save_dir: str, default = "data"
141 | The directory (relative to the current working directory)
142 | where the data will be saved.
143 |
144 | filename: str, default = "weather.json"
145 |
146 | Returns
147 | -------
148 | str: filepath to downloaded data
149 |
150 | Notes
151 | -----
152 | Data source: https://open-meteo.com/. Open-Meteo's
153 | license permits non-commercial use for up to
154 | 10,000 API calls per day. Since this repository is
155 | used [for educational purposes](https://open-meteo.com/en/terms), it qualifies as
156 | non-commercial use. This function uses the
157 | equivalent of 6-8 API calls because of its long
158 | time horizon. Please respect Open-Meteo's restrictions
159 | and do not repeatedly run this function beyond the
160 | limits set by Open-Meteo, including ensuring that
161 | your use of this API call is non-commercial.
162 |
163 | See also:
164 | - [Open-Meteo's terms of use](https://open-meteo.com/en/terms)
165 | - [Open-Meteo's license]()
166 | - [Open-Meteo's historical data API docs](https://open-meteo.com/en/docs/historical-weather-api)
167 | """
168 | # If the start date is within the first three months of the year,
169 | # set the date range to the previous year; otherwise, set the range to
170 | # the current year. This aligns with the data publishing timelines for
171 | # the NYC Yellow Taxi dataset.
172 | today = dt.date.today()
173 | if not start_date:
174 | if today.month >= 3:
175 | start_date = dt.date(today.year, 1, 1)
176 | else:
177 | start_date = dt.date(today.year - 1, 1, 1)
178 | if not end_date:
179 | if today.month >= 3:
180 | end_date = today
181 | else:
182 | end_date = dt.date(today.year - 1, 12, 31)
183 |
184 | api_url = (
185 | "https://archive-api.open-meteo.com/v1/archive?"
186 | f"latitude={latitude}"
187 | f"&longitude={longitude}"
188 | f"&start_date={start_date}" # must be ISO-8601: yyyy-mm-dd, i.e., %Y-%m-%d
189 | f"&end_date={end_date}"
190 | "&hourly=temperature_2m,weather_code,is_day"
191 | )
192 | if time_zone:
193 | tz_url_encoded = urllib.parse.quote(str(time_zone), safe="")
194 | api_url += f"&timezone={tz_url_encoded}"
195 | # Create the folder if it does not exist
196 | Path(save_dir).mkdir(parents=True, exist_ok=True)
197 | filepath = Path(save_dir) / filename
198 |
199 | response = httpx.get(api_url)
200 |
201 | with open(filepath, mode="wt", encoding="utf8") as data_file:
202 | data_file.write(response.text)
203 | print(f"Saved weather data to: {filepath}")
204 | return str(filepath)
205 |
206 |
207 | def download_weather_codes(save_dir: str = "data") -> str:
208 | """
209 | Download WMO weather interpretation codes from: https://gist.github.com/stellasphere/9490c195ed2b53c707087c8c2db4ec0c
210 |
211 | Parameters
212 | ----------
213 | save_dir: str, default = "data"
214 | The directory (relative to the current working directory)
215 | where the data will be saved.
216 |
217 | Returns
218 | -------
219 | str: filepath to downloaded data
220 | """
221 | url = "https://gist.githubusercontent.com/stellasphere/9490c195ed2b53c707087c8c2db4ec0c/raw/76b0cb0ef0bfd8a2ec988aa54e30ecd1b483495d/descriptions.json"
222 | filepath = Path(save_dir) / "weather_codes.json"
223 |
224 | response = httpx.get(url)
225 |
226 | with open(filepath, mode="wt", encoding="utf8") as data_file:
227 | data_file.write(response.text)
228 | print(f"Saved weather codes to: {filepath}")
229 | return str(filepath)
230 |
231 |
232 | def download_all():
233 | asyncio.run(download_taxi_data())
234 | _ = download_weather_data()
235 | _ = download_weather_codes()
236 |
237 |
238 | # Download data
239 | if __name__ == "__main__":
240 | download_all()
241 |
--------------------------------------------------------------------------------
/polars_intro/layouts/polars_intro.slides.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "slides",
3 | "data": {}
4 | }
--------------------------------------------------------------------------------
/polars_intro/polars_intro.py:
--------------------------------------------------------------------------------
1 | import marimo
2 |
3 | __generated_with = "0.10.15"
4 | app = marimo.App(app_title="Polars intro", css_file="../custom.css")
5 |
6 |
7 | @app.cell
8 | def _(mo):
9 | mo.md(
10 | r"""
11 | # Intro to [polars](https://pola.rs)
12 |
13 | A brief introduction to the incredible `polars` dataframe library.
14 |
15 | 
16 |
17 | Created by: [Ryan Parker](https://github.com/rparkr), August 2024. Last updated in October 2024.
18 |
19 | This demo is a [marimo notebook](https://marimo.io/), so it is interactive and reactive -- try experimenting with the widgets later on!
20 | """
21 | )
22 | return
23 |
24 |
25 | @app.cell
26 | async def _(mo):
27 | from pathlib import Path
28 |
29 | import polars as pl
30 | import download_data
31 |
32 | callout_download = None
33 | # Download data
34 | if not Path("data").exists():
35 | callout_download = mo.callout(
36 | kind="info", value="Downloading NYC Taxi and weather data"
37 | )
38 | await download_data.download_taxi_data()
39 | download_data.download_weather_data()
40 | download_data.download_weather_codes()
41 | callout_download
42 | return Path, callout_download, download_data, pl
43 |
44 |
45 | @app.cell(hide_code=True)
46 | def _(mo):
47 | mo.md(
48 | r"""
49 | # Data analysis in Python
50 | As an interpreted language with an easy-to-read syntax, Python is fantastic for data analysis, where rapid iteration enables exploration and accelerates development.
51 |
52 | Since its first release in 2008, [pandas](https://pandas.pydata.org/docs/) has been the de-facto standard for data analysis in Python, but in recent years other libraries have been created which offer distinct advantages. Some of those include:
53 |
54 | - [cuDF](https://docs.rapids.ai/api/cudf/stable/): GPU-accelerated dataframe operations with pandas API support
55 | - [modin](https://modin.readthedocs.io/en/stable/): pandas API running on distributed compute using [Ray](https://www.ray.io/) or [Dask](https://www.dask.org/) as a backend
56 | - [ibis](https://ibis-project.org/): dataframe library supporting dozens of backends (including pandas, polars, DuckDB, and many SQL databases)
57 | - [DuckDB](https://duckdb.org/): in-process database engine for running SQL queries on local or remote data
58 | - [temporian](https://temporian.readthedocs.io/en/stable/): efficient data processing for timeseries data
59 | - [polars](https://pola.rs/): ultra-fast dataframe library written in Rust
60 | - and others...
61 | """
62 | )
63 | return
64 |
65 |
66 | @app.cell
67 | def _(mo):
68 | mo.md(
69 | rf"""
70 | # Polars advantages
71 | - Easy to use
72 | - Parallelized across all CPU cores
73 | - Zero dependencies
74 | - Built on the Apache Arrow in-memory data format: enables zero-copy interoperability with other libraries (e.g., DuckDB, Snowflake)
75 | - Handles datasets larger than RAM
76 | - Powerful query optimizer
77 | - Fully compatible with scikit-learn and a growing ecosystem of other libraries, thanks to the [Dataframe Interchange Protocol](https://data-apis.org/dataframe-protocol/latest/) and [narwhals](https://github.com/narwhals-dev/narwhals)
78 |
79 | - {mo.icon('fluent-mdl2:rust-language-logo')} written in [Rust](https://rust-lang.org), a compiled language that has experienced rapid adoption since its first stable release in 2015 thanks to its C/C++ performance, concurrency, and memory safety
80 | """
81 | )
82 | return
83 |
84 |
85 | @app.cell
86 | def _(mo):
87 | mo.md(
88 | r"""
89 | # Key concepts
90 |
91 | Polars uses the Apache Arrow in-memory data format, which is column-oriented. The primary data structures for polars are Series and DataFrames, similar to pandas.
92 |
93 | Apache Arrow supports many useful data types (many more than those which are supported by NumPy), so you can perform fast, vectorized operations on all kinds of data (nested JSON `structs`, strings, datetimes, etc.)
94 | """
95 | )
96 | return
97 |
98 |
99 | @app.cell
100 | def _(mo):
101 | mo.md(
102 | r"""
103 | ## Contexts
104 | In Polars, a _context_ refers to the data available to operate on.
105 |
106 | The primary contexts are:
107 |
108 | **Selection**:
109 |
110 | - `.select()`: choose a subset of columns and perform operations on them
111 | - `.with_columns()`: add to the columns already available
112 |
113 | **Filtering**:
114 |
115 | - `.filter()`: filter the data using boolean conditions on row values
116 |
117 | **Aggregation**:
118 |
119 | - `.group_by()`: perform aggregations on groups of values
120 | """
121 | )
122 | return
123 |
124 |
125 | @app.cell
126 | def _(mo):
127 | mo.md(
128 | r"""
129 | ## Expressions
130 |
131 | _Expressions_ are the operations performed in Polars, things like:
132 |
133 | - `.sum()`
134 | - `.len()`
135 | - `.mean().over()...`
136 | - `when().then().otherwise()`
137 | - `.str.replace()`
138 | """
139 | )
140 | return
141 |
142 |
143 | @app.cell
144 | def _(mo):
145 | mo.md(
146 | r"""
147 | ## Lazy vs. Eager mode
148 | - `scan_csv()` vs. `read_csv()`
149 |
150 | ### Recommendation: use Lazy mode
151 | - In Lazy mode, Polars will optimize the query plan
152 | """
153 | )
154 | return
155 |
156 |
157 | @app.cell
158 | def _(mo):
159 | mo.vstack(
160 | [
161 | mo.md(
162 | r"""
163 | # Plugin ecosystem
164 | You can create custom expressions to use in Polars, which will also be vectorized and run in parallel like standard Polars expressions. If there's an operation you'd like to run on your data, chances are someone has already implemented it and it's just a `pip install` away. Here are [some examples](https://docs.pola.rs/user-guide/expressions/plugins/#community-plugins)...
165 | """
166 | ),
167 | mo.accordion(
168 | {
169 | "### [`polars_ds`](https://github.com/abstractqqq/polars_ds_extension)": (
170 | r"""
171 | Polars extension for data science tasks
172 |
173 | - A combination of functions and operations from scikit-learn, SciPy, and edit distance
174 | - Polars is the only dependency (unless you want to create plots; that adds Plotly as a dependency)
175 | - Can create bar plots within dataframe outputs (HTML `__repr__` in a notebook) -- like sparklines, and similar to what is available in pandas' advanced dataframe styling options
176 | """
177 | ),
178 | "### [`polars_distance`](https://github.com/ion-elgreco/polars-distance)": (
179 | r"""
180 | Distance calculations (e.g., word similarity) in polars. Also includes haversine distance (lat/lon), cosine similarity, etc.
181 | """
182 | ),
183 | "### [`polars_reverse_geocode`](https://github.com/MarcoGorelli/polars-reverse-geocode)": (
184 | r"""
185 | Offline reverse geocoding: find a city based on provided lat/lon; using an offline lookup table
186 | """
187 | ),
188 | "### Tutorial: [how to create a polars plugin](https://marcogorelli.github.io/polars-plugins-tutorial/)": (
189 | r"""
190 | You can create your own plugin! This tutorial teaches you enough Rust to write a polars plugin, which can published to PyPI and installed by other Polars users.
191 | """
192 | ),
193 | }
194 | ),
195 | ]
196 | )
197 | return
198 |
199 |
200 | @app.cell
201 | def _(mo):
202 | mo.md(
203 | r"""
204 | # Final thoughts
205 |
206 | ## Upgrade weekly
207 | ⭐ Polars development [advances rapidly](https://github.com/pola-rs/polars/releases), so I recommend upgrading often (weekly) to get the latest features
208 |
209 | ## Try it out
210 | The best way to learn is by doing. Try using Polars any time you create a new notebook or start a new project.
211 | """
212 | )
213 | return
214 |
215 |
216 | @app.cell
217 | def _(mo):
218 | mo.md(
219 | r"""
220 | # Resources
221 | - [Polars user guide](https://docs.pola.rs/user-guide/migration/pandas/): fantastic guide to learning Polars alongside helpful explanations
222 | - [Coming from `pandas`](https://docs.pola.rs/user-guide/migration/pandas/): are you familiar with `pandas` and want to learn the differences you'll notice when switching to polars? This guide translates common concepts to help you.
223 | - [This series of articles from 2022](https://kevinheavey.github.io/modern-polars/) demonstrates some operations in pandas and polars, side-by-side. _Polars development advances rapidly, so many of the concepts covered in that series are already different. Still it will help you get a general feel for the flow of using Polars compared to pandas._
224 | - [Polars Python API](https://docs.pola.rs/api/python/stable/reference/index.html): detailed info on every expression, method, and function in Polars. I recommend browsing this list to get a feel for what Polars can do.
225 | """
226 | )
227 | return
228 |
229 |
230 | @app.cell(hide_code=True)
231 | def _(mo):
232 | mo.vstack(
233 | [
234 | mo.md(r"""
235 | # Demo
236 | In this section, I demonstrate basic Polars usage on the NYC Taxi Yellow Cab dataset. You can find more information about that dataset on the [NYC Trip Record Data page](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page).
237 | """),
238 | mo.accordion(
239 | {
240 | "## Data dictionary (from the [PDF file published by NYC Trip Record Data](https://www.nyc.gov/assets/tlc/downloads/pdf/data_dictionary_trip_records_yellow.pdf))": r"""
241 | 1. **VendorID**: A code indicating the TPEP provider that provided the record. 1= Creative Mobile Technologies, LLC; 2= VeriFone Inc.
242 | 2. **tpep_pickup_datetime**: The date and time when the meter was engaged
243 | 3. **tpep_dropoff_datetime**: The date and time when the meter was disengaged
244 | 4. **Passenger_count**: The number of passengers in the vehicle. This is a driver-entered value.
245 | 5. **Trip_distance**: The elapsed trip distance in miles reported by the taximeter
246 | 6. **PULocationID**: TLC Taxi Zone in which the taximeter was engaged
247 | - [See here](http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml) for a map of the TLC Taxi Zones
248 | 7. **DOLocationID**: TLC Taxi Zone in which the taximeter was disengaged
249 | 8. **RateCodeID**: The final rate code in effect at the end of the trip.
250 | - 1 = Standard rate
251 | - 2 = JFK
252 | - 3 = Newark
253 | - 4 = Nassau or Westchester
254 | - 5 = Negotiated fare
255 | - 6 = Group ride
256 | 9. **Store_and_fwd_flag**: This flag indicates whether the trip record was held in vehicle memory before sending to the vendor, aka “store and forward,” because the vehicle did not have a connection to the server
257 | - Y = store and forward trip
258 | - N = not a store and forward trip
259 | 10. **Payment_type**: A numeric code signifying how the passenger paid for the trip
260 | - 1 = Credit card
261 | - 2 = Cash
262 | - 3 = No charge
263 | - 4 = Dispute
264 | - 5 = Unknown
265 | - 6 = Voided trip
266 | 11. **Fare_amount**: The time-and-distance fare calculated by the meter
267 | 12. **Extra**: Miscellaneous extras and surcharges. Currently, this only includes the $0.50 and $1 rush hour and overnight charges.
268 | 13. **MTA_tax**: $0.50 MTA tax that is automatically triggered based on the metered rate in use
269 | 14. **Improvement_surcharge**: $0.30 improvement surcharge assessed trips at the flag drop. The improvement surcharge began being levied in 2015.
270 | 15. **Tip_amount**: Tip amount – This field is automatically populated for credit card tips. Cash tips are not included.
271 | 16. **Tolls_amount**: Total amount of all tolls paid in trip.
272 | 17. **Total_amount**: The total amount charged to passengers. Does not include cash tips
273 | 18. **Congestion_Surcharge**: Total amount collected in trip for NYS congestion surcharge.
274 | 19. **Airport_fee**: $1.25 for pick up only at LaGuardia and John F. Kennedy Airports
275 | """
276 | }
277 | ),
278 | ]
279 | )
280 | return
281 |
282 |
283 | @app.cell
284 | def _(mo):
285 | mo.md(
286 | r"""
287 | ## Lazy-load the data
288 | Polars can read Parquet files (local or hosted on a network), determine their schema (columns and data types), apply filter pushdowns, and download only the data that is needed for the operations being performed.
289 |
290 | ```python
291 | import polars as pl
292 |
293 | # Create a dataframe from a collection of parquet files
294 | df = pl.scan_parquet("data/yellow_tripdata_*.parquet)
295 | ```
296 | """
297 | )
298 | return
299 |
300 |
301 | @app.cell
302 | def _(mo, pl):
303 | _md = mo.md(
304 | """
305 | Let's check the schema:
306 |
307 | ```python
308 | # Polars will scan the data and return
309 | # the column names and datatypes
310 | df.collect_schema()
311 |
312 | # If the file is stored locally, you can
313 | # also read the schema without collecting fist
314 | pl.read_parquet_schema("path/to/a/local/file.parquet")
315 | ```
316 | """
317 | )
318 |
319 | # Create a LazyFrame that will use the data from all the files specified above
320 | df = pl.scan_parquet("data/yellow_tripdata_*.parquet")
321 | _output = mo.plain(df.collect_schema())
322 | mo.vstack([_md, _output])
323 | return (df,)
324 |
325 |
326 | @app.cell
327 | def _(df, mo, pl):
328 | _md = mo.md(
329 | """
330 | **Preview the first few rows:**
331 |
332 | ```python
333 | df.head(n=10).collect()
334 | ```
335 | """
336 | )
337 |
338 | with pl.Config(tbl_cols=20, tbl_width_chars=1000, thousands_separator=True):
339 | _output = mo.plain_text(df.head(n=10).collect())
340 |
341 | mo.vstack([_md, _output])
342 | return
343 |
344 |
345 | @app.cell
346 | def _(df, mo, pl):
347 | _md = mo.md(
348 | """
349 | **You can also preview the first few rows like this:**
350 |
351 | ```python
352 | df.collect().glimpse()
353 | ```
354 | """
355 | )
356 |
357 | with mo.capture_stdout() as buffer:
358 | with pl.Config(thousands_separator=True):
359 | df.collect().glimpse()
360 | _output = mo.plain_text(buffer.getvalue())
361 | print(buffer.getvalue())
362 | mo.vstack([_md, _output, "Full list:", buffer.getvalue().strip().split("\n")])
363 | return (buffer,)
364 |
365 |
366 | @app.cell
367 | def _(df, pl):
368 | with pl.Config(thousands_separator=True):
369 | df.collect().glimpse()
370 | return
371 |
372 |
373 | @app.cell
374 | def _(df, mo, pl):
375 | _month_list = (
376 | df.select(month=pl.col("tpep_pickup_datetime").dt.strftime("%Y-%m"))
377 | .group_by("month")
378 | .agg(num_trips=pl.len())
379 | .filter(pl.col("num_trips") > 100) # Remove erroneous timestamps
380 | .unique()
381 | .sort(by="month")
382 | .collect()
383 | .to_series()
384 | .to_list()
385 | )
386 | print(_month_list)
387 | month_selection = mo.ui.multiselect(value=_month_list, options=_month_list)
388 | return (month_selection,)
389 |
390 |
391 | @app.cell
392 | def _(df, mo, month_selection, pl):
393 | _md = mo.md(
394 | f"""
395 | ## Explore the data
396 |
397 | **Find the average cost per trip, by month**
398 |
399 | Month selection: {month_selection}
400 |
401 | Note that the operations below are performed in parallel across all available CPU cores, and that only the data needed will be downloaded.
402 |
403 | In this case, since I have filtered to {len(month_selection.value)} months, only the files with those months of data will be accessed. Also notice that only 5 columns are accessed, since those are the ones I have requested.
404 | """
405 | )
406 |
407 | query_plan = (
408 | df.with_columns(month=pl.col("tpep_pickup_datetime").dt.strftime("%Y-%m"))
409 | .filter(pl.col("month").is_in(month_selection.value))
410 | .group_by(pl.col("month"))
411 | .agg(
412 | num_trips=pl.len(), # count the number of trips
413 | cost_per_trip=pl.col("total_amount").mean(),
414 | avg_passengers_per_trip=pl.col("passenger_count").mean(),
415 | avg_distance=pl.col("trip_distance").mean(),
416 | num_airport_trips=(pl.col("Airport_fee") > 0).sum(),
417 | )
418 | )
419 | _output = mo.plain_text(
420 | query_plan.explain(format="plain") # see also: format="tree"
421 | )
422 | _output_with_streaming = mo.plain_text(query_plan.explain(streaming=True))
423 |
424 | _accordion = mo.accordion(
425 | {
426 | "Here's a way we could answer this question in Polars": mo.md(
427 | rf"""
428 | Let's see this in Polars:
429 |
430 | ```python
431 | query_plan = (
432 | df.with_columns(month=pl.col("tpep_pickup_datetime").dt.strftime("%Y-%m"))
433 | .filter(pl.col("month").is_in({month_selection.value}))
434 | .group_by(pl.col("month"))
435 | .agg(
436 | num_trips=pl.len(), # count the number of trips
437 | cost_per_trip=pl.col("total_amount").mean(),
438 | avg_passengers_per_trip=pl.col("passenger_count").mean(),
439 | avg_distance=pl.col("trip_distance").mean(),
440 | num_airport_trips=(pl.col("Airport_fee") > 0).sum(),
441 | )
442 | )
443 | ```
444 | """
445 | ),
446 | "Let's see how Polars will optimize this query": mo.md(
447 | r"""
448 | ```python
449 | # Show the optimized query plan:
450 | query_plan.explain()
451 | ```
452 | """
453 | ),
454 | "Query plan": _output,
455 | "You can also run this in streaming mode for memory-constrained environments": mo.md(
456 | r"""
457 | ```python
458 | query_plan.explain(streaming=True)
459 | ```
460 | """
461 | ),
462 | "Query plan with streaming": _output_with_streaming,
463 | }
464 | )
465 |
466 |
467 | mo.vstack([_md, _accordion])
468 | return (query_plan,)
469 |
470 |
471 | @app.cell
472 | def _(mo, month_selection, pl, query_plan):
473 | _md = mo.md(
474 | rf"""
475 | ### Perform the calculation ("collect")
476 |
477 | Month selection: {month_selection}
478 |
479 | ```python
480 | df_avg = query_plan.collect().sort(by=pl.col("month"))
481 | ```
482 |
483 | Some options to `.collect()`: `engine="cpu"`, `streaming=False`, `background=False`
484 | """
485 | )
486 |
487 | df_avg = query_plan.collect().sort(by=pl.col("month"))
488 |
489 | with pl.Config(tbl_cols=20, tbl_width_chars=1000, thousands_separator=True):
490 | _output = mo.plain_text(df_avg)
491 |
492 | mo.vstack([_md, _output])
493 | return (df_avg,)
494 |
495 |
496 | @app.cell
497 | def _(df, mo, month_selection, pl):
498 | _md = mo.md(
499 | f"""
500 | ## How does trip count vary by hour of the day?
501 |
502 | Months: {month_selection}
503 | """
504 | )
505 |
506 | _result = (
507 | df.with_columns(
508 | month=pl.col("tpep_pickup_datetime").dt.strftime("%Y-%m"),
509 | hour=pl.col("tpep_pickup_datetime").dt.hour(),
510 | )
511 | .filter(pl.col("month").is_in(month_selection.value))
512 | .group_by(["month", "hour"])
513 | .agg(
514 | num_trips=pl.len(), # count the number of trips
515 | )
516 | .filter(pl.col("num_trips") > 100) # exclude erroneous timestamps
517 | .sort(by=["month", "hour"], descending=[False, False])
518 | )
519 |
520 | _plot = _result.collect().plot.line(x="hour", y="num_trips", color="month")
521 |
522 | _accordion = mo.accordion(
523 | {
524 | "Let's see this in Polars:": mo.md(
525 | rf"""
526 | ```python
527 | result = (
528 | df.with_columns(
529 | month=pl.col("tpep_pickup_datetime").dt.strftime("%Y-%m"),
530 | hour=pl.col("tpep_pickup_datetime").dt.hour(),
531 | )
532 | .filter(pl.col("month").is_in({month_selection.value}))
533 | .group_by(["month", "hour"])
534 | .agg(
535 | num_trips=pl.len(), # count the number of trips
536 | )
537 | .filter(pl.col("num_trips") > 100) # exclude erroneous timestamps
538 | .sort(by=["month", "hour"], descending=[False, False])
539 | )
540 |
541 | # Plot with Altair
542 | plot = (
543 | result
544 | .collect()
545 | .plot.line(
546 | x="hour",
547 | y="num_trips",
548 | color="month"
549 | )
550 | )
551 | ```
552 | """
553 | ),
554 | }
555 | )
556 |
557 | mo.vstack([_md, _accordion, _plot])
558 | return
559 |
560 |
561 | @app.cell
562 | def _():
563 | # Explore the weather codes:
564 | import json
565 |
566 | with open("data/weather_codes.json", mode="rt", encoding="utf8") as json_file:
567 | weather_codes_dict = json.load(json_file)
568 |
569 | with open("data/weather.json", mode="rt", encoding="utf8") as json_file:
570 | weather_data_dict = json.load(json_file)
571 | return json, json_file, weather_codes_dict, weather_data_dict
572 |
573 |
574 | @app.cell
575 | def _(df, pl):
576 | # import polars.selectors as cs # for "column selectors"
577 |
578 | # Load the weather codes data and convert to a LazyFrame
579 | weather_codes = (
580 | pl.read_json("data/weather_codes.json")
581 | .unpivot()
582 | .select(pl.col("variable").alias("weather_code"), pl.col("value"))
583 | .unnest("value")
584 | # Expand the descriptions for night only,
585 | # which uses "clear" rather than "sunny"
586 | .unnest("night")
587 | .select(
588 | [
589 | # Must be the same data type (Int64) for joining
590 | pl.col("weather_code").cast(pl.Int64),
591 | pl.col("description").alias("weather_description"),
592 | ]
593 | )
594 | ).lazy() # must be a LazyFrame to join with another LazyFrame
595 |
596 | # Get the names of the weather variables, so their lists can be "exploded"
597 | # down the rows of the LazyFrame
598 | data_fields = (
599 | pl.scan_ndjson("data/weather.json")
600 | .select("hourly_units")
601 | .collect() # you have to .collect() to access a Series
602 | .to_series() # the .struct accessor is available only for Series
603 | .struct.fields # .fields holds the keys (column names, after unnesting)
604 | )
605 |
606 | # LazyFrame with weather data and weather descriptions
607 | df_weather = (
608 | pl.scan_ndjson("data/weather.json")
609 | .unnest("hourly") # expand a dict into columns
610 | .explode(
611 | columns=data_fields, # expand a list into rows
612 | # Alternatively, using column selectors:
613 | # columns=cs.by_name(data_fields)
614 | )
615 | .select(data_fields)
616 | .join(weather_codes, on="weather_code")
617 | .with_columns(
618 | # Replace the "time" column and update its
619 | # datatype so it can be joined to the trip data
620 | pl.col("time").cast(pl.Datetime(time_unit="ns")),
621 | )
622 | .sort(by="time")
623 | )
624 |
625 | # Combine with the Taxi data
626 | # for a join_asof, both DataFrames need to
627 | # be sorted by the join_asof key
628 | df_combined = df.sort("tpep_pickup_datetime").join_asof(
629 | df_weather,
630 | left_on="tpep_pickup_datetime",
631 | right_on="time",
632 | # Use the weather data closest to the time of pickup
633 | strategy="nearest", # alternatives: "backward", "forward"
634 | tolerance="2h", # weather data must be within 2 hours of trip time
635 | )
636 | return data_fields, df_combined, df_weather, weather_codes
637 |
638 |
639 | @app.cell
640 | def _(df_combined, df_weather, mo, weather_codes_dict, weather_data_dict):
641 | _md = mo.md(
642 | r"""
643 | ## How does weather impact trips?
644 |
645 | For this analysis, we'll take advantage of Polar's ability to load and join multiple data types and join them together using approximate timestamp matching -- all using "lazy," optimized computations.
646 |
647 | The time zone for both taxi trip data and weather is **America/New_York**.
648 |
649 | > [Weather data from Open-Meteo.com](https://open-meteo.com/), weather codes from [stellasphere](https://gist.github.com/stellasphere/9490c195ed2b53c707087c8c2db4ec0c).
650 | """
651 | )
652 |
653 |
654 | _accordion = mo.accordion(
655 | {
656 | "Here's the plain JSON weather data": weather_data_dict,
657 | "And here are the weather codes": weather_codes_dict,
658 | "Let's load the weather data in Polars": (
659 | """
660 | ```python
661 | # This is also a LazyFrame (you can tell from `scan_`)
662 | # LazyFrame with weather data and weather descriptions
663 | df_weather = (
664 | pl.scan_ndjson("data/weather.json")
665 | .unnest("hourly") # expand a dict into columns
666 | .explode(
667 | columns=data_fields, # expand a list into rows
668 | )
669 | .select(data_fields)
670 | .join(weather_codes, on="weather_code")
671 | .with_columns(
672 | # Replace the "time" column and update its
673 | # datatype so it can be joined to the trip data
674 | pl.col("time").cast(pl.Datetime(time_unit="ns")),
675 | )
676 | .sort(by="time")
677 | )
678 | ```
679 | """
680 | ),
681 | "Here's what the data looks like": df_weather.head(100).collect(),
682 | "Combine with the Taxi trips data": (
683 | """
684 | ```python
685 | # for a join_asof, both DataFrames need to
686 | # be sorted by the join_asof key
687 | df_combined = df.sort("tpep_pickup_datetime").join_asof(
688 | df_weather,
689 | left_on="tpep_pickup_datetime",
690 | right_on="time",
691 | # Use the weather data closest to the time of pickup
692 | strategy="nearest", # alternatives: "backward", "forward"
693 | tolerance="2h", # weather data must be within 2 hours of trip time
694 | )
695 | ```
696 | """
697 | ),
698 | "Preview the combined dataset": df_combined.head(100).collect(),
699 | "Here's the Polars code for the weather codes and data fields": (
700 | """
701 | ```python
702 | # Load the weather codes data and convert to a LazyFrame
703 | weather_codes = (
704 | pl.read_json("data/weather_codes.json")
705 | .unpivot()
706 | .select(pl.col("variable").alias("weather_code"), pl.col("value"))
707 | .unnest("value")
708 | # Expand the descriptions for night only,
709 | # which uses "clear" rather than "sunny"
710 | .unnest("night")
711 | .select(
712 | [
713 | # Must be the same data type (Int64) for joining
714 | pl.col("weather_code").cast(pl.Int64),
715 | pl.col("description").alias("weather_description"),
716 | ]
717 | )
718 | ).lazy() # must be a LazyFrame to join with another LazyFrame
719 |
720 | # Get the names of the weather variables, so their lists can be "exploded"
721 | # down the rows of the LazyFrame
722 | data_fields = (
723 | pl.scan_ndjson("data/weather.json")
724 | .select("hourly_units")
725 | .collect() # you have to .collect() to access a Series
726 | .to_series() # the .struct accessor is available only for Series
727 | .struct.fields # .fields holds the keys (column names, after unnesting)
728 | )
729 | ```
730 | """
731 | ),
732 | }
733 | )
734 |
735 | mo.vstack([_md, _accordion])
736 | return
737 |
738 |
739 | @app.cell
740 | def _(mo, weather_codes):
741 | weather_selection = mo.ui.multiselect.from_series(
742 | weather_codes.select("weather_description").collect().to_series(),
743 | label="",
744 | value=["Clear", "Rain"],
745 | )
746 | return (weather_selection,)
747 |
748 |
749 | @app.cell
750 | def _(df_combined, mo, pl, weather_selection):
751 | _md = mo.md(
752 | f"""
753 | ### How much does weather impact average cost?
754 |
755 | Weather to compare: {weather_selection}
756 | """
757 | )
758 |
759 | _result = (
760 | df_combined.with_columns(
761 | month=pl.col("tpep_pickup_datetime").dt.strftime("%Y-%m")
762 | )
763 | .group_by(["month", "weather_description"])
764 | .agg(
765 | num_trips=pl.len(),
766 | cost_per_person=pl.col("total_amount").sum()
767 | / pl.col("passenger_count").sum(),
768 | )
769 | .filter(pl.col("num_trips") > 100)
770 | # Filter based on our selection
771 | # Joins are highly optimized and can be faster than .is_in()
772 | .join(
773 | pl.LazyFrame({"weather_description": weather_selection.value}),
774 | on="weather_description",
775 | )
776 | .sort(by=["month", "weather_description"])
777 | )
778 |
779 | _plot_bar = _result.collect().plot.bar(
780 | x="month",
781 | y="cost_per_person",
782 | color="weather_description",
783 | xOffset="weather_description",
784 | )
785 |
786 | _plot_line = _result.collect().plot.line(
787 | x="month",
788 | y="num_trips",
789 | color="weather_description",
790 | )
791 | # Show the plots side by side (use + to layer on top of one another)
792 | _plot = _plot_bar | _plot_line
793 |
794 | _accordion = mo.accordion(
795 | {
796 | "Polars operations": (
797 | """
798 | ```python
799 | result = (
800 | df_combined.with_columns(
801 | month=pl.col("tpep_pickup_datetime").dt.strftime("%Y-%m")
802 | )
803 | .group_by(["month", "weather_description"])
804 | .agg(
805 | num_trips=pl.len(),
806 | cost_per_person=pl.col("total_amount").sum()
807 | / pl.col("passenger_count").sum(),
808 | )
809 | .filter(pl.col("num_trips") > 100)
810 | # Filter based on our selection
811 | # Joins are highly optimized and can be faster than .is_in()
812 | .join(
813 | pl.LazyFrame({"weather_description": weather_selection.value}),
814 | on="weather_description",
815 | )
816 | .sort(by=["month", "weather_description"])
817 | )
818 | ```
819 | """
820 | ),
821 | "Let's check the query plan (**`result.explain()`**)": mo.plain_text(
822 | _result.explain()
823 | ),
824 | "Now, we'll visualize this with an interactive plot": (
825 | """
826 | ```python
827 | plot_bar = result.collect().plot.bar(
828 | x="month",
829 | y="cost_per_person",
830 | color="weather_description",
831 | xOffset="weather_description",
832 | )
833 |
834 | plot_line = result.collect().plot.line(
835 | x="month",
836 | y="num_trips",
837 | color="weather_description",
838 | )
839 | # Show the plots side by side (use + to layer on top of one another)
840 | plot = plot_bar | plot_line
841 | ```
842 | """
843 | ),
844 | }
845 | )
846 |
847 | mo.vstack([_md, _accordion, _plot])
848 | return
849 |
850 |
851 | @app.cell
852 | def _(mo):
853 | mo.md(
854 | """
855 | # Keep exploring!
856 | Polars has many additional powerful features. Try exploring with some of them through this dataset and what you find on Polars' [User Guide](https://docs.pola.rs/) and [API reference](https://docs.pola.rs/api/python/stable/reference/index.html).
857 | """
858 | )
859 | return
860 |
861 |
862 | @app.cell
863 | def _():
864 | import marimo as mo
865 | return (mo,)
866 |
867 |
868 | if __name__ == "__main__":
869 | app.run()
870 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "polars-intro"
3 | version = "0.1.0"
4 | description = "A quick demo introducing the polars dataframe library through a marimo notebook."
5 | readme = "README.md"
6 | authors = [
7 | { name = "Ryan Parker", email = "ryanparker.ml@gmail.com" }
8 | ]
9 | requires-python = ">=3.11"
10 | dependencies = [
11 | "httpx>=0.27.2",
12 | "marimo>=0.9.4",
13 | # next-generation reactive notebook and app
14 | "polars[plot]>=1.9.0",
15 | # lightning-fast dataframe library, along with altair for plotting
16 | ]
17 |
18 | [project.scripts]
19 | polars-intro = "polars_intro:main"
20 |
21 | [build-system]
22 | requires = ["hatchling"]
23 | build-backend = "hatchling.build"
24 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | altair==5.4.1
2 | anyio==4.6.0
3 | attrs==24.2.0
4 | certifi==2024.8.30
5 | click==8.1.7
6 | docutils==0.21.2
7 | h11==0.14.0
8 | httpcore==1.0.6
9 | httpx==0.27.2
10 | idna==3.10
11 | itsdangerous==2.2.0
12 | jedi==0.19.1
13 | jinja2==3.1.4
14 | jsonschema==4.23.0
15 | jsonschema-specifications==2024.10.1
16 | marimo==0.9.4
17 | markdown==3.7
18 | markupsafe==3.0.1
19 | narwhals==1.9.2
20 | packaging==24.1
21 | parso==0.8.4
22 | polars==1.9.0
23 | -e file:///home/ryan/code/polars-intro
24 | psutil==6.0.0
25 | pygments==2.18.0
26 | pymdown-extensions==10.11.2
27 | pyyaml==6.0.2
28 | referencing==0.35.1
29 | rpds-py==0.20.0
30 | ruff==0.6.9
31 | sniffio==1.3.1
32 | starlette==0.39.2
33 | tomlkit==0.13.2
34 | typing-extensions==4.12.2
35 | uvicorn==0.31.1
36 | websockets==12.0
37 |
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | requires-python = ">=3.11"
3 |
4 | [[package]]
5 | name = "altair"
6 | version = "5.4.1"
7 | source = { registry = "https://pypi.org/simple" }
8 | dependencies = [
9 | { name = "jinja2" },
10 | { name = "jsonschema" },
11 | { name = "narwhals" },
12 | { name = "packaging" },
13 | { name = "typing-extensions", marker = "python_full_version < '3.13'" },
14 | ]
15 | sdist = { url = "https://files.pythonhosted.org/packages/ae/09/38904138a49f29e529b61b4f39954a6837f443d828c1bc57814be7bd4813/altair-5.4.1.tar.gz", hash = "sha256:0ce8c2e66546cb327e5f2d7572ec0e7c6feece816203215613962f0ec1d76a82", size = 636465 }
16 | wheels = [
17 | { url = "https://files.pythonhosted.org/packages/9b/52/4a86a4fa1cc2aae79137cc9510b7080c3e5aede2310d14fae5486feec7f7/altair-5.4.1-py3-none-any.whl", hash = "sha256:0fb130b8297a569d08991fb6fe763582e7569f8a04643bbd9212436e3be04aef", size = 658150 },
18 | ]
19 |
20 | [[package]]
21 | name = "anyio"
22 | version = "4.6.0"
23 | source = { registry = "https://pypi.org/simple" }
24 | dependencies = [
25 | { name = "idna" },
26 | { name = "sniffio" },
27 | ]
28 | sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 }
29 | wheels = [
30 | { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 },
31 | ]
32 |
33 | [[package]]
34 | name = "attrs"
35 | version = "24.2.0"
36 | source = { registry = "https://pypi.org/simple" }
37 | sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 }
38 | wheels = [
39 | { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 },
40 | ]
41 |
42 | [[package]]
43 | name = "certifi"
44 | version = "2024.8.30"
45 | source = { registry = "https://pypi.org/simple" }
46 | sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
47 | wheels = [
48 | { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
49 | ]
50 |
51 | [[package]]
52 | name = "click"
53 | version = "8.1.7"
54 | source = { registry = "https://pypi.org/simple" }
55 | dependencies = [
56 | { name = "colorama", marker = "platform_system == 'Windows'" },
57 | ]
58 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
59 | wheels = [
60 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
61 | ]
62 |
63 | [[package]]
64 | name = "colorama"
65 | version = "0.4.6"
66 | source = { registry = "https://pypi.org/simple" }
67 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
68 | wheels = [
69 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
70 | ]
71 |
72 | [[package]]
73 | name = "docutils"
74 | version = "0.21.2"
75 | source = { registry = "https://pypi.org/simple" }
76 | sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 }
77 | wheels = [
78 | { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 },
79 | ]
80 |
81 | [[package]]
82 | name = "h11"
83 | version = "0.14.0"
84 | source = { registry = "https://pypi.org/simple" }
85 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
86 | wheels = [
87 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
88 | ]
89 |
90 | [[package]]
91 | name = "httpcore"
92 | version = "1.0.6"
93 | source = { registry = "https://pypi.org/simple" }
94 | dependencies = [
95 | { name = "certifi" },
96 | { name = "h11" },
97 | ]
98 | sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 }
99 | wheels = [
100 | { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 },
101 | ]
102 |
103 | [[package]]
104 | name = "httpx"
105 | version = "0.27.2"
106 | source = { registry = "https://pypi.org/simple" }
107 | dependencies = [
108 | { name = "anyio" },
109 | { name = "certifi" },
110 | { name = "httpcore" },
111 | { name = "idna" },
112 | { name = "sniffio" },
113 | ]
114 | sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 }
115 | wheels = [
116 | { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 },
117 | ]
118 |
119 | [[package]]
120 | name = "idna"
121 | version = "3.10"
122 | source = { registry = "https://pypi.org/simple" }
123 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
124 | wheels = [
125 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
126 | ]
127 |
128 | [[package]]
129 | name = "itsdangerous"
130 | version = "2.2.0"
131 | source = { registry = "https://pypi.org/simple" }
132 | sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 }
133 | wheels = [
134 | { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 },
135 | ]
136 |
137 | [[package]]
138 | name = "jedi"
139 | version = "0.19.1"
140 | source = { registry = "https://pypi.org/simple" }
141 | dependencies = [
142 | { name = "parso" },
143 | ]
144 | sdist = { url = "https://files.pythonhosted.org/packages/d6/99/99b493cec4bf43176b678de30f81ed003fd6a647a301b9c927280c600f0a/jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", size = 1227821 }
145 | wheels = [
146 | { url = "https://files.pythonhosted.org/packages/20/9f/bc63f0f0737ad7a60800bfd472a4836661adae21f9c2535f3957b1e54ceb/jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0", size = 1569361 },
147 | ]
148 |
149 | [[package]]
150 | name = "jinja2"
151 | version = "3.1.4"
152 | source = { registry = "https://pypi.org/simple" }
153 | dependencies = [
154 | { name = "markupsafe" },
155 | ]
156 | sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
157 | wheels = [
158 | { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
159 | ]
160 |
161 | [[package]]
162 | name = "jsonschema"
163 | version = "4.23.0"
164 | source = { registry = "https://pypi.org/simple" }
165 | dependencies = [
166 | { name = "attrs" },
167 | { name = "jsonschema-specifications" },
168 | { name = "referencing" },
169 | { name = "rpds-py" },
170 | ]
171 | sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 }
172 | wheels = [
173 | { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 },
174 | ]
175 |
176 | [[package]]
177 | name = "jsonschema-specifications"
178 | version = "2024.10.1"
179 | source = { registry = "https://pypi.org/simple" }
180 | dependencies = [
181 | { name = "referencing" },
182 | ]
183 | sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 }
184 | wheels = [
185 | { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 },
186 | ]
187 |
188 | [[package]]
189 | name = "marimo"
190 | version = "0.9.4"
191 | source = { registry = "https://pypi.org/simple" }
192 | dependencies = [
193 | { name = "click" },
194 | { name = "docutils" },
195 | { name = "itsdangerous" },
196 | { name = "jedi" },
197 | { name = "markdown" },
198 | { name = "packaging" },
199 | { name = "psutil" },
200 | { name = "pygments" },
201 | { name = "pymdown-extensions" },
202 | { name = "pyyaml" },
203 | { name = "ruff" },
204 | { name = "starlette" },
205 | { name = "tomlkit" },
206 | { name = "uvicorn" },
207 | { name = "websockets" },
208 | ]
209 | sdist = { url = "https://files.pythonhosted.org/packages/3c/aa/ac1327c17b3560854701bd5e638325e8e6ab0d39b28b53fe57e609d0b521/marimo-0.9.4.tar.gz", hash = "sha256:458a0d208a817b1c0f2f293b67df0a044f8a6fd8e64faa476d92fb71f4fdb545", size = 11376216 }
210 | wheels = [
211 | { url = "https://files.pythonhosted.org/packages/0e/22/caa18d2c4b78a413189d3f9053e8f0c495fc97e774cb4d70677861e21326/marimo-0.9.4-py3-none-any.whl", hash = "sha256:309a3c6a673c399cffab876cb77ee4624e02e3317d21337ba0a405680585700f", size = 11667973 },
212 | ]
213 |
214 | [[package]]
215 | name = "markdown"
216 | version = "3.7"
217 | source = { registry = "https://pypi.org/simple" }
218 | sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 }
219 | wheels = [
220 | { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 },
221 | ]
222 |
223 | [[package]]
224 | name = "markupsafe"
225 | version = "3.0.1"
226 | source = { registry = "https://pypi.org/simple" }
227 | sdist = { url = "https://files.pythonhosted.org/packages/b4/d2/38ff920762f2247c3af5cbbbbc40756f575d9692d381d7c520f45deb9b8f/markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344", size = 20249 }
228 | wheels = [
229 | { url = "https://files.pythonhosted.org/packages/ce/af/2f5d88a7fc7226bd34c6e15f6061246ad8cff979da9f19d11bdd0addd8e2/MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad", size = 14387 },
230 | { url = "https://files.pythonhosted.org/packages/8d/43/fd588ef5d192308c5e05974bac659bf6ae29c202b7ea2c4194bcf01eacee/MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583", size = 12410 },
231 | { url = "https://files.pythonhosted.org/packages/58/26/78f161d602fb03804118905e5faacafc0ec592bbad71aaee62537529813a/MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7", size = 24006 },
232 | { url = "https://files.pythonhosted.org/packages/ae/1d/7d5ec8bcfd9c2db235d720fa51d818b7e2abc45250ce5f53dd6cb60409ca/MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b", size = 23303 },
233 | { url = "https://files.pythonhosted.org/packages/26/ce/703ca3b03a709e3bd1fbffa407789e56b9fa664456538092617dd665fc1d/MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3", size = 23205 },
234 | { url = "https://files.pythonhosted.org/packages/88/60/40be0493decabc2344b12d3a709fd6ccdd15a5ebaee1e8d878315d107ad3/MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50", size = 23684 },
235 | { url = "https://files.pythonhosted.org/packages/6d/f8/8fd52a66e8f62a9add62b4a0b5a3ab4092027437f2ef027f812d94ae91cf/MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915", size = 23472 },
236 | { url = "https://files.pythonhosted.org/packages/d4/0b/998b17b9e06ea45ad1646fea586f1b83d02dfdb14d47dd2fd81fba5a08c9/MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91", size = 23388 },
237 | { url = "https://files.pythonhosted.org/packages/5a/57/b6b7aa23b2e26d68d601718f8ce3161fbdaf967b31752c7dec52bef828c9/MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635", size = 15106 },
238 | { url = "https://files.pythonhosted.org/packages/fc/b5/20cb1d714596acb553c810009c8004c809823947da63e13c19a7decfcb6c/MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf", size = 15542 },
239 | { url = "https://files.pythonhosted.org/packages/45/6d/72ed58d42a12bd9fc288dbff6dd8d03ea973a232ac0538d7f88d105b5251/MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4", size = 14322 },
240 | { url = "https://files.pythonhosted.org/packages/86/f5/241238f89cdd6461ac9f521af8389f9a48fab97e4f315c69e9e0d52bc919/MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5", size = 12380 },
241 | { url = "https://files.pythonhosted.org/packages/27/94/79751928bca5841416d8ca02e22198672e021d5c7120338e2a6e3771f8fc/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346", size = 24099 },
242 | { url = "https://files.pythonhosted.org/packages/10/6e/1b8070bbfc467429c7983cd5ffd4ec57e1d501763d974c7caaa0a9a79f4c/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729", size = 23249 },
243 | { url = "https://files.pythonhosted.org/packages/66/50/9389ae6cdff78d7481a2a2641830b5eb1d1f62177550e73355a810a889c9/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc", size = 23149 },
244 | { url = "https://files.pythonhosted.org/packages/16/02/5dddff5366fde47133186efb847fa88bddef85914bbe623e25cfeccb3517/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9", size = 23864 },
245 | { url = "https://files.pythonhosted.org/packages/f3/f1/700ee6655561cfda986e03f7afc309e3738918551afa7dedd99225586227/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b", size = 23440 },
246 | { url = "https://files.pythonhosted.org/packages/fb/3e/d26623ac7f16709823b4c80e0b4a1c9196eeb46182a6c1d47b5e0c8434f4/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38", size = 23610 },
247 | { url = "https://files.pythonhosted.org/packages/51/04/1f8da0810c39cb9fcff96b6baed62272c97065e9cf11471965a161439e20/MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa", size = 15113 },
248 | { url = "https://files.pythonhosted.org/packages/eb/24/a36dc37365bdd358b1e583cc40475593e36ab02cb7da6b3d0b9c05b0da7a/MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f", size = 15611 },
249 | { url = "https://files.pythonhosted.org/packages/b1/60/4572a8aa1beccbc24b133aa0670781a5d2697f4fa3fecf0a87b46383174b/MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772", size = 14325 },
250 | { url = "https://files.pythonhosted.org/packages/38/42/849915b99a765ec104bfd07ee933de5fc9c58fa9570efa7db81717f495d8/MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da", size = 12373 },
251 | { url = "https://files.pythonhosted.org/packages/ef/82/4caaebd963c6d60b28e4445f38841d24f8b49bc10594a09956c9d73bfc08/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a", size = 24059 },
252 | { url = "https://files.pythonhosted.org/packages/20/15/6b319be2f79fcfa3173f479d69f4e950b5c9b642db4f22cf73ae5ade745f/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c", size = 23211 },
253 | { url = "https://files.pythonhosted.org/packages/9d/3f/8963bdf4962feb2154475acb7dc350f04217b5e0be7763a39b432291e229/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd", size = 23095 },
254 | { url = "https://files.pythonhosted.org/packages/af/93/f770bc70953d32de0c6ce4bcb76271512123a1ead91aaef625a020c5bfaf/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7", size = 23901 },
255 | { url = "https://files.pythonhosted.org/packages/11/92/1e5a33aa0a1190161238628fb68eb1bc5e67b56a5c89f0636328704b463a/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd", size = 23463 },
256 | { url = "https://files.pythonhosted.org/packages/0d/fe/657efdfe385d2a3a701f2c4fcc9577c63c438aeefdd642d0d956c4ecd225/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5", size = 23569 },
257 | { url = "https://files.pythonhosted.org/packages/cf/24/587dea40304046ace60f846cedaebc0d33d967a3ce46c11395a10e7a78ba/MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c", size = 15117 },
258 | { url = "https://files.pythonhosted.org/packages/32/8f/d8961d633f26a011b4fe054f3bfff52f673423b8c431553268741dfb089e/MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f", size = 15613 },
259 | { url = "https://files.pythonhosted.org/packages/9e/93/d6367ffbcd0c5c371370767f768eaa32af60bc411245b8517e383c6a2b12/MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a", size = 14563 },
260 | { url = "https://files.pythonhosted.org/packages/4a/37/f813c3835747dec08fe19ac9b9eced01fdf93a4b3e626521675dc7f423a9/MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d", size = 12505 },
261 | { url = "https://files.pythonhosted.org/packages/72/bf/800b4d1580298ca91ccd6c95915bbd147142dad1b8cf91d57b93b28670dd/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396", size = 25358 },
262 | { url = "https://files.pythonhosted.org/packages/fd/78/26e209abc8f0a379f031f0acc151231974e5b153d7eda5759d17d8f329f2/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453", size = 23797 },
263 | { url = "https://files.pythonhosted.org/packages/09/e1/918496a9390891756efee818880e71c1bbaf587f4dc8ede3f3852357310a/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4", size = 23743 },
264 | { url = "https://files.pythonhosted.org/packages/cd/c6/26f576cd58d6c2decd9045e4e3f3c5dbc01ea6cb710916e7bbb6ebd95b6b/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8", size = 25076 },
265 | { url = "https://files.pythonhosted.org/packages/b5/fa/10b24fb3b0e15fe5389dc88ecc6226ede08297e0ba7130610efbe0cdfb27/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984", size = 24037 },
266 | { url = "https://files.pythonhosted.org/packages/c8/81/4b3f5537d9f6cc4f5c80d6c4b78af9a5247fd37b5aba95807b2cbc336b9a/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a", size = 24015 },
267 | { url = "https://files.pythonhosted.org/packages/5f/07/8e8dcecd53216c5e01a51e84c32a2bce166690ed19c184774b38cd41921d/MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b", size = 15213 },
268 | { url = "https://files.pythonhosted.org/packages/0d/87/4c364e0f109eea2402079abecbe33fef4f347b551a11423d1f4e187ea497/MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295", size = 15741 },
269 | ]
270 |
271 | [[package]]
272 | name = "narwhals"
273 | version = "1.9.2"
274 | source = { registry = "https://pypi.org/simple" }
275 | sdist = { url = "https://files.pythonhosted.org/packages/57/b9/f9861a88f9b2e4e8ba70ae5ce42209502db01c98c4740bca0c489543102d/narwhals-1.9.2.tar.gz", hash = "sha256:f5b02ec2ff3f5a668719a01e8d434b3fa100fbf1f4fb0d0653202b5c31695c9a", size = 153645 }
276 | wheels = [
277 | { url = "https://files.pythonhosted.org/packages/9c/45/42e78291a4471963741f113277b4cddb4063af6c793d191b6429036ef73f/narwhals-1.9.2-py3-none-any.whl", hash = "sha256:9cb9a9a384cd7547e022f9601512c93f8bd784ab6cf55cc5fa76f724bb014989", size = 185075 },
278 | ]
279 |
280 | [[package]]
281 | name = "packaging"
282 | version = "24.1"
283 | source = { registry = "https://pypi.org/simple" }
284 | sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 }
285 | wheels = [
286 | { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 },
287 | ]
288 |
289 | [[package]]
290 | name = "parso"
291 | version = "0.8.4"
292 | source = { registry = "https://pypi.org/simple" }
293 | sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 }
294 | wheels = [
295 | { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 },
296 | ]
297 |
298 | [[package]]
299 | name = "polars"
300 | version = "1.9.0"
301 | source = { registry = "https://pypi.org/simple" }
302 | sdist = { url = "https://files.pythonhosted.org/packages/53/09/c2fb0b231d551e0c8e68097d08577712bdff1ba91346cda8228e769602f5/polars-1.9.0.tar.gz", hash = "sha256:8e1206ef876f61c1d50a81e102611ea92ee34631cb135b46ad314bfefd3cb122", size = 4027431 }
303 | wheels = [
304 | { url = "https://files.pythonhosted.org/packages/64/cc/3d0292048d8f9045a03510aeecda2e6ed9df451ae8853274946ff841f98b/polars-1.9.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a471d2ce96f6fa5dd0ef16bcdb227f3dbe3af8acb776ca52f9e64ef40c7489a0", size = 31870933 },
305 | { url = "https://files.pythonhosted.org/packages/ee/be/15af97f4d8b775630da16a8bf0141507d9c0ae5f2637b9a27ed337b3b1ba/polars-1.9.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94b12d731cd200d2c50b13fc070d6353f708e632bca6529c5a72aa6a69e5285d", size = 28171055 },
306 | { url = "https://files.pythonhosted.org/packages/bb/57/b286b317f061d8f17bab4726a27e7b185fbf3d3db65cf689074256ea34a9/polars-1.9.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f85f132732aa63c6f3b502b0fdfc3ba9f0b78cc6330059b5a2d6f9fd78508acb", size = 33063367 },
307 | { url = "https://files.pythonhosted.org/packages/e5/25/bf5d43dcb538bf6573b15f3d5995a52be61b8fbce0cd737e72c4d25eef88/polars-1.9.0-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:f753c8941a3b3249d59262d68a856714a96a7d4e16977aefbb196be0c192e151", size = 29764698 },
308 | { url = "https://files.pythonhosted.org/packages/a6/cf/f9170a3ac20e0efb9d3c1cdacc677e35b711ffd5ec48a6d5f3da7b7d8663/polars-1.9.0-cp38-abi3-win_amd64.whl", hash = "sha256:95de07066cd797dd940fa2783708a7bef93c827a57be0f4dfad3575a6144212b", size = 32819142 },
309 | ]
310 |
311 | [package.optional-dependencies]
312 | plot = [
313 | { name = "altair" },
314 | ]
315 |
316 | [[package]]
317 | name = "polars-intro"
318 | version = "0.1.0"
319 | source = { editable = "." }
320 | dependencies = [
321 | { name = "httpx" },
322 | { name = "marimo" },
323 | { name = "polars", extra = ["plot"] },
324 | ]
325 |
326 | [package.metadata]
327 | requires-dist = [
328 | { name = "httpx", specifier = ">=0.27.2" },
329 | { name = "marimo", specifier = ">=0.9.4" },
330 | { name = "polars", extras = ["plot"], specifier = ">=1.9.0" },
331 | ]
332 |
333 | [[package]]
334 | name = "psutil"
335 | version = "6.0.0"
336 | source = { registry = "https://pypi.org/simple" }
337 | sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067 }
338 | wheels = [
339 | { url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961 },
340 | { url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478 },
341 | { url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455 },
342 | { url = "https://files.pythonhosted.org/packages/cd/5f/60038e277ff0a9cc8f0c9ea3d0c5eb6ee1d2470ea3f9389d776432888e47/psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132", size = 292046 },
343 | { url = "https://files.pythonhosted.org/packages/8b/20/2ff69ad9c35c3df1858ac4e094f20bd2374d33c8643cf41da8fd7cdcb78b/psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d", size = 253560 },
344 | { url = "https://files.pythonhosted.org/packages/73/44/561092313ae925f3acfaace6f9ddc4f6a9c748704317bad9c8c8f8a36a79/psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3", size = 257399 },
345 | { url = "https://files.pythonhosted.org/packages/7c/06/63872a64c312a24fb9b4af123ee7007a306617da63ff13bcc1432386ead7/psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0", size = 251988 },
346 | ]
347 |
348 | [[package]]
349 | name = "pygments"
350 | version = "2.18.0"
351 | source = { registry = "https://pypi.org/simple" }
352 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
353 | wheels = [
354 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
355 | ]
356 |
357 | [[package]]
358 | name = "pymdown-extensions"
359 | version = "10.11.2"
360 | source = { registry = "https://pypi.org/simple" }
361 | dependencies = [
362 | { name = "markdown" },
363 | { name = "pyyaml" },
364 | ]
365 | sdist = { url = "https://files.pythonhosted.org/packages/f4/71/2730a20e9e3752393d78998347f8b1085ef9c417646ea9befbeef221e3c4/pymdown_extensions-10.11.2.tar.gz", hash = "sha256:bc8847ecc9e784a098efd35e20cba772bc5a1b529dfcef9dc1972db9021a1049", size = 830241 }
366 | wheels = [
367 | { url = "https://files.pythonhosted.org/packages/c2/35/c0edf199257ef0a7d407d29cd51c4e70d1dad4370a5f44deb65a7a5475e2/pymdown_extensions-10.11.2-py3-none-any.whl", hash = "sha256:41cdde0a77290e480cf53892f5c5e50921a7ee3e5cd60ba91bf19837b33badcf", size = 259044 },
368 | ]
369 |
370 | [[package]]
371 | name = "pyyaml"
372 | version = "6.0.2"
373 | source = { registry = "https://pypi.org/simple" }
374 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
375 | wheels = [
376 | { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 },
377 | { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 },
378 | { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 },
379 | { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 },
380 | { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 },
381 | { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 },
382 | { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 },
383 | { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 },
384 | { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 },
385 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 },
386 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 },
387 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 },
388 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 },
389 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 },
390 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 },
391 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 },
392 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 },
393 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 },
394 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
395 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
396 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
397 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
398 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
399 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
400 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
401 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
402 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
403 | ]
404 |
405 | [[package]]
406 | name = "referencing"
407 | version = "0.35.1"
408 | source = { registry = "https://pypi.org/simple" }
409 | dependencies = [
410 | { name = "attrs" },
411 | { name = "rpds-py" },
412 | ]
413 | sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 }
414 | wheels = [
415 | { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 },
416 | ]
417 |
418 | [[package]]
419 | name = "rpds-py"
420 | version = "0.20.0"
421 | source = { registry = "https://pypi.org/simple" }
422 | sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 }
423 | wheels = [
424 | { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 },
425 | { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 },
426 | { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 },
427 | { url = "https://files.pythonhosted.org/packages/7a/8c/668195ab9226d01b7cf7cd9e59c1c0be1df05d602df7ec0cf46f857dcf59/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", size = 368359 },
428 | { url = "https://files.pythonhosted.org/packages/52/28/356f6a39c1adeb02cf3e5dd526f5e8e54e17899bef045397abcfbf50dffa/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", size = 394886 },
429 | { url = "https://files.pythonhosted.org/packages/a2/65/640fb1a89080a8fb6f4bebd3dafb65a2edba82e2e44c33e6eb0f3e7956f1/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", size = 432416 },
430 | { url = "https://files.pythonhosted.org/packages/a7/e8/85835077b782555d6b3416874b702ea6ebd7db1f145283c9252968670dd5/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", size = 354819 },
431 | { url = "https://files.pythonhosted.org/packages/4f/87/1ac631e923d65cbf36fbcfc6eaa702a169496de1311e54be142f178e53ee/rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", size = 373282 },
432 | { url = "https://files.pythonhosted.org/packages/e4/ce/cb316f7970189e217b998191c7cf0da2ede3d5437932c86a7210dc1e9994/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", size = 541540 },
433 | { url = "https://files.pythonhosted.org/packages/90/d7/4112d7655ec8aff168ecc91d4ceb51c557336edde7e6ccf6463691a2f253/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", size = 547640 },
434 | { url = "https://files.pythonhosted.org/packages/ab/44/4f61d64dfed98cc71623f3a7fcb612df636a208b4b2c6611eaa985e130a9/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", size = 525555 },
435 | { url = "https://files.pythonhosted.org/packages/35/f2/a862d81eacb21f340d584cd1c749c289979f9a60e9229f78bffc0418a199/rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", size = 199338 },
436 | { url = "https://files.pythonhosted.org/packages/cc/ec/77d0674f9af4872919f3738018558dd9d37ad3f7ad792d062eadd4af7cba/rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", size = 213585 },
437 | { url = "https://files.pythonhosted.org/packages/89/b7/f9682c5cc37fcc035f4a0fc33c1fe92ec9cbfdee0cdfd071cf948f53e0df/rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", size = 321468 },
438 | { url = "https://files.pythonhosted.org/packages/b8/ad/fc82be4eaceb8d444cb6fc1956ce972b3a0795104279de05e0e4131d0a47/rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", size = 313062 },
439 | { url = "https://files.pythonhosted.org/packages/0e/1c/6039e80b13a08569a304dc13476dc986352dca4598e909384db043b4e2bb/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", size = 370168 },
440 | { url = "https://files.pythonhosted.org/packages/dc/c9/5b9aa35acfb58946b4b785bc8e700ac313669e02fb100f3efa6176a83e81/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", size = 371376 },
441 | { url = "https://files.pythonhosted.org/packages/7b/dd/0e0dbeb70d8a5357d2814764d467ded98d81d90d3570de4fb05ec7224f6b/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", size = 397200 },
442 | { url = "https://files.pythonhosted.org/packages/e4/da/a47d931eb688ccfd77a7389e45935c79c41e8098d984d87335004baccb1d/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", size = 426824 },
443 | { url = "https://files.pythonhosted.org/packages/0f/f7/a59a673594e6c2ff2dbc44b00fd4ecdec2fc399bb6a7bd82d612699a0121/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", size = 357967 },
444 | { url = "https://files.pythonhosted.org/packages/5f/61/3ba1905396b2cb7088f9503a460b87da33452da54d478cb9241f6ad16d00/rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", size = 378905 },
445 | { url = "https://files.pythonhosted.org/packages/08/31/6d0df9356b4edb0a3a077f1ef714e25ad21f9f5382fc490c2383691885ea/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", size = 546348 },
446 | { url = "https://files.pythonhosted.org/packages/ae/15/d33c021de5cb793101df9961c3c746dfc476953dbbf5db337d8010dffd4e/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", size = 553152 },
447 | { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 },
448 | { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 },
449 | { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 },
450 | { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 },
451 | { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 },
452 | { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 },
453 | { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 },
454 | { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 },
455 | { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 },
456 | { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 },
457 | { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 },
458 | { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 },
459 | { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 },
460 | { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 },
461 | { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 },
462 | { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 },
463 | ]
464 |
465 | [[package]]
466 | name = "ruff"
467 | version = "0.6.9"
468 | source = { registry = "https://pypi.org/simple" }
469 | sdist = { url = "https://files.pythonhosted.org/packages/26/0d/6148a48dab5662ca1d5a93b7c0d13c03abd3cc7e2f35db08410e47cef15d/ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", size = 3095355 }
470 | wheels = [
471 | { url = "https://files.pythonhosted.org/packages/6e/8f/f7a0a0ef1818662efb32ed6df16078c95da7a0a3248d64c2410c1e27799f/ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", size = 10440526 },
472 | { url = "https://files.pythonhosted.org/packages/8b/69/b179a5faf936a9e2ab45bb412a668e4661eded964ccfa19d533f29463ef6/ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", size = 10034612 },
473 | { url = "https://files.pythonhosted.org/packages/c7/ef/fd1b4be979c579d191eeac37b5cfc0ec906de72c8bcd8595e2c81bb700c1/ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", size = 9706197 },
474 | { url = "https://files.pythonhosted.org/packages/29/61/b376d775deb5851cb48d893c568b511a6d3625ef2c129ad5698b64fb523c/ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", size = 10751855 },
475 | { url = "https://files.pythonhosted.org/packages/13/d7/def9e5f446d75b9a9c19b24231a3a658c075d79163b08582e56fa5dcfa38/ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", size = 10200889 },
476 | { url = "https://files.pythonhosted.org/packages/6c/d6/7f34160818bcb6e84ce293a5966cba368d9112ff0289b273fbb689046047/ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", size = 11038678 },
477 | { url = "https://files.pythonhosted.org/packages/13/34/a40ff8ae62fb1b26fb8e6fa7e64bc0e0a834b47317880de22edd6bfb54fb/ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", size = 11808682 },
478 | { url = "https://files.pythonhosted.org/packages/2e/6d/25a4386ae4009fc798bd10ba48c942d1b0b3e459b5403028f1214b6dd161/ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", size = 11330446 },
479 | { url = "https://files.pythonhosted.org/packages/f7/f6/bdf891a9200d692c94ebcd06ae5a2fa5894e522f2c66c2a12dd5d8cb2654/ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", size = 12483048 },
480 | { url = "https://files.pythonhosted.org/packages/a7/86/96f4252f41840e325b3fa6c48297e661abb9f564bd7dcc0572398c8daa42/ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", size = 10936855 },
481 | { url = "https://files.pythonhosted.org/packages/45/87/801a52d26c8dbf73424238e9908b9ceac430d903c8ef35eab1b44fcfa2bd/ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", size = 10713007 },
482 | { url = "https://files.pythonhosted.org/packages/be/27/6f7161d90320a389695e32b6ebdbfbedde28ccbf52451e4b723d7ce744ad/ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", size = 10274594 },
483 | { url = "https://files.pythonhosted.org/packages/00/52/dc311775e7b5f5b19831563cb1572ecce63e62681bccc609867711fae317/ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", size = 10608024 },
484 | { url = "https://files.pythonhosted.org/packages/98/b6/be0a1ddcbac65a30c985cf7224c4fce786ba2c51e7efeb5178fe410ed3cf/ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", size = 10982085 },
485 | { url = "https://files.pythonhosted.org/packages/bb/a4/c84bc13d0b573cf7bb7d17b16d6d29f84267c92d79b2f478d4ce322e8e72/ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d", size = 8522088 },
486 | { url = "https://files.pythonhosted.org/packages/74/be/fc352bd8ca40daae8740b54c1c3e905a7efe470d420a268cd62150248c91/ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", size = 9359275 },
487 | { url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879 },
488 | ]
489 |
490 | [[package]]
491 | name = "sniffio"
492 | version = "1.3.1"
493 | source = { registry = "https://pypi.org/simple" }
494 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
495 | wheels = [
496 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
497 | ]
498 |
499 | [[package]]
500 | name = "starlette"
501 | version = "0.39.2"
502 | source = { registry = "https://pypi.org/simple" }
503 | dependencies = [
504 | { name = "anyio" },
505 | ]
506 | sdist = { url = "https://files.pythonhosted.org/packages/02/0a/62fbd5697f6174041f9b4e2e377b6f383f9189b77dbb7d73d24624caca1d/starlette-0.39.2.tar.gz", hash = "sha256:caaa3b87ef8518ef913dac4f073dea44e85f73343ad2bdc17941931835b2a26a", size = 2573080 }
507 | wheels = [
508 | { url = "https://files.pythonhosted.org/packages/60/f0/04547f776c8845be46df4bdd1f11159c088bd39e916f35d7da1b9f6eb3ef/starlette-0.39.2-py3-none-any.whl", hash = "sha256:134dd6deb655a9775991d352312d53f1879775e5cc8a481f966e83416a2c3f71", size = 73219 },
509 | ]
510 |
511 | [[package]]
512 | name = "tomlkit"
513 | version = "0.13.2"
514 | source = { registry = "https://pypi.org/simple" }
515 | sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 }
516 | wheels = [
517 | { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 },
518 | ]
519 |
520 | [[package]]
521 | name = "typing-extensions"
522 | version = "4.12.2"
523 | source = { registry = "https://pypi.org/simple" }
524 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
525 | wheels = [
526 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
527 | ]
528 |
529 | [[package]]
530 | name = "uvicorn"
531 | version = "0.31.1"
532 | source = { registry = "https://pypi.org/simple" }
533 | dependencies = [
534 | { name = "click" },
535 | { name = "h11" },
536 | ]
537 | sdist = { url = "https://files.pythonhosted.org/packages/76/87/a886eda9ed495a3a4506d5a125cd07c54524280718c4969bde88f075fe98/uvicorn-0.31.1.tar.gz", hash = "sha256:f5167919867b161b7bcaf32646c6a94cdbd4c3aa2eb5c17d36bb9aa5cfd8c493", size = 77368 }
538 | wheels = [
539 | { url = "https://files.pythonhosted.org/packages/3c/55/37407280931038a3f21fa0245d60edeaa76f18419581aa3f4397761c78df/uvicorn-0.31.1-py3-none-any.whl", hash = "sha256:adc42d9cac80cf3e51af97c1851648066841e7cfb6993a4ca8de29ac1548ed41", size = 63666 },
540 | ]
541 |
542 | [[package]]
543 | name = "websockets"
544 | version = "12.0"
545 | source = { registry = "https://pypi.org/simple" }
546 | sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994 }
547 | wheels = [
548 | { url = "https://files.pythonhosted.org/packages/02/73/9c1e168a2e7fdf26841dc98f5f5502e91dea47428da7690a08101f616169/websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", size = 124047 },
549 | { url = "https://files.pythonhosted.org/packages/e4/2d/9a683359ad2ed11b2303a7a94800db19c61d33fa3bde271df09e99936022/websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", size = 121282 },
550 | { url = "https://files.pythonhosted.org/packages/95/aa/75fa3b893142d6d98a48cb461169bd268141f2da8bfca97392d6462a02eb/websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", size = 121325 },
551 | { url = "https://files.pythonhosted.org/packages/6e/a4/51a25e591d645df71ee0dc3a2c880b28e5514c00ce752f98a40a87abcd1e/websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", size = 131502 },
552 | { url = "https://files.pythonhosted.org/packages/cd/ea/0ceeea4f5b87398fe2d9f5bcecfa00a1bcd542e2bfcac2f2e5dd612c4e9e/websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", size = 130491 },
553 | { url = "https://files.pythonhosted.org/packages/e3/05/f52a60b66d9faf07a4f7d71dc056bffafe36a7e98c4eb5b78f04fe6e4e85/websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", size = 130872 },
554 | { url = "https://files.pythonhosted.org/packages/ac/4e/c7361b2d7b964c40fea924d64881145164961fcd6c90b88b7e3ab2c4f431/websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", size = 136318 },
555 | { url = "https://files.pythonhosted.org/packages/0a/31/337bf35ae5faeaf364c9cddec66681cdf51dc4414ee7a20f92a18e57880f/websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", size = 135594 },
556 | { url = "https://files.pythonhosted.org/packages/95/aa/1ac767825c96f9d7e43c4c95683757d4ef28cf11fa47a69aca42428d3e3a/websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", size = 136191 },
557 | { url = "https://files.pythonhosted.org/packages/28/4b/344ec5cfeb6bc417da097f8253607c3aed11d9a305fb58346f506bf556d8/websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", size = 124453 },
558 | { url = "https://files.pythonhosted.org/packages/d1/40/6b169cd1957476374f51f4486a3e85003149e62a14e6b78a958c2222337a/websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", size = 124971 },
559 | { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061 },
560 | { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296 },
561 | { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326 },
562 | { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807 },
563 | { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751 },
564 | { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176 },
565 | { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246 },
566 | { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466 },
567 | { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083 },
568 | { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460 },
569 | { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985 },
570 | { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370 },
571 | ]
572 |
--------------------------------------------------------------------------------