polars-api 0.1.4__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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