httpee 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
httpee/__init__.py ADDED
@@ -0,0 +1,204 @@
1
+ """httpee — HTTP для людей. Теперь пайплайнами.
2
+
3
+ Тонкая обёртка над httpx для eelang: запрос собирается пайплайном, результат
4
+ проверяется паттернами::
5
+
6
+ from httpee import POST, headers, Response
7
+
8
+ user =
9
+ {'name': 'kenneth', 'role': 'admin'}
10
+ ..headers: {'Authorization': 'Bearer ...'}
11
+ ..POST: 'https://api.example.com/users'
12
+ ..assert:
13
+ Response(status=201)
14
+
15
+ page =
16
+ httpee()
17
+ ..GET: 'https://api.example.com/users'
18
+ ..assert:
19
+ Response(status=200)
20
+
21
+ 4xx/5xx исключений не вызывают — статус проверяется ``..assert:``/``..match:``.
22
+ """
23
+
24
+ import httpx
25
+
26
+ __version__ = "0.1.0"
27
+
28
+
29
+ class Response(httpx.Response):
30
+ """httpx.Response + свойство ``status`` для паттерна ``Response(status=200)``.
31
+
32
+ Остальное — родное: ``.json()``, ``.text``, ``.content``, ``.headers``,
33
+ ``.raise_for_status()``.
34
+ """
35
+
36
+ @property
37
+ def status(self):
38
+ return self.status_code
39
+
40
+
41
+ class _Request:
42
+ """Накопленное состояние запроса. Пользователю не виден."""
43
+
44
+ __slots__ = ('method', 'url', 'body', 'json_body', 'headers', 'params',
45
+ 'timeout', 'cookies', 'auth')
46
+
47
+ def __init__(self, body=None):
48
+ self.method = None
49
+ self.url = None
50
+ self.body = body
51
+ self.json_body = None
52
+ self.headers = {}
53
+ self.params = {}
54
+ self.timeout = None
55
+ self.cookies = None
56
+ self.auth = None
57
+
58
+
59
+ def httpee():
60
+ """Пустой запрос: httpee() ..GET: url — когда тела нет."""
61
+ return _Request()
62
+
63
+
64
+ def _ensure(value):
65
+ """Тело или _Request → _Request; на Response — внятный TypeError."""
66
+ if isinstance(value, Response):
67
+ raise TypeError(
68
+ 'это уже Response — модификаторы и глаголы идут до выполнения запроса')
69
+ return value if isinstance(value, _Request) else _Request(body=value)
70
+
71
+
72
+ def _wrap(resp):
73
+ """httpx.Response → httpee.Response (для class-паттернов)."""
74
+ return Response(
75
+ resp.status_code,
76
+ headers=resp.headers,
77
+ content=resp.content,
78
+ request=resp.request,
79
+ extensions=resp.extensions,
80
+ history=resp.history,
81
+ default_encoding=resp.default_encoding,
82
+ )
83
+
84
+
85
+ def _execute(req):
86
+ """Отправить _Request новым httpx.Client и вернуть Response."""
87
+ if req.url is None:
88
+ raise TypeError(
89
+ 'запрос без адреса: задайте ..url: или вызовите ..GET:/..POST: с адресом')
90
+ method = (req.method or 'GET').upper()
91
+ kwargs = {}
92
+ if req.json_body is not None:
93
+ kwargs['json'] = req.json_body
94
+ else:
95
+ body = req.body
96
+ if body is None:
97
+ pass
98
+ elif isinstance(body, dict):
99
+ kwargs['data'] = body
100
+ elif isinstance(body, str):
101
+ kwargs['content'] = body.encode()
102
+ elif isinstance(body, (bytes, bytearray, memoryview)):
103
+ kwargs['content'] = bytes(body)
104
+ else:
105
+ raise TypeError(
106
+ f'неподдерживаемое тело запроса: {type(body).__name__} '
107
+ '(ожидаются dict/str/bytes)')
108
+ with httpx.Client(timeout=req.timeout) as client:
109
+ resp = client.request(method, req.url, headers=req.headers or None,
110
+ params=req.params or None, cookies=req.cookies,
111
+ auth=req.auth, **kwargs)
112
+ return _wrap(resp)
113
+
114
+
115
+ def _verb(method):
116
+ """Глагол: ..GET: url → GET(url, значение) — выполняет запрос сразу."""
117
+
118
+ def run(url, value):
119
+ req = _ensure(value)
120
+ req.method = method
121
+ req.url = url
122
+ return _execute(req)
123
+
124
+ run.__name__ = method
125
+ run.__qualname__ = method
126
+ run.__doc__ = (
127
+ f'Выполнить {method}-запрос: ..{method}: url. '
128
+ 'Значение пайплайна — тело или накопленный запрос.')
129
+ return run
130
+
131
+
132
+ GET = _verb('GET')
133
+ POST = _verb('POST')
134
+ PUT = _verb('PUT')
135
+ PATCH = _verb('PATCH')
136
+ DELETE = _verb('DELETE')
137
+ HEAD = _verb('HEAD')
138
+ OPTIONS = _verb('OPTIONS')
139
+
140
+
141
+ def send(value):
142
+ """Выполнить запрос с заранее заданными ..method:/..url: (..send())."""
143
+ return _execute(_ensure(value))
144
+
145
+
146
+ def headers(headers_value, value):
147
+ """..headers: {...} — заголовки запроса (повторные вызовы дополняют)."""
148
+ req = _ensure(value)
149
+ req.headers.update(dict(headers_value))
150
+ return req
151
+
152
+
153
+ def query(params_value, value):
154
+ """..query: {...} — query-параметры URL (повторные вызовы дополняют)."""
155
+ req = _ensure(value)
156
+ req.params.update(dict(params_value))
157
+ return req
158
+
159
+
160
+ def timeout(timeout_value, value):
161
+ """..timeout: 5.0 — таймаут запроса в секундах."""
162
+ req = _ensure(value)
163
+ req.timeout = timeout_value
164
+ return req
165
+
166
+
167
+ def cookies(cookies_value, value):
168
+ """..cookies: {...} — куки запроса."""
169
+ req = _ensure(value)
170
+ req.cookies = cookies_value
171
+ return req
172
+
173
+
174
+ def auth(auth_value, value):
175
+ """..auth: ('user', 'pass') — basic-auth; можно httpx.Auth."""
176
+ req = _ensure(value)
177
+ req.auth = auth_value
178
+ return req
179
+
180
+
181
+ def json(json_value, value):
182
+ """..json: {...} — тело JSON-ом (побеждает над телом пайплайна)."""
183
+ req = _ensure(value)
184
+ req.json_body = json_value
185
+ return req
186
+
187
+
188
+ def method(method_value, value):
189
+ """..method: 'PATCH' — метод заранее; выполняется ..send()."""
190
+ req = _ensure(value)
191
+ req.method = str(method_value).upper()
192
+ return req
193
+
194
+
195
+ def url(url_value, value):
196
+ """..url: 'https://...' — адрес заранее; выполняется ..send()."""
197
+ req = _ensure(value)
198
+ req.url = url_value
199
+ return req
200
+
201
+
202
+ __all__ = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS',
203
+ 'send', 'headers', 'query', 'timeout', 'cookies', 'auth', 'json',
204
+ 'method', 'url', 'Response', 'httpee']
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: httpee
3
+ Version: 0.1.0
4
+ Summary: httpee — thin httpx wrapper for eelang pipelines
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: httpx>=0.27
10
+ Dynamic: license-file
11
+
12
+ # httpee
13
+
14
+ `httpee` is a thin wrapper around [httpx](https://www.python-httpx.org/) for
15
+ [eelang](https://github.com/eel-lang/eelang-py): requests are written as pipelines,
16
+ responses are checked with patterns.
17
+
18
+ ```python
19
+ from httpee import POST, headers, httpee, Response
20
+
21
+ user =
22
+ {'name': 'kenneth', 'role': 'admin'} # dict → form-encoded, like requests data=
23
+ ..headers: {'Authorization': f'Bearer {token}'}
24
+ ..POST: 'https://api.example.com/users'
25
+ ..assert:
26
+ Response(status=201)
27
+
28
+ page =
29
+ httpee() # empty request — no body
30
+ ..query: {'page': 2}
31
+ ..GET: 'https://api.example.com/users'
32
+ ..assert:
33
+ Response(status=200)
34
+ ```
35
+
36
+ ## Cheatsheet
37
+
38
+ ```python
39
+ from httpee import (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, send,
40
+ httpee, headers, query, timeout, cookies, auth, json,
41
+ method, url, Response)
42
+ from eelang import MatchError
43
+ ```
44
+
45
+ ### A request is a pipeline
46
+
47
+ ```python
48
+ r =
49
+ {'name': 'kenneth', 'role': 'admin'} # dict → form-encoded
50
+ ..headers: {'X-Key': v} # headers (repeats add keys)
51
+ ..query: {'page': 2} # query parameters (repeats add keys)
52
+ ..POST: 'https://api.example.com/users' # verb performs the request
53
+ ..assert:
54
+ Response(status=201) # failure → MatchError
55
+ ```
56
+
57
+ ### Bodies
58
+
59
+ | Value | Sent as |
60
+ |---|---|
61
+ | dict | form-encoded (`data=`, like requests) |
62
+ | str | text |
63
+ | bytes | raw bytes |
64
+ | `httpee()` | empty request — no body |
65
+ | `..json: {…}` | JSON — takes precedence over the pipeline body |
66
+
67
+ ### Dynamic method — `..send()`
68
+
69
+ ```python
70
+ r =
71
+ {'role': 'moderator'}
72
+ ..method: m # any method, even from a variable
73
+ ..url: 'https://…'
74
+ ..send() # without ..method: defaults to GET
75
+ ..assert:
76
+ Response(status=200)
77
+ ```
78
+
79
+ ### Pattern checks
80
+
81
+ ```python
82
+ # ..assert: — shape check, binds variables, failure → MatchError
83
+ p =
84
+ httpee()
85
+ ..GET: url
86
+ ..assert:
87
+ Response(status=s) # s is available afterwards
88
+
89
+ # ..match: — branch on status; no match → the value is unchanged
90
+ verdict =
91
+ httpee()
92
+ ..GET: url
93
+ ..match:
94
+ case Response(status=200): 'ok'
95
+ case Response(status=n): f'HTTP {n}'
96
+ ```
97
+
98
+ ### Errors
99
+
100
+ - 4xx/5xx do **not** raise: check the status with a pattern.
101
+ - Network errors and timeouts are httpx exceptions (`ConnectError`, `TimeoutException`).
102
+ - `Response` is `httpx.Response` plus `.status`: `.json()`, `.text`, `.headers` work as usual.
103
+
104
+ ### Modifiers at a glance
105
+
106
+ `..headers:` · `..query:` · `..timeout: 5.0` · `..cookies:` · `..auth: (u, p)` · `..json:` · `..method:` · `..url:`
107
+
108
+ ## How it works
109
+
110
+ - The pipeline's initial value is the request body: dict → form-encoded, str → text,
111
+ bytes → raw bytes. JSON goes through an explicit `..json: {…}`. A request without
112
+ a body starts with `httpee()`.
113
+ - Modifiers accumulate parameters (repeatable, in any order); the verb at the end
114
+ performs the request and returns a `Response`.
115
+ - 4xx/5xx do not raise — the status is checked with a pattern; a failed `..assert:`
116
+ raises `MatchError`. Network errors and timeouts are httpx exceptions.
117
+
118
+ ## Verbs
119
+
120
+ | Verb | Example | What it does |
121
+ |---|---|---|
122
+ | `GET` … `OPTIONS` | `..GET: url` | performs the request with this method |
123
+ | `send` | `..send()` | performs the request with a previously set `..method:`/`..url:` |
124
+
125
+ The method can be set up front — and can be anything:
126
+
127
+ ```python
128
+ patch =
129
+ {'role': 'moderator'}
130
+ ..method: 'PATCH' # or from a variable
131
+ ..url: 'https://api.example.com/users/1'
132
+ ..send()
133
+ ..assert:
134
+ Response(status=200)
135
+ ```
136
+
137
+ ## Modifiers
138
+
139
+ | Modifier | Example | What it does |
140
+ |---|---|---|
141
+ | `headers` | `..headers: {'X-Key': v}` | headers (repeats add keys) |
142
+ | `query` | `..query: {'page': 2}` | URL query parameters (repeats add keys) |
143
+ | `timeout` | `..timeout: 5.0` | request timeout, seconds |
144
+ | `cookies` | `..cookies: {'sid': v}` | cookies |
145
+ | `auth` | `..auth: ('user', 'pass')` | basic auth (any `httpx.Auth` works) |
146
+ | `json` | `..json: {…}` | JSON body (takes precedence over the pipeline body) |
147
+ | `method` | `..method: 'PATCH'` | method up front — for `..send()` |
148
+ | `url` | `..url: 'https://…'` | URL up front — for `..send()` |
149
+
150
+ ## Installation
151
+
152
+ ```sh
153
+ pip install httpee
154
+ ```
155
+
156
+ From source:
157
+
158
+ ```sh
159
+ git clone https://github.com/eel-lang/httpee
160
+ pip install ./httpee
161
+ ```
162
+
163
+ Requires CPython 3.15 + [eelang](https://github.com/eel-lang/eelang-py) to run `.eel`
164
+ files — the package itself is plain Python, and httpx is its only dependency.
165
+ Works under gevent (`gevent.monkey.patch_all()`).
166
+
167
+ ## Tests
168
+
169
+ ```sh
170
+ python -m unittest discover -s tests
171
+ ```
172
+
173
+ ## License
174
+
175
+ MIT (`LICENSE`).
@@ -0,0 +1,6 @@
1
+ httpee/__init__.py,sha256=7J7iS6VTl8dG970V0ShkU5JneNXWkfOgozDqxGowIe4,6421
2
+ httpee-0.1.0.dist-info/licenses/LICENSE,sha256=e5O-ASdm-ay53Ombr3FGTuTCHitjde-7X9cOqWEA5nw,1063
3
+ httpee-0.1.0.dist-info/METADATA,sha256=LfuLdXYEz79ScK68GQLAPnNcdUuwJ7s7NjPAka2jKig,5125
4
+ httpee-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
5
+ httpee-0.1.0.dist-info/top_level.txt,sha256=4CRnPD5HgDwCZ2xgs5z306QWHMgAGNGzZsk7-5VT4V4,7
6
+ httpee-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pwtail
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 @@
1
+ httpee