urban-http 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 (61) hide show
  1. urban_http-0.1.0/.github/workflows/ci.yml +62 -0
  2. urban_http-0.1.0/.github/workflows/publish.yml +87 -0
  3. urban_http-0.1.0/.gitignore +29 -0
  4. urban_http-0.1.0/LICENSE +21 -0
  5. urban_http-0.1.0/PKG-INFO +235 -0
  6. urban_http-0.1.0/README.md +172 -0
  7. urban_http-0.1.0/RELEASING.md +62 -0
  8. urban_http-0.1.0/demo/index.html +408 -0
  9. urban_http-0.1.0/demo/server.py +210 -0
  10. urban_http-0.1.0/docs/curl-coverage.md +171 -0
  11. urban_http-0.1.0/docs/curl-use-cases.md +175 -0
  12. urban_http-0.1.0/docs/openapi.md +106 -0
  13. urban_http-0.1.0/docs/postman-collections.md +76 -0
  14. urban_http-0.1.0/docs/protocols.md +142 -0
  15. urban_http-0.1.0/docs/variables.md +93 -0
  16. urban_http-0.1.0/pyproject.toml +62 -0
  17. urban_http-0.1.0/src/urban_http/__init__.py +59 -0
  18. urban_http-0.1.0/src/urban_http/cli.py +119 -0
  19. urban_http-0.1.0/src/urban_http/command.py +61 -0
  20. urban_http-0.1.0/src/urban_http/cookies.py +101 -0
  21. urban_http-0.1.0/src/urban_http/digest.py +113 -0
  22. urban_http-0.1.0/src/urban_http/execute.py +349 -0
  23. urban_http-0.1.0/src/urban_http/glob.py +95 -0
  24. urban_http-0.1.0/src/urban_http/model.py +132 -0
  25. urban_http-0.1.0/src/urban_http/net.py +170 -0
  26. urban_http-0.1.0/src/urban_http/openapi.py +345 -0
  27. urban_http-0.1.0/src/urban_http/parse.py +810 -0
  28. urban_http-0.1.0/src/urban_http/postman.py +221 -0
  29. urban_http-0.1.0/src/urban_http/protocols/__init__.py +59 -0
  30. urban_http-0.1.0/src/urban_http/protocols/file.py +73 -0
  31. urban_http-0.1.0/src/urban_http/protocols/ftp.py +103 -0
  32. urban_http-0.1.0/src/urban_http/protocols/imap.py +99 -0
  33. urban_http-0.1.0/src/urban_http/protocols/mqtt.py +81 -0
  34. urban_http-0.1.0/src/urban_http/protocols/parse.py +314 -0
  35. urban_http-0.1.0/src/urban_http/protocols/pop3.py +72 -0
  36. urban_http-0.1.0/src/urban_http/protocols/registry.py +35 -0
  37. urban_http-0.1.0/src/urban_http/protocols/scp.py +83 -0
  38. urban_http-0.1.0/src/urban_http/protocols/sftp.py +82 -0
  39. urban_http-0.1.0/src/urban_http/protocols/smb.py +98 -0
  40. urban_http-0.1.0/src/urban_http/protocols/smtp.py +88 -0
  41. urban_http-0.1.0/src/urban_http/protocols/tftp.py +119 -0
  42. urban_http-0.1.0/src/urban_http/protocols/types.py +119 -0
  43. urban_http-0.1.0/src/urban_http/protocols/ws.py +91 -0
  44. urban_http-0.1.0/src/urban_http/tokenize.py +189 -0
  45. urban_http-0.1.0/src/urban_http/variables.py +114 -0
  46. urban_http-0.1.0/tests/conftest.py +5 -0
  47. urban_http-0.1.0/tests/test_advanced.py +234 -0
  48. urban_http-0.1.0/tests/test_cookies.py +50 -0
  49. urban_http-0.1.0/tests/test_digest.py +57 -0
  50. urban_http-0.1.0/tests/test_execute.py +90 -0
  51. urban_http-0.1.0/tests/test_glob.py +56 -0
  52. urban_http-0.1.0/tests/test_imap_scp.py +90 -0
  53. urban_http-0.1.0/tests/test_openapi.py +181 -0
  54. urban_http-0.1.0/tests/test_parse.py +178 -0
  55. urban_http-0.1.0/tests/test_postman.py +117 -0
  56. urban_http-0.1.0/tests/test_protocols_adapters.py +244 -0
  57. urban_http-0.1.0/tests/test_protocols_parse.py +91 -0
  58. urban_http-0.1.0/tests/test_protocols_signaling.py +51 -0
  59. urban_http-0.1.0/tests/test_tokenize.py +41 -0
  60. urban_http-0.1.0/tests/test_variables.py +86 -0
  61. urban_http-0.1.0/tests/test_websocket.py +80 -0
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ name: Tests (Python ${{ matrix.python-version }})
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - name: Set up Python ${{ matrix.python-version }}
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+
32
+ - name: Installer le paquet et les dépendances de test
33
+ run: |
34
+ python -m pip install --upgrade pip
35
+ python -m pip install ".[dev]" pyyaml
36
+
37
+ - name: Lancer les tests
38
+ run: pytest -q
39
+
40
+ build:
41
+ name: Build & vérification du paquet
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - uses: actions/setup-python@v5
47
+ with:
48
+ python-version: "3.12"
49
+
50
+ - name: Build sdist + wheel
51
+ run: |
52
+ python -m pip install --upgrade build twine
53
+ python -m build
54
+
55
+ - name: Vérifier les métadonnées du paquet
56
+ run: python -m twine check dist/*
57
+
58
+ - name: Conserver les artefacts
59
+ uses: actions/upload-artifact@v4
60
+ with:
61
+ name: distributions
62
+ path: dist/
@@ -0,0 +1,87 @@
1
+ name: Publier sur PyPI
2
+
3
+ # Déclenché en poussant un tag de version, ex. : git tag v0.2.0 && git push origin v0.2.0
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+ workflow_dispatch:
8
+ inputs:
9
+ repository:
10
+ description: "Cible : pypi ou testpypi"
11
+ default: "pypi"
12
+ type: choice
13
+ options: ["pypi", "testpypi"]
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ build:
20
+ name: Construire le paquet
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Vérifier que le tag correspond à la version du paquet
30
+ if: startsWith(github.ref, 'refs/tags/v')
31
+ run: |
32
+ TAG="${GITHUB_REF_NAME#v}"
33
+ VERSION="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
34
+ echo "tag=$TAG version_pyproject=$VERSION"
35
+ if [ "$TAG" != "$VERSION" ]; then
36
+ echo "::error::Le tag v$TAG ne correspond pas à project.version=$VERSION dans pyproject.toml"
37
+ exit 1
38
+ fi
39
+
40
+ - name: Build sdist + wheel
41
+ run: |
42
+ python -m pip install --upgrade build twine
43
+ python -m build
44
+ python -m twine check dist/*
45
+
46
+ - uses: actions/upload-artifact@v4
47
+ with:
48
+ name: distributions
49
+ path: dist/
50
+
51
+ publish-testpypi:
52
+ name: Publier sur TestPyPI
53
+ if: github.event_name == 'workflow_dispatch' && inputs.repository == 'testpypi'
54
+ needs: build
55
+ runs-on: ubuntu-latest
56
+ environment:
57
+ name: testpypi
58
+ url: https://test.pypi.org/p/urban-http
59
+ permissions:
60
+ id-token: write # OIDC pour le Trusted Publishing, aucun token stocké
61
+ steps:
62
+ - uses: actions/download-artifact@v4
63
+ with:
64
+ name: distributions
65
+ path: dist/
66
+ - name: Téléverser vers TestPyPI
67
+ uses: pypa/gh-action-pypi-publish@release/v1
68
+ with:
69
+ repository-url: https://test.pypi.org/legacy/
70
+
71
+ publish-pypi:
72
+ name: Publier sur PyPI
73
+ if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.repository == 'pypi')
74
+ needs: build
75
+ runs-on: ubuntu-latest
76
+ environment:
77
+ name: pypi
78
+ url: https://pypi.org/p/urban-http
79
+ permissions:
80
+ id-token: write # OIDC pour le Trusted Publishing, aucun token stocké
81
+ steps:
82
+ - uses: actions/download-artifact@v4
83
+ with:
84
+ name: distributions
85
+ path: dist/
86
+ - name: Téléverser vers PyPI
87
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+
12
+ # Environnements virtuels
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Tests et couverture
18
+ .pytest_cache/
19
+ .coverage
20
+ coverage.xml
21
+ htmlcov/
22
+ .tox/
23
+ .mypy_cache/
24
+ .ruff_cache/
25
+
26
+ # Éditeurs / OS
27
+ .DS_Store
28
+ .idea/
29
+ .vscode/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 black beard
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,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: urban-http
3
+ Version: 0.1.0
4
+ Summary: Transformer une commande curl ou une collection Postman en appel exécutable (HTTP et autres protocoles).
5
+ Project-URL: Repository, https://github.com/gaelgael5/urban-http
6
+ Author: urban-http
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 black beard
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: curl,http,parser,postman,sdk
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: Programming Language :: Python :: 3.9
33
+ Classifier: Programming Language :: Python :: 3.10
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Topic :: Internet :: WWW/HTTP
38
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
39
+ Requires-Python: >=3.9
40
+ Provides-Extra: all
41
+ Requires-Dist: flask>=3; extra == 'all'
42
+ Requires-Dist: paho-mqtt>=2; extra == 'all'
43
+ Requires-Dist: paramiko>=3; extra == 'all'
44
+ Requires-Dist: smbprotocol>=1.12; extra == 'all'
45
+ Requires-Dist: websockets>=12; extra == 'all'
46
+ Provides-Extra: dev
47
+ Requires-Dist: pytest>=8; extra == 'dev'
48
+ Provides-Extra: mqtt
49
+ Requires-Dist: paho-mqtt>=2; extra == 'mqtt'
50
+ Provides-Extra: openapi
51
+ Requires-Dist: pyyaml>=6; extra == 'openapi'
52
+ Provides-Extra: scp
53
+ Requires-Dist: paramiko>=3; extra == 'scp'
54
+ Provides-Extra: sftp
55
+ Requires-Dist: paramiko>=3; extra == 'sftp'
56
+ Provides-Extra: smb
57
+ Requires-Dist: smbprotocol>=1.12; extra == 'smb'
58
+ Provides-Extra: web
59
+ Requires-Dist: flask>=3; extra == 'web'
60
+ Provides-Extra: websocket
61
+ Requires-Dist: websockets>=12; extra == 'websocket'
62
+ Description-Content-Type: text/markdown
63
+
64
+ # urban-http
65
+
66
+ [![CI](https://github.com/gaelgael5/urban-http/actions/workflows/ci.yml/badge.svg)](https://github.com/gaelgael5/urban-http/actions/workflows/ci.yml)
67
+
68
+ SDK **Python** pour transformer une commande **curl** ou une **collection Postman**
69
+ en **appel exécutable** — HTTP via la bibliothèque standard, et les autres
70
+ protocoles de curl (SMTP, FTP, SFTP, POP3, MQTT, TFTP, FILE, SMB, WebSocket) via
71
+ des adaptateurs dont les dépendances sont gérées en **extras PyPI**. Protocoles
72
+ couverts : HTTP, SMTP, FTP/FTPS, SFTP, SCP, POP3, IMAP, MQTT, TFTP, FILE, SMB,
73
+ WebSocket.
74
+
75
+ ```
76
+ commande curl ───▶ parse_curl() ────┐
77
+ ├──▶ HttpRequestModel ──▶ prepare_request() / execute()
78
+ collection Postman ─▶ parse_postman() ┘
79
+ └▶ parse_command() ──▶ ProtocolRequest ──▶ run() (adaptateur)
80
+ ```
81
+
82
+ ## Installation
83
+
84
+ ```bash
85
+ pip install urban-http # cœur : HTTP + SMTP/FTP/POP3/TFTP/FILE (stdlib, zéro dépendance)
86
+ pip install "urban-http[sftp,mqtt,smb]" # extras pour les protocoles qui requièrent une bibliothèque
87
+ pip install "urban-http[all]" # tout
88
+ ```
89
+
90
+ Le **cœur n'a aucune dépendance obligatoire**. Chaque protocole qui s'appuie sur
91
+ une bibliothèque tierce est un extra :
92
+
93
+ | Extra | Protocole | Bibliothèque PyPI |
94
+ |---|---|---|
95
+ | `sftp` | SFTP | paramiko |
96
+ | `mqtt` | MQTT / MQTTS | paho-mqtt |
97
+ | `smb` | SMB / SMBS | smbprotocol |
98
+ | `websocket` | WS / WSS | websockets |
99
+ | `web` | site de test local | flask (optionnel) |
100
+
101
+ SMTP, FTP/FTPS, POP3, IMAP, TFTP et FILE utilisent uniquement la bibliothèque
102
+ standard (`smtplib`, `ftplib`, `poplib`, `imaplib`, `socket`, `open`) : aucun
103
+ extra. SCP partage paramiko avec SFTP.
104
+
105
+ ## Utilisation
106
+
107
+ ```python
108
+ from urban_http import parse_curl, execute
109
+
110
+ result = parse_curl(
111
+ "curl -X POST https://api.example.com/items "
112
+ "-H 'Content-Type: application/json' -d '{\"name\": \"clavier\"}' -u alice:secret -m 10"
113
+ )
114
+ req = result.requests[0]
115
+ req.method # "POST"
116
+ req.url # "https://api.example.com/items"
117
+ req.header("Content-Type") # "application/json"
118
+
119
+ resp = execute(req).response
120
+ print(resp.status, resp.text())
121
+ ```
122
+
123
+ Tout ce qui n'est pas reproductible est **signalé dans `warnings`** (jamais ignoré
124
+ en silence) : chaque warning a un `code` stable, un `message` et un niveau.
125
+
126
+ ## Variables
127
+
128
+ ```python
129
+ # {{nom}} partout, et $NOM / ${NOM} comme dans le shell (quotes doubles et nu)
130
+ parse_curl('curl -H "Authorization: Bearer $TOKEN" {{baseUrl}}/users',
131
+ {"baseUrl": "https://api.example.com", "TOKEN": "tok-42"})
132
+
133
+ # Dynamiques Postman générées : {{$guid}}, {{$randomUUID}}, {{$timestamp}}, {{$isoTimestamp}}, {{$randomInt}}
134
+ from urban_http import parse_postman_environment
135
+ variables = parse_postman_environment(env_json) # export d'environnement Postman
136
+ ```
137
+
138
+ ## Collections Postman
139
+
140
+ ```python
141
+ from urban_http import parse_postman, is_postman_collection
142
+
143
+ result = parse_postman(collection_json, {"baseUrl": "https://api.example.com"})
144
+ for req in result.requests:
145
+ print(req.name, req.method, req.url)
146
+ ```
147
+
148
+ Format Collection v2.0 / v2.1 : dossiers imbriqués, variables, tous les modes de
149
+ corps (raw, urlencoded, formdata, file, graphql), auth basic/bearer/apikey avec
150
+ héritage collection → dossier → requête.
151
+
152
+ ## Autres protocoles
153
+
154
+ ```python
155
+ from urban_http import run, register_default_adapters
156
+
157
+ register_default_adapters() # une fois
158
+
159
+ # Envoi d'un mail (smtplib), téléversement FTP (ftplib), publication MQTT (paho)...
160
+ result = run("curl smtp://mail.example.com --mail-from moi@x.com --mail-rcpt toi@y.com -T message.eml")
161
+ print(result.protocol.summary)
162
+ ```
163
+
164
+ `run()` détecte le protocole et route : HTTP via urllib, sinon l'adaptateur
165
+ enregistré. Chaque adaptateur accepte un `client_factory` injectable (tests sans
166
+ réseau, ou autre bibliothèque). Détail : **[docs/protocols.md](docs/protocols.md)**.
167
+
168
+ ## OpenAPI → curl
169
+
170
+ `openapi_to_curl(contrat)` lit un contrat OpenAPI v3.x (dict, JSON, ou YAML avec
171
+ l'extra `openapi`) et renvoie, pour chaque opération, une commande curl avec des
172
+ variables injectables, le tableau de ses paramètres, et un modèle générique du
173
+ corps de requête.
174
+
175
+ ```python
176
+ from urban_http import openapi_to_curl, parse_curl
177
+
178
+ for op in openapi_to_curl(contrat):
179
+ print(op.method, op.path)
180
+ print(op.curl) # avec des {{placeholders}}
181
+ print(op.example_variables) # {nom: valeur} prêt pour parse_curl
182
+ req = parse_curl(op.curl, op.example_variables).requests[0]
183
+ ```
184
+
185
+ Le corps est généré exhaustivement avec des valeurs types : chaîne → nom de la
186
+ propriété, entier → 0, date/date-time → instant courant, sous-objet développé,
187
+ tableau à un seul élément. Détail : **[docs/openapi.md](docs/openapi.md)**.
188
+
189
+ ## Ligne de commande
190
+
191
+ ```bash
192
+ urban-http "curl -X POST https://api.example.com -d a=1" # analyse et affiche la requête
193
+ urban-http "curl file:///etc/hostname" --run # analyse ET exécute
194
+ urban-http "curl {{baseUrl}}/get" --var baseUrl=https://httpbin.org
195
+ urban-http --openapi contrat.json # génère les curl d'un contrat OpenAPI
196
+ ```
197
+
198
+ ## Site de test
199
+
200
+ ```bash
201
+ python demo/server.py # http://127.0.0.1:8000
202
+ ```
203
+
204
+ Une page web qui analyse en direct n'importe quelle commande curl ou collection
205
+ Postman (analyse faite en Python côté serveur) et exécute les requêtes HTTP. Les
206
+ exemples HTTP ciblent httpbin.org.
207
+
208
+ ## Développement
209
+
210
+ ```bash
211
+ python -m pytest # 199 tests : tokenizer, parser, Postman, variables,
212
+ # globbing, cookies, Digest, exécution, protocoles,
213
+ # options TLS/réseau, OpenAPI
214
+ ```
215
+
216
+ La CI (GitHub Actions) lance les tests sur Python 3.9 → 3.13 et construit le
217
+ paquet à chaque push / PR. Une version se publie sur PyPI en poussant un tag
218
+ `vX.Y.Z` — voir **[RELEASING.md](RELEASING.md)**.
219
+
220
+ ## Couverture de curl
221
+
222
+ - **HTTP/HTTPS** : requête, corps, multipart, globbing, redirections, retries,
223
+ timeout, auth Basic/Bearer/Digest, cookies (inline et fichiers Netscape),
224
+ variables — plus l'import Postman. Détail : [docs/curl-use-cases.md](docs/curl-use-cases.md).
225
+ - **Autres protocoles** : SMTP, FTP/FTPS, SFTP, SCP, POP3, IMAP, MQTT, TFTP,
226
+ FILE, SMB, WebSocket. Détail : [docs/protocols.md](docs/protocols.md).
227
+ - **Panorama complet** (ce qui est couvert et ce qui ne l'est pas) :
228
+ [docs/curl-coverage.md](docs/curl-coverage.md).
229
+
230
+ ## Feuille de route
231
+
232
+ - Protocoles restants : LDAP, TELNET.
233
+ - Auth : AWS SigV4, NTLM/Negotiate, Digest `qop=auth-int`.
234
+ - Requêtes conditionnelles (`-z`, `--etag-compare`), reprise `-C -` automatique.
235
+ - Générateurs de code supplémentaires (httpx, requests).
@@ -0,0 +1,172 @@
1
+ # urban-http
2
+
3
+ [![CI](https://github.com/gaelgael5/urban-http/actions/workflows/ci.yml/badge.svg)](https://github.com/gaelgael5/urban-http/actions/workflows/ci.yml)
4
+
5
+ SDK **Python** pour transformer une commande **curl** ou une **collection Postman**
6
+ en **appel exécutable** — HTTP via la bibliothèque standard, et les autres
7
+ protocoles de curl (SMTP, FTP, SFTP, POP3, MQTT, TFTP, FILE, SMB, WebSocket) via
8
+ des adaptateurs dont les dépendances sont gérées en **extras PyPI**. Protocoles
9
+ couverts : HTTP, SMTP, FTP/FTPS, SFTP, SCP, POP3, IMAP, MQTT, TFTP, FILE, SMB,
10
+ WebSocket.
11
+
12
+ ```
13
+ commande curl ───▶ parse_curl() ────┐
14
+ ├──▶ HttpRequestModel ──▶ prepare_request() / execute()
15
+ collection Postman ─▶ parse_postman() ┘
16
+ └▶ parse_command() ──▶ ProtocolRequest ──▶ run() (adaptateur)
17
+ ```
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install urban-http # cœur : HTTP + SMTP/FTP/POP3/TFTP/FILE (stdlib, zéro dépendance)
23
+ pip install "urban-http[sftp,mqtt,smb]" # extras pour les protocoles qui requièrent une bibliothèque
24
+ pip install "urban-http[all]" # tout
25
+ ```
26
+
27
+ Le **cœur n'a aucune dépendance obligatoire**. Chaque protocole qui s'appuie sur
28
+ une bibliothèque tierce est un extra :
29
+
30
+ | Extra | Protocole | Bibliothèque PyPI |
31
+ |---|---|---|
32
+ | `sftp` | SFTP | paramiko |
33
+ | `mqtt` | MQTT / MQTTS | paho-mqtt |
34
+ | `smb` | SMB / SMBS | smbprotocol |
35
+ | `websocket` | WS / WSS | websockets |
36
+ | `web` | site de test local | flask (optionnel) |
37
+
38
+ SMTP, FTP/FTPS, POP3, IMAP, TFTP et FILE utilisent uniquement la bibliothèque
39
+ standard (`smtplib`, `ftplib`, `poplib`, `imaplib`, `socket`, `open`) : aucun
40
+ extra. SCP partage paramiko avec SFTP.
41
+
42
+ ## Utilisation
43
+
44
+ ```python
45
+ from urban_http import parse_curl, execute
46
+
47
+ result = parse_curl(
48
+ "curl -X POST https://api.example.com/items "
49
+ "-H 'Content-Type: application/json' -d '{\"name\": \"clavier\"}' -u alice:secret -m 10"
50
+ )
51
+ req = result.requests[0]
52
+ req.method # "POST"
53
+ req.url # "https://api.example.com/items"
54
+ req.header("Content-Type") # "application/json"
55
+
56
+ resp = execute(req).response
57
+ print(resp.status, resp.text())
58
+ ```
59
+
60
+ Tout ce qui n'est pas reproductible est **signalé dans `warnings`** (jamais ignoré
61
+ en silence) : chaque warning a un `code` stable, un `message` et un niveau.
62
+
63
+ ## Variables
64
+
65
+ ```python
66
+ # {{nom}} partout, et $NOM / ${NOM} comme dans le shell (quotes doubles et nu)
67
+ parse_curl('curl -H "Authorization: Bearer $TOKEN" {{baseUrl}}/users',
68
+ {"baseUrl": "https://api.example.com", "TOKEN": "tok-42"})
69
+
70
+ # Dynamiques Postman générées : {{$guid}}, {{$randomUUID}}, {{$timestamp}}, {{$isoTimestamp}}, {{$randomInt}}
71
+ from urban_http import parse_postman_environment
72
+ variables = parse_postman_environment(env_json) # export d'environnement Postman
73
+ ```
74
+
75
+ ## Collections Postman
76
+
77
+ ```python
78
+ from urban_http import parse_postman, is_postman_collection
79
+
80
+ result = parse_postman(collection_json, {"baseUrl": "https://api.example.com"})
81
+ for req in result.requests:
82
+ print(req.name, req.method, req.url)
83
+ ```
84
+
85
+ Format Collection v2.0 / v2.1 : dossiers imbriqués, variables, tous les modes de
86
+ corps (raw, urlencoded, formdata, file, graphql), auth basic/bearer/apikey avec
87
+ héritage collection → dossier → requête.
88
+
89
+ ## Autres protocoles
90
+
91
+ ```python
92
+ from urban_http import run, register_default_adapters
93
+
94
+ register_default_adapters() # une fois
95
+
96
+ # Envoi d'un mail (smtplib), téléversement FTP (ftplib), publication MQTT (paho)...
97
+ result = run("curl smtp://mail.example.com --mail-from moi@x.com --mail-rcpt toi@y.com -T message.eml")
98
+ print(result.protocol.summary)
99
+ ```
100
+
101
+ `run()` détecte le protocole et route : HTTP via urllib, sinon l'adaptateur
102
+ enregistré. Chaque adaptateur accepte un `client_factory` injectable (tests sans
103
+ réseau, ou autre bibliothèque). Détail : **[docs/protocols.md](docs/protocols.md)**.
104
+
105
+ ## OpenAPI → curl
106
+
107
+ `openapi_to_curl(contrat)` lit un contrat OpenAPI v3.x (dict, JSON, ou YAML avec
108
+ l'extra `openapi`) et renvoie, pour chaque opération, une commande curl avec des
109
+ variables injectables, le tableau de ses paramètres, et un modèle générique du
110
+ corps de requête.
111
+
112
+ ```python
113
+ from urban_http import openapi_to_curl, parse_curl
114
+
115
+ for op in openapi_to_curl(contrat):
116
+ print(op.method, op.path)
117
+ print(op.curl) # avec des {{placeholders}}
118
+ print(op.example_variables) # {nom: valeur} prêt pour parse_curl
119
+ req = parse_curl(op.curl, op.example_variables).requests[0]
120
+ ```
121
+
122
+ Le corps est généré exhaustivement avec des valeurs types : chaîne → nom de la
123
+ propriété, entier → 0, date/date-time → instant courant, sous-objet développé,
124
+ tableau à un seul élément. Détail : **[docs/openapi.md](docs/openapi.md)**.
125
+
126
+ ## Ligne de commande
127
+
128
+ ```bash
129
+ urban-http "curl -X POST https://api.example.com -d a=1" # analyse et affiche la requête
130
+ urban-http "curl file:///etc/hostname" --run # analyse ET exécute
131
+ urban-http "curl {{baseUrl}}/get" --var baseUrl=https://httpbin.org
132
+ urban-http --openapi contrat.json # génère les curl d'un contrat OpenAPI
133
+ ```
134
+
135
+ ## Site de test
136
+
137
+ ```bash
138
+ python demo/server.py # http://127.0.0.1:8000
139
+ ```
140
+
141
+ Une page web qui analyse en direct n'importe quelle commande curl ou collection
142
+ Postman (analyse faite en Python côté serveur) et exécute les requêtes HTTP. Les
143
+ exemples HTTP ciblent httpbin.org.
144
+
145
+ ## Développement
146
+
147
+ ```bash
148
+ python -m pytest # 199 tests : tokenizer, parser, Postman, variables,
149
+ # globbing, cookies, Digest, exécution, protocoles,
150
+ # options TLS/réseau, OpenAPI
151
+ ```
152
+
153
+ La CI (GitHub Actions) lance les tests sur Python 3.9 → 3.13 et construit le
154
+ paquet à chaque push / PR. Une version se publie sur PyPI en poussant un tag
155
+ `vX.Y.Z` — voir **[RELEASING.md](RELEASING.md)**.
156
+
157
+ ## Couverture de curl
158
+
159
+ - **HTTP/HTTPS** : requête, corps, multipart, globbing, redirections, retries,
160
+ timeout, auth Basic/Bearer/Digest, cookies (inline et fichiers Netscape),
161
+ variables — plus l'import Postman. Détail : [docs/curl-use-cases.md](docs/curl-use-cases.md).
162
+ - **Autres protocoles** : SMTP, FTP/FTPS, SFTP, SCP, POP3, IMAP, MQTT, TFTP,
163
+ FILE, SMB, WebSocket. Détail : [docs/protocols.md](docs/protocols.md).
164
+ - **Panorama complet** (ce qui est couvert et ce qui ne l'est pas) :
165
+ [docs/curl-coverage.md](docs/curl-coverage.md).
166
+
167
+ ## Feuille de route
168
+
169
+ - Protocoles restants : LDAP, TELNET.
170
+ - Auth : AWS SigV4, NTLM/Negotiate, Digest `qop=auth-int`.
171
+ - Requêtes conditionnelles (`-z`, `--etag-compare`), reprise `-C -` automatique.
172
+ - Générateurs de code supplémentaires (httpx, requests).
@@ -0,0 +1,62 @@
1
+ # Publier une version sur PyPI
2
+
3
+ La CI publie automatiquement sur PyPI quand on **pousse un tag de version**.
4
+ L'authentification utilise le **Trusted Publishing (OIDC)** de PyPI : **aucun
5
+ token n'est stocké** dans le dépôt.
6
+
7
+ ## Configuration (une seule fois)
8
+
9
+ ### 1. Déclarer le « trusted publisher » sur PyPI
10
+
11
+ Sur <https://pypi.org>, projet `urban-http` → *Settings* → *Publishing* (ou, avant
12
+ le tout premier envoi, <https://pypi.org/manage/account/publishing/> → *pending
13
+ publisher*), ajouter un éditeur GitHub Actions avec :
14
+
15
+ | Champ | Valeur |
16
+ |---|---|
17
+ | PyPI Project Name | `urban-http` |
18
+ | Owner | `gaelgael5` |
19
+ | Repository name | `urban-http` |
20
+ | Workflow name | `publish.yml` |
21
+ | Environment name | `pypi` |
22
+
23
+ (Pour TestPyPI, répéter la même chose sur <https://test.pypi.org> avec
24
+ l'environment `testpypi`.)
25
+
26
+ ### 2. Créer les environnements GitHub
27
+
28
+ Dans le dépôt → *Settings* → *Environments*, créer `pypi` (et `testpypi` si
29
+ souhaité). On peut y ajouter une règle de protection (révision obligatoire avant
30
+ publication).
31
+
32
+ ## Publier une version
33
+
34
+ 1. Mettre à jour la version dans `pyproject.toml` (`project.version`).
35
+ 2. Commiter et pousser sur `main`.
36
+ 3. Créer et pousser le tag correspondant :
37
+
38
+ ```bash
39
+ git tag v0.2.0
40
+ git push origin v0.2.0
41
+ ```
42
+
43
+ Le workflow `publish.yml` vérifie que le tag (`v0.2.0`) correspond bien à
44
+ `project.version` (`0.2.0`), construit le sdist + wheel, les passe à
45
+ `twine check`, puis publie sur PyPI.
46
+
47
+ ### Tester d'abord sur TestPyPI
48
+
49
+ Lancer manuellement le workflow *Publier sur PyPI* (onglet Actions →
50
+ *Run workflow*) en choisissant la cible `testpypi`.
51
+
52
+ ## Repli : token d'API
53
+
54
+ Si le Trusted Publishing n'est pas souhaité, créer un token sur PyPI, l'ajouter
55
+ en secret `PYPI_API_TOKEN`, et passer le mot de passe à l'action dans
56
+ `publish.yml` :
57
+
58
+ ```yaml
59
+ - uses: pypa/gh-action-pypi-publish@release/v1
60
+ with:
61
+ password: ${{ secrets.PYPI_API_TOKEN }}
62
+ ```