moyreq 0.1__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.
moyreq-0.1/LICENSE.txt ADDED
@@ -0,0 +1,2 @@
1
+ copyright 2026 auther ; god generous
2
+
moyreq-0.1/PKG-INFO ADDED
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: moyreq
3
+ Version: 0.1
4
+ Summary: god not generous
5
+ Author: god generous
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ License-File: LICENSE.txt
10
+ Requires-Dist: httpx
11
+ Requires-Dist: aioquic
12
+ Dynamic: author
13
+ Dynamic: classifier
14
+ Dynamic: license-file
15
+ Dynamic: requires-dist
16
+ Dynamic: summary
@@ -0,0 +1,369 @@
1
+ import asyncio
2
+ import json as json_module
3
+ from urllib.parse import urlparse, urlencode
4
+ import httpx
5
+ from aioquic.asyncio import connect
6
+ from aioquic.asyncio.protocol import QuicConnectionProtocol
7
+ from aioquic.h3.connection import H3Connection
8
+ from aioquic.h3.events import HeadersReceived, DataReceived
9
+ from aioquic.quic.configuration import QuicConfiguration
10
+
11
+ # HTTP/3
12
+
13
+ class HTTP3Protocol(QuicConnectionProtocol):
14
+
15
+ def __init__(self, *args, **kwargs):
16
+ super().__init__(*args, **kwargs)
17
+
18
+ self.h3 = H3Connection(self._quic)
19
+ self.events = asyncio.Queue()
20
+
21
+ def quic_event_received(self, event):
22
+
23
+ for http_event in self.h3.handle_event(event):
24
+ self.events.put_nowait(http_event)
25
+
26
+ async def request(self, method, url, headers=None, body=None):
27
+
28
+ parsed = urlparse(url)
29
+
30
+ stream_id = self._quic.get_next_available_stream_id()
31
+
32
+ path = parsed.path or "/"
33
+
34
+ if parsed.query:
35
+ path += "?" + parsed.query
36
+
37
+ request_headers = [
38
+ (b":method", method.encode()),
39
+ (b":scheme", parsed.scheme.encode()),
40
+ (b":authority", parsed.netloc.encode()),
41
+ (b":path", path.encode()),
42
+ ]
43
+
44
+ for key, value in (headers or {}).items():
45
+
46
+ key = key.lower()
47
+
48
+ # لا نرسل pseudo headers يدويًا
49
+ if key.startswith(":"):
50
+ continue
51
+
52
+ request_headers.append(
53
+ (
54
+ key.encode(),
55
+ str(value).encode()
56
+ )
57
+ )
58
+
59
+ self.h3.send_headers(
60
+ stream_id=stream_id,
61
+ headers=request_headers,
62
+ end_stream=body is None,
63
+ )
64
+
65
+ if body is not None:
66
+
67
+ self.h3.send_data(
68
+ stream_id=stream_id,
69
+ data=body,
70
+ end_stream=True,
71
+ )
72
+
73
+ self.transmit()
74
+
75
+ status_code = 0
76
+ response_headers = []
77
+ response_body = bytearray()
78
+
79
+ while True:
80
+
81
+ event = await self.events.get()
82
+
83
+ if isinstance(event, HeadersReceived):
84
+
85
+
86
+ for key, value in event.headers:
87
+
88
+ if key == b":status":
89
+
90
+ status_code = int(value)
91
+
92
+ elif not key.startswith(b":"):
93
+
94
+ response_headers.append(
95
+ (
96
+ key.decode(
97
+ errors="replace"
98
+ ),
99
+ value.decode(
100
+ errors="replace"
101
+ )
102
+ )
103
+ )
104
+
105
+ elif isinstance(event, DataReceived):
106
+ if event.stream_id == stream_id:
107
+ response_body.extend(event.data)
108
+
109
+ if event.stream_ended:
110
+ break
111
+
112
+
113
+ response = httpx.Response(
114
+ status_code=status_code,
115
+ headers=response_headers,
116
+ content=bytes(response_body),
117
+ request=httpx.Request(
118
+ method,
119
+ url
120
+ )
121
+ )
122
+
123
+ # تسجيل البروتوكول
124
+ response.extensions["http_version"] = b"HTTP/3"
125
+
126
+ return response
127
+
128
+
129
+ async def _http3_request(
130
+ method,
131
+ url,
132
+ headers=None,
133
+ params=None,
134
+ data=None,
135
+ json=None,
136
+ cookies=None,
137
+ ):
138
+
139
+ parsed = urlparse(url)
140
+
141
+ if parsed.scheme != "https":
142
+ raise ValueError(
143
+ "HTTP/3 requires HTTPS"
144
+ )
145
+
146
+ # Parameters
147
+ if params:
148
+
149
+ query = urlencode(
150
+ params,
151
+ doseq=True
152
+ )
153
+
154
+ url += (
155
+ "&" if parsed.query else "?"
156
+ ) + query
157
+
158
+ headers = dict(headers or {})
159
+
160
+ # Cookies
161
+ if cookies:
162
+
163
+ headers["cookie"] = "; ".join(
164
+ f"{key}={value}"
165
+ for key, value in cookies.items()
166
+ )
167
+
168
+ # JSON
169
+ body = data
170
+
171
+ if json is not None:
172
+
173
+ body = json_module.dumps(
174
+ json
175
+ ).encode()
176
+
177
+ headers.setdefault(
178
+ "content-type",
179
+ "application/json"
180
+ )
181
+
182
+ # String → bytes
183
+ if isinstance(body, str):
184
+ body = body.encode()
185
+
186
+ configuration = QuicConfiguration(
187
+ is_client=True,
188
+ alpn_protocols=["h3"],
189
+ )
190
+
191
+ async with connect(
192
+ parsed.hostname,
193
+ parsed.port or 443,
194
+ configuration=configuration,
195
+ create_protocol=HTTP3Protocol,
196
+ ) as protocol:
197
+ return await protocol.request(
198
+ method,
199
+ url,
200
+ headers,
201
+ body
202
+ )
203
+ # HTTP/2 + HTTP/1.1
204
+ def _httpx_request(
205
+ method,
206
+ url,
207
+ headers=None,
208
+ params=None,
209
+ data=None,
210
+ json=None,
211
+ cookies=None,
212
+ ):
213
+
214
+ with httpx.Client(
215
+ http2=True,
216
+ follow_redirects=True
217
+ ) as client:
218
+
219
+ return client.request(
220
+ method,
221
+ url,
222
+ headers=headers,
223
+ params=params,
224
+ content=data,
225
+ json=json,
226
+ cookies=cookies,
227
+ )
228
+
229
+
230
+ # MAIN REQUEST
231
+
232
+ def request(
233
+ method,
234
+ url,
235
+ headers=None,
236
+ params=None,
237
+ data=None,
238
+ json=None,
239
+ cookies=None,
240
+ ):
241
+
242
+ # HTTP/3
243
+
244
+ try:
245
+
246
+ return asyncio.run(
247
+ _http3_request(
248
+ method=method,
249
+ url=url,
250
+ headers=headers,
251
+ params=params,
252
+ data=data,
253
+ json=json,
254
+ cookies=cookies,
255
+ )
256
+ )
257
+
258
+ except Exception:
259
+ pass
260
+
261
+ # HTTP/2 → HTTP/1.1
262
+ return _httpx_request(
263
+ method=method,
264
+ url=url,
265
+ headers=headers,
266
+ params=params,
267
+ data=data,
268
+ json=json,
269
+ cookies=cookies,
270
+ )
271
+ # HTTP METHODS
272
+ def get(
273
+ url,
274
+ headers=None,
275
+ params=None,
276
+ data=None,
277
+ json=None,
278
+ cookies=None
279
+ ):
280
+
281
+ return request(
282
+ "GET",
283
+ url,
284
+ headers,
285
+ params,
286
+ data,
287
+ json,
288
+ cookies
289
+ )
290
+
291
+
292
+ def post(
293
+ url,
294
+ headers=None,
295
+ params=None,
296
+ data=None,
297
+ json=None,
298
+ cookies=None
299
+ ):
300
+
301
+ return request(
302
+ "POST",
303
+ url,
304
+ headers,
305
+ params,
306
+ data,
307
+ json,
308
+ cookies
309
+ )
310
+
311
+
312
+ def put(
313
+ url,
314
+ headers=None,
315
+ params=None,
316
+ data=None,
317
+ json=None,
318
+ cookies=None
319
+ ):
320
+
321
+ return request(
322
+ "PUT",
323
+ url,
324
+ headers,
325
+ params,
326
+ data,
327
+ json,
328
+ cookies
329
+ )
330
+
331
+
332
+ def patch(
333
+ url,
334
+ headers=None,
335
+ params=None,
336
+ data=None,
337
+ json=None,
338
+ cookies=None
339
+ ):
340
+
341
+ return request(
342
+ "PATCH",
343
+ url,
344
+ headers,
345
+ params,
346
+ data,
347
+ json,
348
+ cookies
349
+ )
350
+
351
+
352
+ def delete(
353
+ url,
354
+ headers=None,
355
+ params=None,
356
+ data=None,
357
+ json=None,
358
+ cookies=None
359
+ ):
360
+
361
+ return request(
362
+ "DELETE",
363
+ url,
364
+ headers,
365
+ params,
366
+ data,
367
+ json,
368
+ cookies
369
+ )
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: moyreq
3
+ Version: 0.1
4
+ Summary: god not generous
5
+ Author: god generous
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ License-File: LICENSE.txt
10
+ Requires-Dist: httpx
11
+ Requires-Dist: aioquic
12
+ Dynamic: author
13
+ Dynamic: classifier
14
+ Dynamic: license-file
15
+ Dynamic: requires-dist
16
+ Dynamic: summary
@@ -0,0 +1,8 @@
1
+ LICENSE.txt
2
+ setup.py
3
+ moyreq/__init__.py
4
+ moyreq.egg-info/PKG-INFO
5
+ moyreq.egg-info/SOURCES.txt
6
+ moyreq.egg-info/dependency_links.txt
7
+ moyreq.egg-info/requires.txt
8
+ moyreq.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ httpx
2
+ aioquic
@@ -0,0 +1 @@
1
+ moyreq
moyreq-0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
moyreq-0.1/setup.py ADDED
@@ -0,0 +1,18 @@
1
+ import setuptools
2
+
3
+ setuptools.setup(
4
+ name="moyreq",
5
+ version="0.1",
6
+ author="god generous",
7
+ description="god not generous",
8
+ packages=setuptools.find_packages(),
9
+ install_requires=[
10
+ "httpx",
11
+ "aioquic",
12
+ ],
13
+ classifiers=[
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ "License :: OSI Approved :: MIT License",
17
+ ],
18
+ )