polars-api 0.1.4__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,92 @@
1
+ # polars-api
2
+
3
+ [![Release](https://img.shields.io/github/v/release/diegoglozano/polars-api)](https://img.shields.io/github/v/release/diegoglozano/polars-api)
4
+ [![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)
5
+ [![codecov](https://codecov.io/gh/diegoglozano/polars-api/branch/main/graph/badge.svg)](https://codecov.io/gh/diegoglozano/polars-api)
6
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/diegoglozano/polars-api)](https://img.shields.io/github/commit-activity/m/diegoglozano/polars-api)
7
+ [![License](https://img.shields.io/github/license/diegoglozano/polars-api)](https://img.shields.io/github/license/diegoglozano/polars-api)
8
+
9
+ Polars extension for dealing with REST APIs
10
+
11
+ - **Github repository**: <https://github.com/diegoglozano/polars-api/>
12
+ - **Documentation** <https://diegoglozano.github.io/polars-api/>
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ uv add polars-api
18
+ ```
19
+
20
+ ```sh
21
+ poetry add polars-api
22
+ ```
23
+
24
+ ```sh
25
+ pip install polars-api
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ Just import the library as `import polars_api` and the new `api` namespace will be available.
31
+
32
+ In the following example:
33
+
34
+ - We set a base URL using [jsonplaceholder](https://jsonplaceholder.typicode.com/) as a fake REST API
35
+ - For each row, we generate a different body using a `struct` type
36
+ - Finally, we call different methods for getting the data:
37
+ - `.api.get()`: sync GET
38
+ - `.api.aget()`: async GET
39
+ - `.api.post()`: sync POST
40
+ - `.api.apost()`: async POST
41
+
42
+ 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.
43
+
44
+ ```python
45
+ import polars as pl
46
+ import polars_api
47
+
48
+
49
+ BASE_URL = "https://jsonplaceholder.typicode.com/posts"
50
+ df = (
51
+ pl
52
+ .DataFrame({
53
+ "url": [BASE_URL for _ in range(10)],
54
+ })
55
+ .with_columns(
56
+ pl
57
+ .struct(
58
+ title=pl.lit("foo"),
59
+ body=pl.lit("bar"),
60
+ userId=pl.arange(10),
61
+ )
62
+ .alias("body"),
63
+ )
64
+ .with_columns(
65
+ pl
66
+ .col("url")
67
+ .api.get()
68
+ .str.json_decode()
69
+ .alias("get"),
70
+ pl
71
+ .col("url")
72
+ .api.aget()
73
+ .str.json_decode()
74
+ .alias("aget"),
75
+ pl
76
+ .col("url")
77
+ .api.post(body=pl.col("body"))
78
+ .str.json_decode()
79
+ .alias("post"),
80
+ pl
81
+ .col("url")
82
+ .api.apost(body=pl.col("body"))
83
+ .str.json_decode()
84
+ .alias("apost"),
85
+ )
86
+ )
87
+
88
+ ```
89
+
90
+ ---
91
+
92
+ Repository initiated with [fpgmaas/cookiecutter-uv](https://github.com/fpgmaas/cookiecutter-uv).
@@ -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
+ )
@@ -0,0 +1,3 @@
1
+ from .api import Api
2
+
3
+ __all__ = ["Api"]
@@ -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,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,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ examples/test.py
5
+ polars_api/__init__.py
6
+ polars_api/api.py
7
+ polars_api.egg-info/PKG-INFO
8
+ polars_api.egg-info/SOURCES.txt
9
+ polars_api.egg-info/dependency_links.txt
10
+ polars_api.egg-info/requires.txt
11
+ polars_api.egg-info/top_level.txt
12
+ tests/test_api.py
@@ -0,0 +1,2 @@
1
+ httpx>=0.28.1
2
+ polars>=1.19.0
@@ -0,0 +1,5 @@
1
+ dist
2
+ docs
3
+ examples
4
+ polars_api
5
+ tests
@@ -0,0 +1,120 @@
1
+ [project]
2
+ name = "polars-api"
3
+ version = "0.1.4"
4
+ description = "Polars extension for dealing with REST APIs"
5
+ authors = [{ name = "Diego Garcia Lozano", email = "diegoglozano96@gmail.com" }]
6
+ readme = "README.md"
7
+ keywords = ['python']
8
+ requires-python = ">=3.9,<4.0"
9
+ classifiers = [
10
+ "Intended Audience :: Developers",
11
+ "Programming Language :: Python",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.9",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ ]
20
+ dependencies = [
21
+ "httpx>=0.28.1",
22
+ "polars>=1.19.0",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://diegoglozano.github.io/polars-api/"
27
+ Repository = "https://github.com/diegoglozano/polars-api"
28
+ Documentation = "https://diegoglozano.github.io/polars-api/"
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "pytest>=7.2.0",
33
+ "pre-commit>=2.20.0",
34
+ "tox-uv>=1.11.3",
35
+ "deptry>=0.20.0",
36
+ "mypy>=0.991",
37
+ "pytest-cov>=4.0.0",
38
+ "ruff>=0.6.9",
39
+ "mkdocs>=1.4.2",
40
+ "mkdocs-material>=8.5.10",
41
+ "mkdocstrings[python]>=0.26.1",
42
+ ]
43
+
44
+ [build-system]
45
+ requires = ["setuptools >= 61.0"]
46
+ build-backend = "setuptools.build_meta"
47
+
48
+ [tool.setuptools]
49
+ packages = { find = {} }
50
+
51
+ [tool.mypy]
52
+ files = ["polars_api"]
53
+ disallow_untyped_defs = true
54
+ disallow_any_unimported = true
55
+ no_implicit_optional = true
56
+ check_untyped_defs = true
57
+ warn_return_any = true
58
+ warn_unused_ignores = true
59
+ show_error_codes = true
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+
64
+ [tool.ruff]
65
+ target-version = "py39"
66
+ line-length = 120
67
+ fix = true
68
+
69
+ [tool.ruff.lint]
70
+ select = [
71
+ # flake8-2020
72
+ "YTT",
73
+ # flake8-bandit
74
+ "S",
75
+ # flake8-bugbear
76
+ "B",
77
+ # flake8-builtins
78
+ "A",
79
+ # flake8-comprehensions
80
+ "C4",
81
+ # flake8-debugger
82
+ "T10",
83
+ # flake8-simplify
84
+ "SIM",
85
+ # isort
86
+ "I",
87
+ # mccabe
88
+ "C90",
89
+ # pycodestyle
90
+ "E", "W",
91
+ # pyflakes
92
+ "F",
93
+ # pygrep-hooks
94
+ "PGH",
95
+ # pyupgrade
96
+ "UP",
97
+ # ruff
98
+ "RUF",
99
+ # tryceratops
100
+ "TRY",
101
+ ]
102
+ ignore = [
103
+ # LineTooLong
104
+ "E501",
105
+ # DoNotAssignLambda
106
+ "E731",
107
+ ]
108
+
109
+ [tool.ruff.lint.per-file-ignores]
110
+ "tests/*" = ["S101"]
111
+
112
+ [tool.ruff.format]
113
+ preview = true
114
+
115
+ [tool.coverage.report]
116
+ skip_empty = true
117
+
118
+ [tool.coverage.run]
119
+ branch = true
120
+ source = ["polars_api"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ def test_get():
2
+ assert True