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 +28 -0
- polars_api/__init__.py +3 -0
- polars_api/api.py +91 -0
- polars_api-0.1.4.dist-info/LICENSE +21 -0
- polars_api-0.1.4.dist-info/METADATA +116 -0
- polars_api-0.1.4.dist-info/RECORD +9 -0
- polars_api-0.1.4.dist-info/WHEEL +5 -0
- polars_api-0.1.4.dist-info/top_level.txt +3 -0
- tests/test_api.py +2 -0
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
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
|
+
[](https://img.shields.io/github/v/release/diegoglozano/polars-api)
|
28
|
+
[](https://github.com/diegoglozano/polars-api/actions/workflows/main.yml?query=branch%3Amain)
|
29
|
+
[](https://codecov.io/gh/diegoglozano/polars-api)
|
30
|
+
[](https://img.shields.io/github/commit-activity/m/diegoglozano/polars-api)
|
31
|
+
[](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,,
|
tests/test_api.py
ADDED