Skip to content

Commit 2fcf9bc

Browse files
authored
Feature / Bugfix - Implement a better Request/Response Handler, HttpError Exception, Parse_Response (#22)
* Remove! Remove request_async, add httpx. * Fixup! Reimplement Req.request method, implement parse_response, HttpError exception. * Fixup! Remove space. Ref: #22
1 parent 32eaebb commit 2fcf9bc

File tree

10 files changed

+111
-62
lines changed

10 files changed

+111
-62
lines changed

pyblox3/src/abtesting.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
root = "https://abtesting.roblox.com"
1212

13+
Req = Req()
1314

1415
class AbTesting_v1:
1516

pyblox3/src/accountInformation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
#
88

99
from .util import *
10-
import json
10+
11+
Req = Req()
1112

1213
class accountInformation_v1:
1314

pyblox3/src/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
#
88

99
from .util import *
10-
import json
11-
import requests
10+
11+
Req = Req()
1212

1313
root = "https://auth.roblox.com"
1414

pyblox3/src/games.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
#
88

99
from .util import *
10-
import json
10+
11+
Req = Req()
1112

1213
class Games_v1:
1314

pyblox3/src/groups.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
#
88

99
from .util import *
10-
import json
10+
11+
Req = Req()
1112

1213
class Groups_v1:
1314

pyblox3/src/privateMessages.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
#
88

99
from .util import *
10-
import json
10+
11+
Req = Req()
1112

1213
class privateMessages_v1:
1314

pyblox3/src/users.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
#
88

99
from .util import *
10-
import json
10+
11+
Req = Req()
1112

1213
class Users_v1:
1314

pyblox3/src/util/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
# Some nifty methods that I like to keep handy because
1010
# I write them over and over for every project.
1111
# Bet you relate..
12+
#
13+
# Message from Sanjay 2024: Who the hell uses
14+
# the word nifty..?
1215

1316
# Parent Class Modules
1417
from .req import Req

pyblox3/src/util/req.py

Lines changed: 94 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,48 @@
66
# Copyright © 2019- Sanjay-B(Sanjay Bhadra). All rights reserved.
77
#
88

9-
import asyncio
10-
import requests_async as requests
11-
import json
9+
import httpx
10+
11+
class HttpError(Exception):
12+
"""
13+
Base class that represents an HTTP Error in the form of an exception.
14+
"""
15+
def __init__(self, kind="Generic", message="Generic Error", url="Somewhere"):
16+
"""
17+
Creates an exception with specified message.
18+
If not message is provided, it defaults to "Generic Error".
19+
20+
@param kind: str. Represents the the kind of exception.
21+
@param message: str. Represents the error message of the exception.
22+
@param url: str. Represents the url the error occured at.
23+
"""
24+
self.kind = kind
25+
self.message = message
26+
self.url = url
27+
super().__init__(self.message)
1228

1329

1430
class Req:
15-
16-
token = None
17-
18-
'''
19-
@method
20-
request(t=String,url=String,*args,**kwargs)
21-
22-
@params
23-
t = Type of Request(GET or POST)
24-
url = URL
25-
26-
@optionals
27-
28-
payload = Dictionary Payload
29-
header = Request Headers
30-
cookies = cookie
31-
32-
@result
33-
Returns request response wholesale as a tuple
34-
35-
@example 1:
31+
"""
32+
Base class that holds HTTP request and response mechanisms.
33+
"""
34+
35+
async def request(self, t: str, url: str, payload=None, header={}, cookies={}):
36+
"""
37+
Creates, executes and returns a request in the form of a response.
38+
Throws an exception if conditions are not met.
39+
40+
@param t: Str. Type of request. ie. GET, POST, PATCH, DELETE or DEL.
41+
Can also be lowercase, snakecase, etc.
42+
@param url: Str. URL to send the request.
43+
@param payload: Dict or None. Represents the payload or data to send
44+
the request with.
45+
@param header: Dict. Represents the headers to send the request with.
46+
@param cookies: Dict. Represents the cookies to send the request with.
47+
48+
@return: Tuple.
49+
50+
@example:
3651
3752
# GET Request
3853
response = Req.request(t="GET",url="http://httpbin.org/get")
@@ -42,9 +57,8 @@ class Req:
4257
4358
# And so on... its a tuple so this also works as a shorthand:
4459
response = Req.request(t="GET",url="http://httpbin.org/get")[0]
45-
46-
47-
@example 2:
60+
61+
@example:
4862
4963
# POST Request
5064
data = {"Username":"JohnDoe","Password":"JaneDoe"}
@@ -57,30 +71,56 @@ class Req:
5771
5872
# Once again, this works as a shorthand:
5973
response = Req.request(t="POST",url"http://httpbin.org/post",payload=data)[0]
60-
'''
61-
62-
async def request(t=str,url=str,*args,**kwargs):
63-
payload = kwargs.get('payload',None)
64-
header = kwargs.get('header',{})
65-
cookies = kwargs.get('cookies',{})
66-
67-
if t == "GET" or "POST" or "PATCH" or "DEL":
68-
method = t.replace('DEL', 'delete')
69-
response = await requests.request("POST", "https://www.roblox.com/authentication/signoutfromallsessionsandreauthenticate", data=None, headers=header, cookies=cookies or {})
70-
header['X-CSRF-TOKEN'] = response.headers['X-CSRF-TOKEN']
71-
request = await requests.request(method, str(url), data=payload or None, headers=header, cookies=cookies)
72-
statusCode = request.status_code
73-
content = request.content
74-
headers = request.headers
75-
encoding = request.encoding
76-
json = request.json()
77-
78-
# resend request with xcsrf token
79-
if statusCode == 403 and 'X-CSRF-TOKEN' in headers:
80-
kwargs['headers']['X-CSRF-TOKEN'] = headers['X-CSRF-TOKEN']
81-
return await request(t=t, url=url, *args, **kwargs)
82-
83-
if statusCode == 200:
84-
return statusCode, content, headers, encoding, json
85-
else:
86-
return statusCode, content, headers, encoding, json["errors"][0]
74+
"""
75+
async with httpx.AsyncClient(cookies=cookies) as client:
76+
77+
# TODO: Backwards Compatibility (will be renamed to this later)
78+
kind = t.upper()
79+
headers = header
80+
81+
# Obtain our initial token (fresh)
82+
response = await client.post(url="https://www.roblox.com/authentication/signoutfromallsessionsandreauthenticate", data=None, headers=headers)
83+
csrf_token = response.headers.get("X-CSRF-TOKEN")
84+
if not csrf_token:
85+
raise HttpError("POST Request", "Initial X-CSRF-TOKEN was not found in headers, aborted", url)
86+
87+
# Perform actual request
88+
headers["X-CSRF-TOKEN"] = csrf_token
89+
if kind == "GET":
90+
response = await client.get(url, headers=headers)
91+
elif kind == "POST":
92+
if not cookies:
93+
raise HttpError("POST Request", "Endpoint requires account cookie / authentication", url)
94+
elif not payload:
95+
raise HttpError("POST Request", "Endpoint requires payload / request body", url)
96+
response = await client.post(url=url, data=payload, headers=headers)
97+
elif kind == "PATCH":
98+
if not cookies:
99+
raise HttpError("PATCH Request", "Endpoint requires account cookie / authentication", url)
100+
elif not payload:
101+
raise HttpError("PATCH Request", "Endpoint requires payload / request body", url)
102+
response = await client.patch(url=url, data=payload, headers=headers)
103+
elif kind == ("DEL" or "DELETE"):
104+
if not cookies:
105+
raise HttpError("DELETE Request", "Endpoint requires account cookie / authentication", url)
106+
response = await client.delete(url=url, payload=payload, headers=headers)
107+
return self.parse_response(response)
108+
109+
def parse_response(self, response):
110+
"""
111+
Parses respone into expected tuple format.
112+
113+
@param response: Response. The response object from the request that has been performed.
114+
115+
@return: tuple. If the status code is 200, the return is Tuple(status_code, content, headers, encoding, json).
116+
If the status code is not 200, the return is Tuple(status_code, content, headers, encoding, json["errors"][0]).
117+
Note that a status code of 200 means the request is successful. Otherwise, an error has occured.
118+
"""
119+
status_code = response.status_code
120+
content = response.content
121+
headers = response.headers
122+
encoding = response.encoding
123+
json = response.json()
124+
if status_code == 200:
125+
return status_code, content, headers, encoding, json
126+
return status_code, content, headers, encoding, json["errors"][0]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
aiohttp
22
asyncio
3-
requests_async
3+
httpx
44
pytest

0 commit comments

Comments
 (0)