robotframework-parallel-requests 0.1.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.
Files changed (27) hide show
  1. robotframework_parallel_requests-0.1.0/LICENSE +21 -0
  2. robotframework_parallel_requests-0.1.0/PKG-INFO +524 -0
  3. robotframework_parallel_requests-0.1.0/README.md +490 -0
  4. robotframework_parallel_requests-0.1.0/pyproject.toml +51 -0
  5. robotframework_parallel_requests-0.1.0/robot_parallel_requests/__init__.py +24 -0
  6. robotframework_parallel_requests-0.1.0/robot_parallel_requests/library.py +513 -0
  7. robotframework_parallel_requests-0.1.0/robot_parallel_requests/metrics.py +95 -0
  8. robotframework_parallel_requests-0.1.0/robot_parallel_requests/rate_limiter.py +76 -0
  9. robotframework_parallel_requests-0.1.0/robot_parallel_requests/response_store.py +33 -0
  10. robotframework_parallel_requests-0.1.0/robot_parallel_requests/retry.py +106 -0
  11. robotframework_parallel_requests-0.1.0/robot_parallel_requests/session.py +27 -0
  12. robotframework_parallel_requests-0.1.0/robot_parallel_requests/tasks.py +18 -0
  13. robotframework_parallel_requests-0.1.0/robot_parallel_requests/transport/__init__.py +3 -0
  14. robotframework_parallel_requests-0.1.0/robot_parallel_requests/transport/base.py +9 -0
  15. robotframework_parallel_requests-0.1.0/robot_parallel_requests/transport/httpx_async_future.py +82 -0
  16. robotframework_parallel_requests-0.1.0/robot_parallel_requests/transport/httpx_sync.py +53 -0
  17. robotframework_parallel_requests-0.1.0/robot_parallel_requests/worker.py +136 -0
  18. robotframework_parallel_requests-0.1.0/robotframework_parallel_requests.egg-info/PKG-INFO +524 -0
  19. robotframework_parallel_requests-0.1.0/robotframework_parallel_requests.egg-info/SOURCES.txt +25 -0
  20. robotframework_parallel_requests-0.1.0/robotframework_parallel_requests.egg-info/dependency_links.txt +1 -0
  21. robotframework_parallel_requests-0.1.0/robotframework_parallel_requests.egg-info/requires.txt +12 -0
  22. robotframework_parallel_requests-0.1.0/robotframework_parallel_requests.egg-info/top_level.txt +1 -0
  23. robotframework_parallel_requests-0.1.0/setup.cfg +4 -0
  24. robotframework_parallel_requests-0.1.0/setup.py +9 -0
  25. robotframework_parallel_requests-0.1.0/tests/test_advanced.py +411 -0
  26. robotframework_parallel_requests-0.1.0/tests/test_core.py +116 -0
  27. robotframework_parallel_requests-0.1.0/tests/test_optimizations.py +180 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tallin Carlson
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,524 @@
1
+ Metadata-Version: 2.4
2
+ Name: robotframework-parallel-requests
3
+ Version: 0.1.0
4
+ Summary: Parallelized HTTP Request library for Robot Framework (httpx + ThreadPool MVP)
5
+ Author: Tallin Carlson
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/tallin32/robotframework-parallel-requests
8
+ Project-URL: Repository, https://github.com/tallin32/robotframework-parallel-requests
9
+ Project-URL: Issues, https://github.com/tallin32/robotframework-parallel-requests/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: Robot Framework
12
+ Classifier: Framework :: Robot Framework :: Library
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: httpx>=0.23.0
24
+ Requires-Dist: robotframework>=4.0
25
+ Provides-Extra: http2
26
+ Requires-Dist: h2>=3.0.0; extra == "http2"
27
+ Provides-Extra: dev
28
+ Requires-Dist: respx>=0.20.0; extra == "dev"
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: build; extra == "dev"
31
+ Requires-Dist: twine; extra == "dev"
32
+ Requires-Dist: h2>=3.0.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # robotframework-parallel-requests
36
+
37
+ [![PyPI version](https://badge.fury.io/py/robotframework-parallel-requests.svg)](https://badge.fury.io/py/robotframework-parallel-requests)
38
+ [![CI Tests](https://github.com/tallin32/robotframework-parallel-requests/actions/workflows/tests.yml/badge.svg)](https://github.com/tallin32/robotframework-parallel-requests/actions/workflows/tests.yml)
39
+ [![Docs](https://github.com/tallin32/robotframework-parallel-requests/actions/workflows/pages.yml/badge.svg)](https://tallin32.github.io/robotframework-parallel-requests/)
40
+ [![Python Version](https://img.shields.io/pypi/pyversions/robotframework-parallel-requests.svg)](https://pypi.org/project/robotframework-parallel-requests/)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
42
+
43
+ A Robot Framework library for parallelized HTTP requests using `httpx` and ThreadPool.
44
+
45
+ **Queue multiple HTTP requests and run them in parallel**, then retrieve responses by ID or await all responses. Designed for testing scenarios like rate limiting, bulk API operations, and performance validation.
46
+
47
+ ## Status
48
+
49
+ ⚠️ **Beta** — Core functionality stable; API may evolve.
50
+
51
+ Current production scope uses synchronous `httpx` transport with ThreadPool concurrency
52
+ (connection pool sized to worker count; optional HTTP/2). Native async transport is planned
53
+ for a later release.
54
+
55
+ ## Features
56
+
57
+ - **Parallelized requests**: Queue up multiple HTTP requests and execute them concurrently using ThreadPoolExecutor.
58
+ - **RequestsLibrary-like API**: Keywords mirror RequestsLibrary for familiarity (with `Parallel ` prefix).
59
+ - **Direct response access**: Retrieve the underlying `httpx.Response` object for advanced assertions.
60
+ - **Simple session management**: Create named sessions with base URLs and default headers.
61
+
62
+ ## Table of Contents
63
+
64
+ - [Installation](#installation)
65
+ - [Quick Start](#quick-start)
66
+ - [Comparison with RequestsLibrary](#comparison-with-requestslibrary)
67
+ - [Keywords Reference](#keywords)
68
+ - [Library Initialization](#library-initialization)
69
+ - [Use Cases](#use-cases)
70
+ - [Error Handling](#error-handling)
71
+ - [Best Practices](#best-practices)
72
+ - [Architecture](#architecture)
73
+ - [Testing](#testing)
74
+ - [Future Enhancements](#future-enhancements)
75
+ - [Contributing](#contributing)
76
+ - [Release Process](#release-process)
77
+ - [License](#license)
78
+
79
+ ## Installation
80
+
81
+ **From PyPI:**
82
+ ```bash
83
+ pip install robotframework-parallel-requests
84
+ ```
85
+
86
+ **Optional HTTP/2 support:**
87
+ ```bash
88
+ pip install robotframework-parallel-requests[http2]
89
+ ```
90
+
91
+ **Development/Local Installation:**
92
+ ```bash
93
+ pip install -r requirements.txt
94
+ pip install -e .
95
+ ```
96
+
97
+ **Prerequisites:**
98
+ - Python 3.8+
99
+ - Robot Framework 4.0+
100
+
101
+ **Dependencies:**
102
+ - `httpx>=0.23.0` - HTTP client
103
+ - `robotframework>=4.0` - Robot Framework core
104
+ - `pytest>=7.0` - Testing (dev only)
105
+ - `respx>=0.20.0` - httpx mocking (dev only)
106
+
107
+ ## Quick Start
108
+
109
+ ### Basic Usage
110
+
111
+ ```robot
112
+ *** Settings ***
113
+ Library robot_parallel_requests
114
+
115
+ *** Test Cases ***
116
+ Queue And Wait For Responses
117
+ Parallel Create Session alias=default base_url=https://api.example.com
118
+ ${id1}= Parallel Queue Request GET /users/1
119
+ ${id2}= Parallel Queue Request GET /users/2
120
+ ${id3}= Parallel Queue Request GET /users/3
121
+
122
+ Parallel Wait For All Requests timeout=30
123
+
124
+ ${status1}= Parallel Get Response Status ${id1}
125
+ Should Be Equal ${status1} 200
126
+
127
+ ${body}= Parallel Get Response Body ${id1}
128
+ Log ${body}
129
+
130
+ Parallel Shutdown
131
+ ```
132
+
133
+ ### Using Response Objects Directly
134
+
135
+ ```robot
136
+ *** Test Cases ***
137
+ Access Response Object
138
+ Parallel Create Session
139
+ ${id}= Parallel Queue Request GET https://httpbin.org/json
140
+ Parallel Wait For All Requests timeout=30
141
+
142
+ ${response}= Parallel Get Response Object ${id}
143
+ # Now you have the underlying httpx.Response object
144
+ Should Be Equal ${response.status_code} 200
145
+ ${json_data}= Parallel Get Response JSON ${id}
146
+ Log ${json_data}
147
+
148
+ Parallel Shutdown
149
+ ```
150
+
151
+ ## Comparison with RequestsLibrary
152
+
153
+ | Feature | robot_parallel_requests | RequestsLibrary |
154
+ |---------|------------------------|-----------------|
155
+ | **Parallel/Async Requests** | ✅ Native ThreadPoolExecutor | ❌ Sequential only |
156
+ | **Queue Multiple Requests** | ✅ Yes, with ID-based retrieval | ❌ No |
157
+ | **Bulk Operations** | ✅ Optimized | ❌ Requires loops + waits |
158
+ | **Rate Limiting Tests** | ✅ Token bucket with per-send throttling | ⚠️ Difficult/slow |
159
+ | **API Compatibility** | Similar keywords (with `Parallel ` prefix) | — |
160
+ | **Direct Response Objects** | ✅ `httpx.Response` access | ✅ `requests.Response` access |
161
+ | **Session Management** | ✅ Named sessions | ✅ Named sessions |
162
+
163
+ **When to use robot_parallel_requests:**
164
+ - Testing APIs with rate limits, quotas, or concurrency requirements
165
+ - Bulk operations (e.g., creating 100 records in parallel)
166
+ - Performance/load testing within Robot Framework
167
+ - Simulating real-world parallel client behavior
168
+ - Optimizing tests that make many requests in succession
169
+
170
+ **When to stick with RequestsLibrary:**
171
+ - Simple sequential API testing
172
+ - Lightweight HTTP assertions
173
+ - No parallel workload requirements
174
+ - But feel free to use both—we're good with that
175
+
176
+ ## Error Handling
177
+
178
+ When a request fails (network error, timeout, invalid URL), the exception is stored in the response store:
179
+
180
+ ```robot
181
+ *** Test Cases ***
182
+ Handle Request Failures
183
+ Parallel Create Session
184
+ ${id1}= Parallel Queue Request GET https://httpbin.org/delay/2 timeout=1
185
+ ${id2}= Parallel Queue Request GET https://invalid-domain-12345.com
186
+
187
+ Parallel Wait For All Requests timeout=10
188
+
189
+ # Check if response is an exception
190
+ ${resp}= Parallel Get Response Object ${id1}
191
+ Run Keyword If '${type(resp).__name__}' == 'ReadTimeout' Log Request timed out
192
+
193
+ # For a regular response, status code is safe
194
+ ${resp2}= Parallel Get Response Object ${id2}
195
+ Run Keyword If '${type(resp2).__name__}' == 'ConnectError' Log Request failed: ${resp2}
196
+
197
+ Parallel Shutdown
198
+ ```
199
+
200
+ **Safe Patterns:**
201
+ - Always call `Parallel Wait For All Requests` before retrieving responses
202
+ - Use `Parallel Get Response Object` and check the exception type if needed
203
+ - Use `Run Keyword If` with type checks for conditional error handling
204
+
205
+ ## Best Practices
206
+
207
+ **Library Scope:**
208
+ - This library sets `ROBOT_LIBRARY_SCOPE = "TEST"` so each test gets a fresh instance and resources are always isolated.
209
+ - This is the most idiomatic and robust approach for libraries managing connections, pools, or other stateful resources.
210
+
211
+ **Shutdown Handling:**
212
+ - Shutdown runs automatically at end of each test via the library listener.
213
+ - Explicit `Parallel Shutdown` is still safe (idempotent) and useful mid-test.
214
+ - ❌ **Bad:** Calling `Parallel Shutdown` mid-test and then queueing more requests without `Parallel Set Worker Count` to recreate the pool.
215
+
216
+ **Worker Count:**
217
+ - Default `worker_count=5` is suitable for most scenarios.
218
+ - For high-throughput tests, increase to 10-20 (connection pool limits scale with workers).
219
+ - For I/O-heavy operations, ThreadPoolExecutor can handle 50+ safely.
220
+ - Enable `http2=True` (optional `h2` extra) if you want multiplexing to a single origin.
221
+
222
+ **Timeout Handling:**
223
+ - Always set explicit timeouts in `Parallel Wait For All Requests` to prevent hanging tests.
224
+ - Individual request timeouts (via `timeout=` parameter in `Queue Request`) affect only that request.
225
+ - By default, if the wait timeout expires, the keyword logs a warning. Use `fail_on_timeout=${True}` (keyword or library init) to fail the test instead.
226
+ - Not-yet-started futures are cancelled on timeout; in-flight HTTP calls may still finish in the background.
227
+
228
+ **Sessions:**
229
+ - Omitting `session=` automatically uses the `default` session when one was created.
230
+ - Pass `session=<alias>` for non-default sessions.
231
+
232
+ **Request IDs:**
233
+ - Each queued request must use a unique `id` when you provide a custom value. Reusing an ID raises `ValueError`.
234
+
235
+ **Batch waits:**
236
+ - `Parallel Wait For All And Get Responses` returns only the requests queued since the previous wait in the same test.
237
+
238
+ **Rate limiting:**
239
+ - Throttling is enforced when requests are sent, not when they are queued.
240
+ - Default burst size is `requests + 1` (e.g. `105 per="minute"` → burst `106`).
241
+ - Retries consume rate-limit tokens on each HTTP attempt.
242
+
243
+ **If you override library scope:**
244
+ - Use `Suite Teardown` to call shutdown/cleanup keywords.
245
+ - Avoid calling shutdown in individual tests unless you fully understand the implications.
246
+
247
+ ## Keywords
248
+
249
+ ### Session Management
250
+
251
+ **Parallel Create Session**
252
+ - **Arguments:** `alias` (str, default `default`), `base_url` (str, optional), `headers` (dict, optional)
253
+ - **Description:** Create a named session with optional base URL and default headers. The `default` session is applied automatically when queue keywords omit `session=`.
254
+
255
+ **Parallel Shutdown**
256
+ - **Description:** Shutdown worker pool and close transport (also runs automatically at end of each test).
257
+
258
+ ### Request Queuing
259
+
260
+ **Parallel Queue Request**
261
+ - **Arguments:** `method` (str), `url` (str), `session` (str, optional), `id` (str, optional), `**kwargs` (headers, json, data, params, etc.)
262
+ - **Returns:** Response ID (string)
263
+ - **Description:** Queue a request to be processed by the worker pool. Returns a response ID for later retrieval. Custom `id` values must be unique within the test instance.
264
+
265
+ **Parallel Queue Many**
266
+ - **Arguments:** `requests` (list of dicts or `[method, url]` pairs), `session` (str, optional)
267
+ - **Returns:** List of response IDs
268
+ - **Description:** Bulk-enqueue many requests and return their IDs in order.
269
+
270
+ **Parallel Start Workers**
271
+ - **Description:** Start worker pool (workers are ready on init; this is a no-op in MVP).
272
+
273
+ **Parallel Wait For All Requests**
274
+ - **Arguments:** `timeout` (float, optional, seconds), `fail_on_timeout` (bool, optional)
275
+ - **Description:** Block until all requests queued since the last wait complete or the timeout expires. Logs a warning (or raises `TimeoutError` when fail-on-timeout is enabled) if any requests remain incomplete.
276
+
277
+ **Parallel Wait For All And Get Responses**
278
+ - **Arguments:** `timeout` (float, optional, seconds), `fail_on_timeout` (bool, optional)
279
+ - **Returns:** List of `httpx.Response` or Exception objects in submission order for the current pending batch only.
280
+
281
+ ### Response Retrieval
282
+
283
+ **Parallel Get Response Object**
284
+ - **Arguments:** `id` (str)
285
+ - **Returns:** `httpx.Response` object (or Exception if request failed)
286
+ - **Description:** Retrieve the underlying response object for direct assertions.
287
+
288
+ **Parallel Get Response Status**
289
+ - **Arguments:** `id` (str)
290
+ - **Returns:** Status code (int)
291
+
292
+ **Parallel Get Response Body**
293
+ - **Arguments:** `id` (str)
294
+ - **Returns:** Response body as string
295
+
296
+ **Parallel Get Response JSON**
297
+ - **Arguments:** `id` (str)
298
+ - **Returns:** Parsed JSON (dict/list)
299
+
300
+ ### Configuration
301
+
302
+ **Parallel Set Worker Count**
303
+ - **Arguments:** `count` (int)
304
+ - **Description:** Adjust the number of concurrent worker threads.
305
+
306
+ **Parallel Set Rate Limit**
307
+ - **Arguments:** `requests` (float), `per` (str, default `"second"`), `burst_size` (int, optional)
308
+ - **Description:** Configure client-side token-bucket rate limiting. Default burst size is `requests + 1`.
309
+
310
+ **Parallel Set Retry Policy**
311
+ - **Arguments:** `max_retries`, `backoff_factor`, `retry_statuses`, `jitter`
312
+ - **Description:** Configure exponential backoff retries for selected HTTP status codes and transport errors (timeouts/network). Jitter is applied to backoff waits.
313
+
314
+ **Parallel Get Metrics**
315
+ - **Description:** Return aggregated request metrics including retry counts and requests per second.
316
+
317
+ ## Library Initialization
318
+
319
+ ### Arguments
320
+
321
+ - `worker_count` (int, default: `5`): Number of worker threads in the pool (connection limits follow).
322
+ - `fail_on_timeout` (bool, default: `False`): Make wait keywords raise `TimeoutError` when incomplete.
323
+ - `http2` (bool, default: `False`): Enable HTTP/2 (requires optional `h2` extra).
324
+ - `cancel_pending_on_timeout` (bool, default: `True`): Cancel not-yet-started futures when a wait times out.
325
+
326
+ ### Examples
327
+
328
+ ```robot
329
+ # Default: 5 worker threads
330
+ Library robot_parallel_requests
331
+
332
+ # High concurrency: 20 worker threads, fail waits on timeout
333
+ Library robot_parallel_requests worker_count=20 fail_on_timeout=${True}
334
+ ```
335
+
336
+ ## Use Cases
337
+
338
+ ### 1. Rate Limiting Testing
339
+ Queue 101 requests (where the 101st should fail) to test error handling and rate limits:
340
+
341
+ ```robot
342
+ *** Test Cases ***
343
+ Test Rate Limit With Bulk Requests
344
+ Parallel Create Session
345
+ FOR ${i} IN RANGE 101
346
+ ${id}= Parallel Queue Request POST /api/favorite-restaurants json={"name": "Restaurant ${i}"}
347
+ END
348
+
349
+ Parallel Wait For All Requests timeout=60
350
+
351
+ # Verify 100 succeeded, 1 failed
352
+ # ... retrieve and check statuses
353
+ ```
354
+
355
+ ### 2. Parallel API Calls
356
+ Fetch multiple user profiles in parallel:
357
+
358
+ ```robot
359
+ *** Test Cases ***
360
+ Fetch Multiple User Profiles
361
+ Parallel Create Session base_url=https://api.example.com
362
+ @{user_ids}= Create List 1 2 3 4 5
363
+ @{response_ids}= Create List
364
+
365
+ FOR ${user_id} IN @{user_ids}
366
+ ${id}= Parallel Queue Request GET /users/${user_id}
367
+ Append To List @{response_ids} ${id}
368
+ END
369
+
370
+ Parallel Wait For All Requests timeout=30
371
+
372
+ FOR ${id} IN @{response_ids}
373
+ ${resp}= Parallel Get Response Object ${id}
374
+ Should Be Equal ${resp.status_code} 200
375
+ END
376
+
377
+ Parallel Shutdown
378
+ ```
379
+
380
+ ### 3. Direct Response Assertions
381
+ Use the raw response object for complex assertions:
382
+
383
+ ```robot
384
+ *** Test Cases ***
385
+ Advanced Response Assertions
386
+ Parallel Create Session
387
+ ${id}= Parallel Queue Request GET https://httpbin.org/headers
388
+ Parallel Wait For All Requests
389
+
390
+ ${resp}= Parallel Get Response Object ${id}
391
+ Should Contain ${resp.headers['user-agent']} python-httpx
392
+ Should Be Equal As Numbers ${resp.elapsed.total_seconds()} ${0} delta=5
393
+
394
+ Parallel Shutdown
395
+ ```
396
+
397
+ ## Testing
398
+
399
+ Run unit tests:
400
+
401
+ ```bash
402
+ pytest tests/ -v
403
+ ```
404
+
405
+ Run example Robot tests (requires Robot Framework installed in venv):
406
+
407
+ ```bash
408
+ robot examples/parallel_requests.robot
409
+ ```
410
+
411
+ ## Architecture
412
+
413
+ - **`tasks.py`**: `RequestTask` dataclass for queued requests.
414
+ - **`response_store.py`**: In-memory storage for responses by ID.
415
+ - **`transport/base.py`**: Abstract transport interface.
416
+ - **`transport/httpx_sync.py`**: Synchronous httpx-based transport.
417
+ - **`worker.py`**: ThreadPoolExecutor-based worker pool.
418
+ - **`library.py`**: Robot Framework library with keywords.
419
+
420
+ ## Future Enhancements
421
+
422
+ - **Async transport** (`transport/httpx_async.py`) using `httpx.AsyncClient` for very high concurrency.
423
+ - **Circuit breaker / adaptive backoff** to complement existing retry policy.
424
+ - **Structured logging / tracing hooks** (OpenTelemetry integration).
425
+ - **Per-session prioritized queues** for differentiated QoS.
426
+ - **Optional persistent response cache** (configurable TTL).
427
+
428
+ ## Contributing
429
+
430
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup,
431
+ branching, and pull request expectations.
432
+
433
+ ## License
434
+
435
+ MIT — see [LICENSE](LICENSE).
436
+
437
+ ## Release Process
438
+
439
+ Automated releases are driven by Git tags and a GitHub Actions workflow (`publish.yml`).
440
+
441
+ ### Version Tags (PEP 440)
442
+
443
+ | Type | Example | Notes |
444
+ |------|---------|-------|
445
+ | Final | `v0.1.0` | Stable release consumers get by default |
446
+ | Release Candidate | `v0.1.0rc1` | Treated as prerelease; published to PyPI & TestPyPI |
447
+ | Beta / Alpha | `v0.1.0b1`, `v0.1.0a1` | Published to TestPyPI only |
448
+ | Dev Snapshot | `v0.1.0.dev2` | Iterative build, TestPyPI only |
449
+
450
+ ### Publishing Matrix
451
+
452
+ | Tag Type | TestPyPI | PyPI | GitHub Release | Prerelease Flag |
453
+ |----------|----------|-----|----------------|-----------------|
454
+ | Final (`vX.Y.Z`) | No | Yes | Yes | No |
455
+ | RC (`vX.Y.ZrcN`) | Yes | Yes | Yes | Yes |
456
+ | Beta/Alpha (`vX.Y.ZbN/aN`) | Yes | No | Yes | Yes |
457
+ | Dev (`vX.Y.Z.devN`) | Yes | No | Yes | Yes |
458
+
459
+ ### Changelog Enforcement
460
+
461
+ Final and RC tags must have a `## [X.Y.Z]` section in `CHANGELOG.md`. Missing sections cause the workflow to fail.
462
+
463
+ ### Prerelease Notes Generation
464
+
465
+ For dev/alpha/beta tags, release notes are generated from the commit diff between the new tag and the previous `v*` tag:
466
+
467
+ ```
468
+ ### v0.1.0b1
469
+ Changes since v0.1.0:
470
+ - abc123 Short commit message (Author)
471
+ ```
472
+
473
+ ### Trusted Publishing
474
+
475
+ Configure trusted publishers with workflow file **`publish.yml`**.
476
+
477
+ | Tag type | GitHub environment |
478
+ |----------|--------------------|
479
+ | Dev / alpha / beta | `testpypi` |
480
+ | RC | `testpypi` and `pypi` |
481
+ | Final | `pypi` |
482
+
483
+ ### Release Steps
484
+
485
+ 1. Update `CHANGELOG.md` (for final/RC).
486
+ 2. Run tests: `pytest -v` and `robot examples/parallel_requests.robot`.
487
+ 3. Ensure the matching GitHub environment(s) and PyPI/TestPyPI trusted publishers exist.
488
+ 4. Tag and push:
489
+ ```bash
490
+ git tag v0.1.0rc1
491
+ git push origin v0.1.0rc1
492
+ # Final:
493
+ git tag v0.1.0
494
+ git push origin v0.1.0
495
+ ```
496
+ 5. Workflow builds, uploads, generates notes, creates GitHub Release.
497
+
498
+ ### Installing Pre-Releases
499
+
500
+ ```bash
501
+ pip install --pre robotframework-parallel-requests
502
+ # Or pin a specific build
503
+ pip install robotframework-parallel-requests==0.1.0rc1
504
+ ```
505
+
506
+ ### TestPyPI Validation
507
+
508
+ ```bash
509
+ pip install -i https://test.pypi.org/simple robotframework-parallel-requests==0.1.0.dev2 --extra-index-url https://pypi.org/simple
510
+ ```
511
+
512
+ ### Patch / Hotfix
513
+
514
+ Add a new section in CHANGELOG, tag, push:
515
+ ```bash
516
+ git tag v0.1.1
517
+ git push origin v0.1.1
518
+ ```
519
+
520
+ ### Yanks & Post Releases
521
+
522
+ If a bad release ships, yank on PyPI and publish `X.Y.Z.post1` with the fix.
523
+
524
+