polars-api 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
examples/test.py ADDED
@@ -0,0 +1,28 @@
1
+ import polars as pl
2
+
3
+ import polars_api # noqa:F401
4
+
5
+ BASE_URL = "https://jsonplaceholder.typicode.com/posts"
6
+ print(
7
+ pl.DataFrame({
8
+ "url": [BASE_URL for _ in range(10)],
9
+ })
10
+ .with_columns(
11
+ pl.struct(
12
+ userId=3,
13
+ ).alias("params"),
14
+ pl.struct(
15
+ title=pl.lit("foo"),
16
+ body=pl.lit("bar"),
17
+ userId=pl.arange(10),
18
+ ).alias("body"),
19
+ )
20
+ .with_columns(
21
+ pl.col("url").api.get().str.json_decode().alias("get"),
22
+ pl.col("url").api.aget().str.json_decode().alias("aget"),
23
+ pl.col("url").api.get(params=pl.col("params")).str.json_decode().alias("get_params"),
24
+ pl.col("url").api.post(body=pl.col("body")).str.json_decode().alias("post"),
25
+ pl.col("url").api.apost(body=pl.col("body")).str.json_decode().alias("apost"),
26
+ pl.col("url").api.apost(params=pl.col("params")).alias("apost_params"),
27
+ )
28
+ )
polars_api/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .api import Api
2
+
3
+ __all__ = ["Api"]
polars_api/api.py ADDED
@@ -0,0 +1,91 @@
1
+ import asyncio
2
+ from typing import Optional
3
+
4
+ import httpx
5
+ import polars as pl
6
+
7
+
8
+ def check_status_code(status_code):
9
+ return status_code >= 200 and status_code < 300
10
+
11
+
12
+ @pl.api.register_expr_namespace("api")
13
+ class Api:
14
+ def __init__(self, url: pl.Expr) -> None:
15
+ self._url = url
16
+
17
+ @staticmethod
18
+ def _get(url: str, params: Optional[dict[str, str]] = None) -> Optional[str]:
19
+ result = httpx.get(url, params=params)
20
+ if check_status_code(result.status_code):
21
+ return result.text
22
+ else:
23
+ return None
24
+
25
+ @staticmethod
26
+ def _post(url: str, params: dict[str, str], body: str) -> Optional[str]:
27
+ result = httpx.post(url, params=params, json=body)
28
+ if check_status_code(result.status_code):
29
+ return result.text
30
+ else:
31
+ return None
32
+
33
+ @staticmethod
34
+ async def _aget_one(url: str, params: str) -> str:
35
+ async with httpx.AsyncClient() as client:
36
+ r = await client.get(url, params=params)
37
+ return r.text
38
+
39
+ async def _aget_all(self, x, params):
40
+ return await asyncio.gather(*[self._aget_one(url, param) for url, param in zip(x, params)])
41
+
42
+ def _aget(self, x, params):
43
+ return pl.Series(asyncio.run(self._aget_all(x, params)))
44
+
45
+ @staticmethod
46
+ async def _apost_one(url: str, params: str, body: str) -> str:
47
+ async with httpx.AsyncClient() as client:
48
+ r = await client.post(url, params=params, json=body)
49
+ return r.text
50
+
51
+ async def _apost_all(self, x, params, body):
52
+ return await asyncio.gather(*[
53
+ self._apost_one(url, _params, _body) for url, _params, _body in zip(x, params, body)
54
+ ])
55
+
56
+ def _apost(self, x, params, body):
57
+ return pl.Series(asyncio.run(self._apost_all(x, params, body)))
58
+
59
+ def get(self, params: Optional[pl.Expr] = None) -> pl.Expr:
60
+ if params is None:
61
+ params = pl.lit(None)
62
+ return pl.struct(self._url.alias("url"), params.alias("params")).map_elements(
63
+ lambda x: self._get(x["url"], params=x["params"]),
64
+ return_dtype=pl.Utf8,
65
+ )
66
+
67
+ def post(self, params: Optional[pl.Expr] = None, body: Optional[pl.Expr] = None) -> pl.Expr:
68
+ if params is None:
69
+ params = pl.lit(None)
70
+ if body is None:
71
+ body = pl.lit(None)
72
+ return pl.struct(self._url.alias("url"), params.alias("params"), body.alias("body")).map_elements(
73
+ lambda x: self._post(x["url"], params=x["params"], body=x["body"]),
74
+ return_dtype=pl.Utf8,
75
+ )
76
+
77
+ def aget(self, params: Optional[pl.Expr] = None) -> pl.Expr:
78
+ if params is None:
79
+ params = pl.lit(None)
80
+ return pl.struct(self._url.alias("url"), params.alias("params")).map_batches(
81
+ lambda x: self._aget(x.struct.field("url"), params=x.struct.field("params"))
82
+ )
83
+
84
+ def apost(self, params: Optional[pl.Expr] = None, body: Optional[pl.Expr] = None) -> pl.Expr:
85
+ if params is None:
86
+ params = pl.lit(None)
87
+ if body is None:
88
+ body = pl.lit(None)
89
+ return pl.struct(self._url.alias("url"), params.alias("params"), body.alias("body")).map_batches(
90
+ lambda x: self._apost(x.struct.field("url"), params=x.struct.field("params"), body=x.struct.field("body"))
91
+ )
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Diego Garcia Lozano
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.2
2
+ Name: polars-api
3
+ Version: 0.1.4
4
+ Summary: Polars extension for dealing with REST APIs
5
+ Author-email: Diego Garcia Lozano <diegoglozano96@gmail.com>
6
+ Project-URL: Homepage, https://diegoglozano.github.io/polars-api/
7
+ Project-URL: Repository, https://github.com/diegoglozano/polars-api
8
+ Project-URL: Documentation, https://diegoglozano.github.io/polars-api/
9
+ Keywords: python
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: <4.0,>=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: httpx>=0.28.1
23
+ Requires-Dist: polars>=1.19.0
24
+
25
+ # polars-api
26
+
27
+ [![Release](https://img.shields.io/github/v/release/diegoglozano/polars-api)](https://img.shields.io/github/v/release/diegoglozano/polars-api)
28
+ [![Build status](https://img.shields.io/github/actions/workflow/status/diegoglozano/polars-api/main.yml?branch=main)](https://github.com/diegoglozano/polars-api/actions/workflows/main.yml?query=branch%3Amain)
29
+ [![codecov](https://codecov.io/gh/diegoglozano/polars-api/branch/main/graph/badge.svg)](https://codecov.io/gh/diegoglozano/polars-api)
30
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/diegoglozano/polars-api)](https://img.shields.io/github/commit-activity/m/diegoglozano/polars-api)
31
+ [![License](https://img.shields.io/github/license/diegoglozano/polars-api)](https://img.shields.io/github/license/diegoglozano/polars-api)
32
+
33
+ Polars extension for dealing with REST APIs
34
+
35
+ - **Github repository**: <https://github.com/diegoglozano/polars-api/>
36
+ - **Documentation** <https://diegoglozano.github.io/polars-api/>
37
+
38
+ ## Installation
39
+
40
+ ```sh
41
+ uv add polars-api
42
+ ```
43
+
44
+ ```sh
45
+ poetry add polars-api
46
+ ```
47
+
48
+ ```sh
49
+ pip install polars-api
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ Just import the library as `import polars_api` and the new `api` namespace will be available.
55
+
56
+ In the following example:
57
+
58
+ - We set a base URL using [jsonplaceholder](https://jsonplaceholder.typicode.com/) as a fake REST API
59
+ - For each row, we generate a different body using a `struct` type
60
+ - Finally, we call different methods for getting the data:
61
+ - `.api.get()`: sync GET
62
+ - `.api.aget()`: async GET
63
+ - `.api.post()`: sync POST
64
+ - `.api.apost()`: async POST
65
+
66
+ These methods will return the result as a `string`, but with polars you can convert it easily in a struct and access its values using `.str.json_decode()` method.
67
+
68
+ ```python
69
+ import polars as pl
70
+ import polars_api
71
+
72
+
73
+ BASE_URL = "https://jsonplaceholder.typicode.com/posts"
74
+ df = (
75
+ pl
76
+ .DataFrame({
77
+ "url": [BASE_URL for _ in range(10)],
78
+ })
79
+ .with_columns(
80
+ pl
81
+ .struct(
82
+ title=pl.lit("foo"),
83
+ body=pl.lit("bar"),
84
+ userId=pl.arange(10),
85
+ )
86
+ .alias("body"),
87
+ )
88
+ .with_columns(
89
+ pl
90
+ .col("url")
91
+ .api.get()
92
+ .str.json_decode()
93
+ .alias("get"),
94
+ pl
95
+ .col("url")
96
+ .api.aget()
97
+ .str.json_decode()
98
+ .alias("aget"),
99
+ pl
100
+ .col("url")
101
+ .api.post(body=pl.col("body"))
102
+ .str.json_decode()
103
+ .alias("post"),
104
+ pl
105
+ .col("url")
106
+ .api.apost(body=pl.col("body"))
107
+ .str.json_decode()
108
+ .alias("apost"),
109
+ )
110
+ )
111
+
112
+ ```
113
+
114
+ ---
115
+
116
+ Repository initiated with [fpgmaas/cookiecutter-uv](https://github.com/fpgmaas/cookiecutter-uv).
@@ -0,0 +1,9 @@
1
+ examples/test.py,sha256=e-4VmxKdfRJZ01UN_3IAyAdnENTDGSZsrhGzJOtHwh0,927
2
+ polars_api/__init__.py,sha256=JoRS_iy8pZLEz_ADKooXOqOPlUQcd5wvdcSa_PMuGLs,40
3
+ polars_api/api.py,sha256=XjGJu4Hhg_7aN_FmMqeH953sD7V1l1OS2F5JcNKJBaY,3341
4
+ tests/test_api.py,sha256=1OfyzFM1fUecl8TnY9CUkWt_zpUNgoIYnN_Ma9ZN77w,32
5
+ polars_api-0.1.4.dist-info/LICENSE,sha256=Ms_a-6jJtWdrIBCOaYS_hKFCODG2QXHcNgsQJnbujnA,1076
6
+ polars_api-0.1.4.dist-info/METADATA,sha256=8Vra2wJdHNE-h1MgOI8rNTUmT9pFDOD9VyRbyKaFp8U,3703
7
+ polars_api-0.1.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
+ polars_api-0.1.4.dist-info/top_level.txt,sha256=irGgxpseyd3aPqxnvE54AITFumMlSLGg1O9-zg5Gpac,26
9
+ polars_api-0.1.4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ examples
2
+ polars_api
3
+ tests
tests/test_api.py ADDED
@@ -0,0 +1,2 @@
1
+ def test_get():
2
+ assert True