apitestgenie 1.0.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akash Gujarathi
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,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: apitestgenie
3
+ Version: 1.0.0
4
+ Summary: A lightweight, developer-friendly Python library for API test automation
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Akash Gujarathi
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: httpx>=0.27
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=9.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # APItestGenie
36
+
37
+ APItestGenie is a lightweight, developer-friendly Python library designed to simplify API testing for automation engineers.
38
+ Version 1.0 focuses on clarity, correctness, and essential functionality without unnecessary complexity.
39
+
40
+ ---
41
+
42
+ ## Overview
43
+
44
+ APItestGenie provides:
45
+
46
+ - Two ways to perform API requests: Client mode and Simple mode
47
+ - Built-in JSON assertions
48
+ - JSON path assertions for nested responses
49
+ - Basic retry logic with retries, retry delay, and retry_on_status
50
+ - A ResponseWrapper abstraction for consistent behavior across requests
51
+ - GET, POST, PUT, PATCH, DELETE support
52
+ - Timeout and headers support
53
+ - A complete pytest suite
54
+ - A modern Python packaging layout using the src structure
55
+
56
+ Version 1.0 intentionally avoids heavy features like logging frameworks, async support, or automation framework integrations.
57
+
58
+ ---
59
+
60
+ ## Installation
61
+
62
+ Clone the repository:
63
+
64
+ ```
65
+ git clone https://github.com/Akash402/apitestgenie.git
66
+ cd apitestgenie
67
+ ```
68
+
69
+ Create and activate a virtual environment:
70
+
71
+ ```
72
+ python3 -m venv venv
73
+ source venv/bin/activate
74
+ ```
75
+
76
+ Install dependencies:
77
+
78
+ ```
79
+ pip install httpx pytest
80
+ ```
81
+
82
+ (Optional) Install the library locally in editable mode:
83
+
84
+ ```
85
+ pip install -e .
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Usage Examples
91
+
92
+ ### Client Mode
93
+
94
+ ```python
95
+ from apitestgenie.client import ApiClient
96
+
97
+ api = ApiClient("https://jsonplaceholder.typicode.com", timeout=10)
98
+
99
+ response = api.get("/posts/1", retries=2)
100
+ response.assert_status(200)
101
+ response.assert_json_value("id", 1)
102
+ ```
103
+
104
+ POST example:
105
+
106
+ ```python
107
+ resp = api.post("/posts", json={"title": "foo"})
108
+ resp.assert_status(201)
109
+ ```
110
+
111
+ PUT example:
112
+
113
+ ```python
114
+ resp = api.put("/posts/1", json={"id": 1, "title": "updated"})
115
+ resp.assert_status(200)
116
+ ```
117
+
118
+ DELETE example:
119
+
120
+ ```python
121
+ resp = api.delete("/posts/1")
122
+ resp.assert_status(200)
123
+ ```
124
+
125
+ ---
126
+
127
+ ### Simple Mode
128
+
129
+ ```python
130
+ from apitestgenie.simple import get
131
+
132
+ response = get("https://jsonplaceholder.typicode.com/posts/1")
133
+ response.assert_status(200)
134
+ print(response.json())
135
+ ```
136
+
137
+ POST example:
138
+
139
+ ```python
140
+ from apitestgenie.simple import post
141
+
142
+ resp = post("https://jsonplaceholder.typicode.com/posts", json={"hello": "world"})
143
+ resp.assert_status(201)
144
+ ```
145
+
146
+ Retry and timeout example:
147
+
148
+ ```python
149
+ resp = get(
150
+ "https://jsonplaceholder.typicode.com/posts/1",
151
+ retries=3,
152
+ retry_delay=1,
153
+ retry_on_status=[500],
154
+ timeout=5
155
+ )
156
+ ```
157
+
158
+ ---
159
+
160
+ ## JSON Assertions
161
+
162
+ ```python
163
+ resp.assert_status(200)
164
+ resp.assert_json_key("id")
165
+ resp.assert_json_value("id", 1)
166
+ resp.assert_json_path_exists("title")
167
+ resp.assert_json_path_value("id", 1)
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Running Tests
173
+
174
+ ```
175
+ pytest
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Project Structure
181
+
182
+ ```
183
+ apitestgenie/
184
+
185
+ ├── src/
186
+ │ └── apitestgenie/
187
+ │ ├── client.py
188
+ │ ├── simple.py
189
+ │ ├── response_wrapper.py
190
+ │ └── __init__.py
191
+
192
+ ├── tests/
193
+ ├── playground.py
194
+ ├── pytest.ini
195
+ ├── README.md
196
+ └── SCOPE.md
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Version 1.0 Scope Summary
202
+
203
+ Included:
204
+
205
+ - CRUD operations
206
+ - Basic retry logic
207
+ - JSON and JSON path assertions
208
+ - ResponseWrapper abstraction
209
+ - Simple and client modes
210
+ - Header and timeout support
211
+ - Test suite
212
+ - Clean structure
213
+
214
+ Excluded:
215
+
216
+ - Advanced retry logic
217
+ - Logging framework
218
+ - Robot/Behave integrations
219
+ - Async support
220
+ - Schema validation
221
+ - Plugin system
222
+
223
+ ---
224
+
225
+ ## License
226
+
227
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,193 @@
1
+ # APItestGenie
2
+
3
+ APItestGenie is a lightweight, developer-friendly Python library designed to simplify API testing for automation engineers.
4
+ Version 1.0 focuses on clarity, correctness, and essential functionality without unnecessary complexity.
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ APItestGenie provides:
11
+
12
+ - Two ways to perform API requests: Client mode and Simple mode
13
+ - Built-in JSON assertions
14
+ - JSON path assertions for nested responses
15
+ - Basic retry logic with retries, retry delay, and retry_on_status
16
+ - A ResponseWrapper abstraction for consistent behavior across requests
17
+ - GET, POST, PUT, PATCH, DELETE support
18
+ - Timeout and headers support
19
+ - A complete pytest suite
20
+ - A modern Python packaging layout using the src structure
21
+
22
+ Version 1.0 intentionally avoids heavy features like logging frameworks, async support, or automation framework integrations.
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ Clone the repository:
29
+
30
+ ```
31
+ git clone https://github.com/Akash402/apitestgenie.git
32
+ cd apitestgenie
33
+ ```
34
+
35
+ Create and activate a virtual environment:
36
+
37
+ ```
38
+ python3 -m venv venv
39
+ source venv/bin/activate
40
+ ```
41
+
42
+ Install dependencies:
43
+
44
+ ```
45
+ pip install httpx pytest
46
+ ```
47
+
48
+ (Optional) Install the library locally in editable mode:
49
+
50
+ ```
51
+ pip install -e .
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Usage Examples
57
+
58
+ ### Client Mode
59
+
60
+ ```python
61
+ from apitestgenie.client import ApiClient
62
+
63
+ api = ApiClient("https://jsonplaceholder.typicode.com", timeout=10)
64
+
65
+ response = api.get("/posts/1", retries=2)
66
+ response.assert_status(200)
67
+ response.assert_json_value("id", 1)
68
+ ```
69
+
70
+ POST example:
71
+
72
+ ```python
73
+ resp = api.post("/posts", json={"title": "foo"})
74
+ resp.assert_status(201)
75
+ ```
76
+
77
+ PUT example:
78
+
79
+ ```python
80
+ resp = api.put("/posts/1", json={"id": 1, "title": "updated"})
81
+ resp.assert_status(200)
82
+ ```
83
+
84
+ DELETE example:
85
+
86
+ ```python
87
+ resp = api.delete("/posts/1")
88
+ resp.assert_status(200)
89
+ ```
90
+
91
+ ---
92
+
93
+ ### Simple Mode
94
+
95
+ ```python
96
+ from apitestgenie.simple import get
97
+
98
+ response = get("https://jsonplaceholder.typicode.com/posts/1")
99
+ response.assert_status(200)
100
+ print(response.json())
101
+ ```
102
+
103
+ POST example:
104
+
105
+ ```python
106
+ from apitestgenie.simple import post
107
+
108
+ resp = post("https://jsonplaceholder.typicode.com/posts", json={"hello": "world"})
109
+ resp.assert_status(201)
110
+ ```
111
+
112
+ Retry and timeout example:
113
+
114
+ ```python
115
+ resp = get(
116
+ "https://jsonplaceholder.typicode.com/posts/1",
117
+ retries=3,
118
+ retry_delay=1,
119
+ retry_on_status=[500],
120
+ timeout=5
121
+ )
122
+ ```
123
+
124
+ ---
125
+
126
+ ## JSON Assertions
127
+
128
+ ```python
129
+ resp.assert_status(200)
130
+ resp.assert_json_key("id")
131
+ resp.assert_json_value("id", 1)
132
+ resp.assert_json_path_exists("title")
133
+ resp.assert_json_path_value("id", 1)
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Running Tests
139
+
140
+ ```
141
+ pytest
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Project Structure
147
+
148
+ ```
149
+ apitestgenie/
150
+
151
+ ├── src/
152
+ │ └── apitestgenie/
153
+ │ ├── client.py
154
+ │ ├── simple.py
155
+ │ ├── response_wrapper.py
156
+ │ └── __init__.py
157
+
158
+ ├── tests/
159
+ ├── playground.py
160
+ ├── pytest.ini
161
+ ├── README.md
162
+ └── SCOPE.md
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Version 1.0 Scope Summary
168
+
169
+ Included:
170
+
171
+ - CRUD operations
172
+ - Basic retry logic
173
+ - JSON and JSON path assertions
174
+ - ResponseWrapper abstraction
175
+ - Simple and client modes
176
+ - Header and timeout support
177
+ - Test suite
178
+ - Clean structure
179
+
180
+ Excluded:
181
+
182
+ - Advanced retry logic
183
+ - Logging framework
184
+ - Robot/Behave integrations
185
+ - Async support
186
+ - Schema validation
187
+ - Plugin system
188
+
189
+ ---
190
+
191
+ ## License
192
+
193
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "apitestgenie"
7
+ version = "1.0.0"
8
+ description = "A lightweight, developer-friendly Python library for API test automation"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.11"
12
+ dependencies = [
13
+ "httpx>=0.27",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = [
18
+ "pytest>=9.0",
19
+ ]
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
23
+
24
+ [tool.pytest.ini_options]
25
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .client import ApiClient
2
+ from .simple import get, post, put, patch, delete
3
+
4
+ __all__ = ["ApiClient", "get", "post", "put", "patch", "delete"]
@@ -0,0 +1,73 @@
1
+ import time
2
+ import httpx
3
+ from .response_wrapper import ResponseWrapper
4
+
5
+
6
+ class ApiClient:
7
+ def __init__(self, base_url, headers=None, timeout=30):
8
+ self.base_url = base_url.rstrip("/")
9
+ self.session = httpx.Client(headers=headers, timeout=timeout)
10
+
11
+ def _build_url(self, path):
12
+ return f"{self.base_url}/{path.lstrip('/')}"
13
+
14
+ def _request_with_retry(self, method, url, retries, retry_delay, retry_on_status, **kwargs):
15
+ attempt = 0
16
+
17
+ while True:
18
+ try:
19
+ response = self.session.request(method, url, **kwargs)
20
+
21
+ # If no retry rules → return immediately
22
+ if not retry_on_status:
23
+ return ResponseWrapper(response)
24
+
25
+ # If status code NOT in retry list → return
26
+ if response.status_code not in retry_on_status:
27
+ return ResponseWrapper(response)
28
+
29
+ # If retry limit exceeded → return last response
30
+ attempt += 1
31
+ if attempt > retries:
32
+ return ResponseWrapper(response)
33
+
34
+ time.sleep(retry_delay)
35
+
36
+ except httpx.RequestError:
37
+ attempt += 1
38
+ if attempt > retries:
39
+ raise
40
+ time.sleep(retry_delay)
41
+
42
+ # ------------------------
43
+ # HTTP METHODS
44
+ # ------------------------
45
+
46
+ def get(self, path, retries=0, retry_delay=0, retry_on_status=None, **kwargs):
47
+ url = self._build_url(path)
48
+ return self._request_with_retry("GET", url, retries, retry_delay, retry_on_status, **kwargs)
49
+
50
+ def post(self, path, json=None, retries=0, retry_delay=0, retry_on_status=None, **kwargs):
51
+ url = self._build_url(path)
52
+ return self._request_with_retry("POST", url, retries, retry_delay, retry_on_status, json=json, **kwargs)
53
+
54
+ def put(self, path, json=None, retries=0, retry_delay=0, retry_on_status=None, **kwargs):
55
+ url = self._build_url(path)
56
+ return self._request_with_retry("PUT", url, retries, retry_delay, retry_on_status, json=json, **kwargs)
57
+
58
+ def patch(self, path, json=None, retries=0, retry_delay=0, retry_on_status=None, **kwargs):
59
+ url = self._build_url(path)
60
+ return self._request_with_retry("PATCH", url, retries, retry_delay, retry_on_status, json=json, **kwargs)
61
+
62
+ def delete(self, path, retries=0, retry_delay=0, retry_on_status=None, **kwargs):
63
+ url = self._build_url(path)
64
+ return self._request_with_retry("DELETE", url, retries, retry_delay, retry_on_status, **kwargs)
65
+
66
+ def close(self):
67
+ self.session.close()
68
+
69
+ def __enter__(self):
70
+ return self
71
+
72
+ def __exit__(self, *args):
73
+ self.close()
@@ -0,0 +1,68 @@
1
+ class ResponseWrapper:
2
+ def __init__(self, response):
3
+ self.response = response
4
+
5
+ @property
6
+ def status_code(self):
7
+ return self.response.status_code
8
+
9
+ def json(self):
10
+ return self.response.json()
11
+
12
+ def assert_status(self, expected):
13
+ actual = self.status_code
14
+ if actual != expected:
15
+ raise AssertionError(f"Expected {expected}, got {actual}")
16
+ return self
17
+
18
+ def assert_json_key(self, key: str):
19
+ data = self.json()
20
+ if not isinstance(data, dict):
21
+ raise AssertionError(
22
+ f"Expected a JSON object to check key '{key}', but got {type(data).__name__}"
23
+ )
24
+ if key not in data:
25
+ raise AssertionError(f"Expected key '{key}' not found in JSON response")
26
+ return self
27
+
28
+ def assert_json_value(self, key: str, expected_value):
29
+ data = self.json()
30
+ if not isinstance(data, dict):
31
+ raise AssertionError(
32
+ f"Expected a JSON object to check key '{key}', but got {type(data).__name__}"
33
+ )
34
+ if key not in data:
35
+ raise AssertionError(f"Key '{key}' not found in JSON response")
36
+
37
+ actual_value = data[key]
38
+ if actual_value != expected_value:
39
+ raise AssertionError(
40
+ f"Expected '{key}' to be '{expected_value}', got '{actual_value}'"
41
+ )
42
+ return self
43
+
44
+ def _resolve_json_path(self, path):
45
+ """Resolve a dotted path like 'user.address.city'."""
46
+ parts = path.split(".")
47
+ current = self.json()
48
+
49
+ try:
50
+ for p in parts:
51
+ if isinstance(current, list):
52
+ p = int(p) # list index
53
+ current = current[p]
54
+ return current
55
+ except Exception as e:
56
+ raise AssertionError(f"JSON path '{path}' not found: {e}")
57
+
58
+ def assert_json_path_exists(self, path):
59
+ _ = self._resolve_json_path(path)
60
+ return self
61
+
62
+ def assert_json_path_value(self, path, expected):
63
+ actual = self._resolve_json_path(path)
64
+ if actual != expected:
65
+ raise AssertionError(
66
+ f"JSON path '{path}' expected '{expected}', got '{actual}'"
67
+ )
68
+ return self
@@ -0,0 +1,145 @@
1
+ import time
2
+ import httpx
3
+ from .response_wrapper import ResponseWrapper
4
+
5
+ def _request_with_retry(
6
+ method,
7
+ url,
8
+ retries=0,
9
+ retry_delay=0,
10
+ retry_on_status=None,
11
+ timeout=None,
12
+ **kwargs
13
+ ):
14
+ attempt = 0
15
+
16
+ while True:
17
+ try:
18
+ response = httpx.request(
19
+ method,
20
+ url,
21
+ timeout=timeout,
22
+ **kwargs
23
+ )
24
+
25
+ # If no retry rules → return immediately
26
+ if not retry_on_status:
27
+ return ResponseWrapper(response)
28
+
29
+ # If status code NOT in retry list → return
30
+ if response.status_code not in retry_on_status:
31
+ return ResponseWrapper(response)
32
+
33
+ # If retry limit exceeded → return last response
34
+ attempt += 1
35
+ if attempt > retries:
36
+ return ResponseWrapper(response)
37
+
38
+ # Otherwise retry
39
+ time.sleep(retry_delay)
40
+
41
+ except httpx.RequestError:
42
+ attempt += 1
43
+ if attempt > retries:
44
+ raise
45
+ time.sleep(retry_delay)
46
+
47
+
48
+ # --------------------------------------------------------
49
+ # SIMPLE MODE HTTP METHODS
50
+ # --------------------------------------------------------
51
+
52
+ def get(
53
+ url,
54
+ retries=0,
55
+ retry_delay=0,
56
+ retry_on_status=None,
57
+ timeout=None,
58
+ **kwargs
59
+ ):
60
+ return _request_with_retry(
61
+ "GET", url,
62
+ retries=retries,
63
+ retry_delay=retry_delay,
64
+ retry_on_status=retry_on_status,
65
+ timeout=timeout,
66
+ **kwargs
67
+ )
68
+
69
+
70
+ def post(
71
+ url,
72
+ json=None,
73
+ retries=0,
74
+ retry_delay=0,
75
+ retry_on_status=None,
76
+ timeout=None,
77
+ **kwargs
78
+ ):
79
+ return _request_with_retry(
80
+ "POST", url,
81
+ retries=retries,
82
+ retry_delay=retry_delay,
83
+ retry_on_status=retry_on_status,
84
+ timeout=timeout,
85
+ json=json,
86
+ **kwargs
87
+ )
88
+
89
+
90
+ def put(
91
+ url,
92
+ json=None,
93
+ retries=0,
94
+ retry_delay=0,
95
+ retry_on_status=None,
96
+ timeout=None,
97
+ **kwargs
98
+ ):
99
+ return _request_with_retry(
100
+ "PUT", url,
101
+ retries=retries,
102
+ retry_delay=retry_delay,
103
+ retry_on_status=retry_on_status,
104
+ timeout=timeout,
105
+ json=json,
106
+ **kwargs
107
+ )
108
+
109
+
110
+ def patch(
111
+ url,
112
+ json=None,
113
+ retries=0,
114
+ retry_delay=0,
115
+ retry_on_status=None,
116
+ timeout=None,
117
+ **kwargs
118
+ ):
119
+ return _request_with_retry(
120
+ "PATCH", url,
121
+ retries=retries,
122
+ retry_delay=retry_delay,
123
+ retry_on_status=retry_on_status,
124
+ timeout=timeout,
125
+ json=json,
126
+ **kwargs
127
+ )
128
+
129
+
130
+ def delete(
131
+ url,
132
+ retries=0,
133
+ retry_delay=0,
134
+ retry_on_status=None,
135
+ timeout=None,
136
+ **kwargs
137
+ ):
138
+ return _request_with_retry(
139
+ "DELETE", url,
140
+ retries=retries,
141
+ retry_delay=retry_delay,
142
+ retry_on_status=retry_on_status,
143
+ timeout=timeout,
144
+ **kwargs
145
+ )
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: apitestgenie
3
+ Version: 1.0.0
4
+ Summary: A lightweight, developer-friendly Python library for API test automation
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Akash Gujarathi
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: httpx>=0.27
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=9.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # APItestGenie
36
+
37
+ APItestGenie is a lightweight, developer-friendly Python library designed to simplify API testing for automation engineers.
38
+ Version 1.0 focuses on clarity, correctness, and essential functionality without unnecessary complexity.
39
+
40
+ ---
41
+
42
+ ## Overview
43
+
44
+ APItestGenie provides:
45
+
46
+ - Two ways to perform API requests: Client mode and Simple mode
47
+ - Built-in JSON assertions
48
+ - JSON path assertions for nested responses
49
+ - Basic retry logic with retries, retry delay, and retry_on_status
50
+ - A ResponseWrapper abstraction for consistent behavior across requests
51
+ - GET, POST, PUT, PATCH, DELETE support
52
+ - Timeout and headers support
53
+ - A complete pytest suite
54
+ - A modern Python packaging layout using the src structure
55
+
56
+ Version 1.0 intentionally avoids heavy features like logging frameworks, async support, or automation framework integrations.
57
+
58
+ ---
59
+
60
+ ## Installation
61
+
62
+ Clone the repository:
63
+
64
+ ```
65
+ git clone https://github.com/Akash402/apitestgenie.git
66
+ cd apitestgenie
67
+ ```
68
+
69
+ Create and activate a virtual environment:
70
+
71
+ ```
72
+ python3 -m venv venv
73
+ source venv/bin/activate
74
+ ```
75
+
76
+ Install dependencies:
77
+
78
+ ```
79
+ pip install httpx pytest
80
+ ```
81
+
82
+ (Optional) Install the library locally in editable mode:
83
+
84
+ ```
85
+ pip install -e .
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Usage Examples
91
+
92
+ ### Client Mode
93
+
94
+ ```python
95
+ from apitestgenie.client import ApiClient
96
+
97
+ api = ApiClient("https://jsonplaceholder.typicode.com", timeout=10)
98
+
99
+ response = api.get("/posts/1", retries=2)
100
+ response.assert_status(200)
101
+ response.assert_json_value("id", 1)
102
+ ```
103
+
104
+ POST example:
105
+
106
+ ```python
107
+ resp = api.post("/posts", json={"title": "foo"})
108
+ resp.assert_status(201)
109
+ ```
110
+
111
+ PUT example:
112
+
113
+ ```python
114
+ resp = api.put("/posts/1", json={"id": 1, "title": "updated"})
115
+ resp.assert_status(200)
116
+ ```
117
+
118
+ DELETE example:
119
+
120
+ ```python
121
+ resp = api.delete("/posts/1")
122
+ resp.assert_status(200)
123
+ ```
124
+
125
+ ---
126
+
127
+ ### Simple Mode
128
+
129
+ ```python
130
+ from apitestgenie.simple import get
131
+
132
+ response = get("https://jsonplaceholder.typicode.com/posts/1")
133
+ response.assert_status(200)
134
+ print(response.json())
135
+ ```
136
+
137
+ POST example:
138
+
139
+ ```python
140
+ from apitestgenie.simple import post
141
+
142
+ resp = post("https://jsonplaceholder.typicode.com/posts", json={"hello": "world"})
143
+ resp.assert_status(201)
144
+ ```
145
+
146
+ Retry and timeout example:
147
+
148
+ ```python
149
+ resp = get(
150
+ "https://jsonplaceholder.typicode.com/posts/1",
151
+ retries=3,
152
+ retry_delay=1,
153
+ retry_on_status=[500],
154
+ timeout=5
155
+ )
156
+ ```
157
+
158
+ ---
159
+
160
+ ## JSON Assertions
161
+
162
+ ```python
163
+ resp.assert_status(200)
164
+ resp.assert_json_key("id")
165
+ resp.assert_json_value("id", 1)
166
+ resp.assert_json_path_exists("title")
167
+ resp.assert_json_path_value("id", 1)
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Running Tests
173
+
174
+ ```
175
+ pytest
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Project Structure
181
+
182
+ ```
183
+ apitestgenie/
184
+
185
+ ├── src/
186
+ │ └── apitestgenie/
187
+ │ ├── client.py
188
+ │ ├── simple.py
189
+ │ ├── response_wrapper.py
190
+ │ └── __init__.py
191
+
192
+ ├── tests/
193
+ ├── playground.py
194
+ ├── pytest.ini
195
+ ├── README.md
196
+ └── SCOPE.md
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Version 1.0 Scope Summary
202
+
203
+ Included:
204
+
205
+ - CRUD operations
206
+ - Basic retry logic
207
+ - JSON and JSON path assertions
208
+ - ResponseWrapper abstraction
209
+ - Simple and client modes
210
+ - Header and timeout support
211
+ - Test suite
212
+ - Clean structure
213
+
214
+ Excluded:
215
+
216
+ - Advanced retry logic
217
+ - Logging framework
218
+ - Robot/Behave integrations
219
+ - Async support
220
+ - Schema validation
221
+ - Plugin system
222
+
223
+ ---
224
+
225
+ ## License
226
+
227
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/apitestgenie/__init__.py
5
+ src/apitestgenie/client.py
6
+ src/apitestgenie/response_wrapper.py
7
+ src/apitestgenie/simple.py
8
+ src/apitestgenie.egg-info/PKG-INFO
9
+ src/apitestgenie.egg-info/SOURCES.txt
10
+ src/apitestgenie.egg-info/dependency_links.txt
11
+ src/apitestgenie.egg-info/requires.txt
12
+ src/apitestgenie.egg-info/top_level.txt
13
+ tests/test_client.py
@@ -0,0 +1,4 @@
1
+ httpx>=0.27
2
+
3
+ [dev]
4
+ pytest>=9.0
@@ -0,0 +1 @@
1
+ apitestgenie
@@ -0,0 +1,183 @@
1
+ import httpx
2
+ import pytest
3
+ from src.apitestgenie.client import ApiClient
4
+ from src.apitestgenie.simple import get, post, put, patch, delete
5
+ from src.apitestgenie.response_wrapper import ResponseWrapper
6
+
7
+ BASE_URL = "https://jsonplaceholder.typicode.com"
8
+
9
+ # ---------------------------------------------------------
10
+ # GET TESTS
11
+ # ---------------------------------------------------------
12
+
13
+ def test_client_get():
14
+ api = ApiClient(BASE_URL)
15
+ resp = api.get("/posts/1")
16
+ resp.assert_status(200)
17
+ resp.assert_json_key("id")
18
+ resp.assert_json_value("id", 1)
19
+
20
+ def test_simple_get():
21
+ resp = get(f"{BASE_URL}/posts/1")
22
+ resp.assert_status(200)
23
+ resp.assert_json_key("id")
24
+ resp.assert_json_value("id", 1)
25
+
26
+ # ---------------------------------------------------------
27
+ # POST TESTS
28
+ # ---------------------------------------------------------
29
+
30
+ def test_client_post():
31
+ api = ApiClient(BASE_URL)
32
+ resp = api.post("/posts", json={"title": "foo", "body": "bar", "userId": 1})
33
+ resp.assert_status(201)
34
+ resp.assert_json_key("id")
35
+
36
+ def test_simple_post():
37
+ resp = post(f"{BASE_URL}/posts", json={"title": "foo"})
38
+ resp.assert_status(201)
39
+ resp.assert_json_key("id")
40
+
41
+
42
+ # ---------------------------------------------------------
43
+ # PUT TESTS
44
+ # ---------------------------------------------------------
45
+
46
+ def test_client_put():
47
+ api = ApiClient(BASE_URL)
48
+ resp = api.put("/posts/1", json={"id": 1, "title": "updated"})
49
+ resp.assert_status(200)
50
+ resp.assert_json_key("id")
51
+ resp.assert_json_value("id", 1)
52
+
53
+ def test_simple_put():
54
+ resp = put(f"{BASE_URL}/posts/1", json={"id": 1, "title": "updated"})
55
+ resp.assert_status(200)
56
+ resp.assert_json_key("id")
57
+ resp.assert_json_value("id", 1)
58
+
59
+ # ---------------------------------------------------------
60
+ # PATCH TESTS
61
+ # ---------------------------------------------------------
62
+
63
+ def test_client_patch():
64
+ api = ApiClient(BASE_URL)
65
+ resp = api.patch("/posts/1", json={"title": "patched"})
66
+ resp.assert_status(200)
67
+ resp.assert_json_key("id")
68
+ resp.assert_json_value("id", 1)
69
+
70
+ def test_simple_patch():
71
+ resp = patch(f"{BASE_URL}/posts/1", json={"title": "patched"})
72
+ resp.assert_status(200)
73
+ resp.assert_json_key("id")
74
+ resp.assert_json_value("id", 1)
75
+
76
+ # ---------------------------------------------------------
77
+ # DELETE TESTS
78
+ # ---------------------------------------------------------
79
+
80
+ def test_client_delete():
81
+ api = ApiClient(BASE_URL)
82
+ resp = api.delete("/posts/1")
83
+ resp.assert_status(200)
84
+
85
+ def test_simple_delete():
86
+ resp = delete(f"{BASE_URL}/posts/1")
87
+ resp.assert_status(200)
88
+
89
+ # ---------------------------------------------------------
90
+ # SIMPLE MODE TIMEOUT TESTS
91
+ # ---------------------------------------------------------
92
+
93
+ def test_simple_mode_timeout():
94
+ resp = get(f"{BASE_URL}/posts/1", timeout=5)
95
+ resp.assert_status(200)
96
+
97
+
98
+ # ---------------------------------------------------------
99
+ # OFFLINE UNIT TESTS (no network required)
100
+ # ---------------------------------------------------------
101
+
102
+ def _make_mock_response(status_code, json_body):
103
+ """Build a ResponseWrapper from a mock httpx response."""
104
+ import json as _json
105
+ body = _json.dumps(json_body).encode()
106
+ mock = httpx.Response(status_code, content=body, headers={"content-type": "application/json"})
107
+ return ResponseWrapper(mock)
108
+
109
+
110
+ def test_assert_status_passes():
111
+ resp = _make_mock_response(200, {"id": 1})
112
+ resp.assert_status(200)
113
+
114
+
115
+ def test_assert_status_fails():
116
+ resp = _make_mock_response(404, {"error": "not found"})
117
+ with pytest.raises(AssertionError, match="Expected 200, got 404"):
118
+ resp.assert_status(200)
119
+
120
+
121
+ def test_assert_json_key_passes():
122
+ resp = _make_mock_response(200, {"id": 1, "title": "foo"})
123
+ resp.assert_json_key("id")
124
+ resp.assert_json_key("title")
125
+
126
+
127
+ def test_assert_json_key_missing():
128
+ resp = _make_mock_response(200, {"id": 1})
129
+ with pytest.raises(AssertionError, match="not found"):
130
+ resp.assert_json_key("missing")
131
+
132
+
133
+ def test_assert_json_key_on_list_response():
134
+ resp = _make_mock_response(200, [{"id": 1}, {"id": 2}])
135
+ with pytest.raises(AssertionError, match="Expected a JSON object"):
136
+ resp.assert_json_key("id")
137
+
138
+
139
+ def test_assert_json_value_passes():
140
+ resp = _make_mock_response(200, {"id": 42})
141
+ resp.assert_json_value("id", 42)
142
+
143
+
144
+ def test_assert_json_value_fails():
145
+ resp = _make_mock_response(200, {"id": 42})
146
+ with pytest.raises(AssertionError, match="Expected 'id' to be '1', got '42'"):
147
+ resp.assert_json_value("id", 1)
148
+
149
+
150
+ def test_assert_json_path_exists():
151
+ resp = _make_mock_response(200, {"user": {"address": {"city": "London"}}})
152
+ resp.assert_json_path_exists("user.address.city")
153
+
154
+
155
+ def test_assert_json_path_value():
156
+ resp = _make_mock_response(200, {"user": {"address": {"city": "London"}}})
157
+ resp.assert_json_path_value("user.address.city", "London")
158
+
159
+
160
+ def test_assert_json_path_missing():
161
+ resp = _make_mock_response(200, {"user": {}})
162
+ with pytest.raises(AssertionError, match="not found"):
163
+ resp.assert_json_path_exists("user.address.city")
164
+
165
+
166
+ def test_chained_assertions():
167
+ resp = _make_mock_response(200, {"id": 1, "title": "foo"})
168
+ resp.assert_status(200).assert_json_key("id").assert_json_value("id", 1)
169
+
170
+
171
+ def test_api_client_context_manager():
172
+ with ApiClient("https://example.com") as api:
173
+ assert api is not None
174
+
175
+
176
+ def test_response_wrapper_status_code():
177
+ resp = _make_mock_response(201, {})
178
+ assert resp.status_code == 201
179
+
180
+
181
+ def test_response_wrapper_json():
182
+ resp = _make_mock_response(200, {"key": "value"})
183
+ assert resp.json() == {"key": "value"}