Skip to content

Commit 8568137

Browse files
Merge pull request #4 from thewebscraping/develop
Develop
2 parents 6ac9a87 + 5978b23 commit 8568137

38 files changed

+647
-91
lines changed

.coveragerc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[run]
2+
omit = tests/*

.editorconfig

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# http://editorconfig.org
2+
3+
root = true
4+
5+
[*]
6+
charset = utf-8
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.{py,rst,ini}]
12+
indent_style = space
13+
indent_size = 4
14+
15+
[*.{html,css,scss,json,yml,xml}]
16+
indent_style = space
17+
indent_size = 2
18+
19+
[*.md]
20+
trim_trailing_whitespace = false
21+
22+
[Makefile]
23+
indent_style = tab

.github/workflows/ci.yml

+11-6
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ jobs:
1111
build:
1212
runs-on: ubuntu-latest
1313
strategy:
14-
max-parallel: 4
14+
fail-fast: false
15+
max-parallel: 3
1516
matrix:
16-
python-version: ['3.9', '3.10', '3.11']
17+
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
1718
steps:
1819
- uses: actions/checkout@v4
1920
- name: Set up Python ${{ matrix.python-version }}
@@ -23,11 +24,15 @@ jobs:
2324

2425
- name: Install Dependencies
2526
run: |
26-
python -m pip install --upgrade pip
27-
pip install -r requirements.txt
27+
make init
2828
29-
- name: Run pre-commit
30-
uses: pre-commit/[email protected]
29+
- name: Lint
30+
run: |
31+
make lint
32+
33+
- name: Tests
34+
run: |
35+
make pytest
3136
3237
deploy:
3338
needs: build

CHANGELOG.md

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
11
Release History
22
===============
33

4+
1.0.3 (2024-12-11)
5+
-------------------
6+
**Improvements:**
7+
8+
- Add unit tests.
9+
- Improve document.
10+
11+
**Bugfixes:**
12+
13+
- Fix timeout.
14+
- Fix missing port redirection.
15+
16+
17+
1.0.3 (2024-12-05)
18+
-------------------
19+
**Improvements**
20+
21+
- improve document.
22+
23+
**Bugfixes**
24+
25+
- Fix multipart encoders, cross share auth.
26+
27+
1.0.2 (2024-12-05)
28+
-------------------
29+
**Improvements**
30+
- Download specific TLS library versions.
31+
- Add a document.
32+
433
1.0.1 (2024-12-04)
534
-------------------
6-
* First release
35+
- First release

Makefile

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
.PHONY: docs
22
init:
3-
python -m pip install -r requirements.txt
3+
python -m pip install --upgrade pip
4+
python -m pip install -r requirements-dev.txt
5+
6+
test:
7+
tox -p
8+
rm -rf *.egg-info
49

510
test-readme:
611
python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and CHANGELOG.md ok") || echo "Invalid markup in README.md or CHANGELOG.md!"
@@ -10,11 +15,25 @@ lint:
1015
python -m isort tls_requests
1116
python -m flake8 tls_requests
1217

18+
pytest:
19+
python -m pytest tests
20+
21+
coverage:
22+
python -m pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=tls_requests tests
23+
24+
docs:
25+
mkdocs serve
26+
27+
publish-test-pypi:
28+
python -m pip install -r requirements-dev.txt
29+
python -m pip install 'twine>=6.0.1'
30+
python setup.py sdist bdist_wheel
31+
twine upload --repository testpypi dist/*
32+
rm -rf build dist .egg wrapper_tls_requests.egg-info
33+
1334
publish-pypi:
35+
python -m pip install -r requirements-dev.txt
1436
python -m pip install 'twine>=6.0.1'
1537
python setup.py sdist bdist_wheel
1638
twine upload dist/*
1739
rm -rf build dist .egg wrapper_tls_requests.egg-info
18-
19-
docs:
20-
mkdocs serve

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# TLS REQUESTS
2-
**A powerful and lightweight Python library for making secure and reliable HTTP/TLS Fingerprint requests.**
1+
# TLS Requests
2+
TLS Requests is a powerful Python library for secure HTTP requests, offering browser-like TLS fingerprinting, anti-bot bypassing, and high performance.
33

44
* * *
55

docs/advanced/hooks.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ client.hooks = {
100100
Best Practices
101101
--------------
102102

103-
1. **Access Content**: Use `.read()` or `await read()` in asynchronous contexts to access `response.content` before returning it.
103+
1. **Access Content**: Use `.read()` or `await .aread()` in asynchronous contexts to access `response.content` before returning it.
104104
2. **Always Use Lists:** Hooks must be registered as **lists of callables**, even if you are adding only one function.
105105
3. **Combine Hooks:** You can register multiple hooks for the same event type to handle various concerns, such as logging and error handling.
106106
4. **Order Matters:** Hooks are executed in the order they are registered.

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[build-system]
22
requires = ['setuptools>=40.8.0']
33
build-backend = 'setuptools.build_meta'
4+
5+
[tool.pytest.ini_options]
6+
asyncio_mode = "auto"

requirements-dev.txt

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-r requirements.txt
2+
3+
# Documentation
4+
mkdocs==1.6.1
5+
mkautodoc==0.2.0
6+
mkdocs-material==9.5.39
7+
8+
# Packaging
9+
setuptools~=75.3.0
10+
twine~=6.0.1
11+
12+
# Tests & Linting
13+
pre-commit==3.7.0
14+
black==24.3.0
15+
coverage[toml]==7.6.1
16+
pre-commit==3.7.0
17+
isort==5.13.2
18+
flake8==7.1.1
19+
mypy==1.11.2
20+
pytest==8.3.3
21+
pytest-asyncio==0.24.0
22+
pytest-cov==6.0.0
23+
pytest_httpserver==1.1.0
24+
Werkzeug==3.1.3
25+
tox==4.23.2

requirements.txt

+1-19
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,4 @@
22
chardet~=5.2.0
33
requests~=2.32.3
44
tqdm~=4.67.1
5-
6-
# Documentation
7-
mkdocs==1.6.1
8-
mkautodoc==0.2.0
9-
mkdocs-material==9.5.39
10-
11-
# Packaging
12-
setuptools~=75.3.0
13-
twine~=6.0.1
14-
15-
# Tests & Linting
16-
pre-commit==3.7.0
17-
black==24.3.0
18-
flake8==7.0.0
19-
coverage[toml]==7.6.1
20-
pre-commit==3.7.0
21-
isort==5.13.2
22-
mypy==1.11.2
23-
pytest==8.3.3
5+
idna~=3.10

setup.cfg

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ classifiers =
2121
Programming Language :: Python :: 3.10
2222
Programming Language :: Python :: 3.11
2323
Programming Language :: Python :: 3.12
24+
Programming Language :: Python :: 3.13
2425
Programming Language :: Python :: 3 :: Only
2526
Programming Language :: Python :: Implementation :: CPython
2627
Programming Language :: Python :: Implementation :: PyPy
@@ -29,7 +30,7 @@ classifiers =
2930

3031
project_urls =
3132
Changelog = https://github.com/thewebscraping/tls-requests/blob/main/CHANGELOG.md
32-
Documentation = https://github.com/thewebscraping/tls-requests
33+
Documentation = https://thewebscraping.github.io/tls-requests/
3334
Source = https://github.com/thewebscraping/tls-requests
3435
Homepage = https://github.com/thewebscraping/tls-requests
3536

tests/conftest.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import tls_requests
2+
3+
pytest_plugins = ['pytest_httpserver', 'pytest_asyncio']
4+
5+
6+
def pytest_configure(config):
7+
tls_requests.TLSLibrary.load()

tests/files/__init__.py

Whitespace-only changes.

tests/files/coingecko.png

36.4 KB
Loading

tests/test_api.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import tls_requests
2+
3+
RESPONSE_BYTE = b"Hello World!"
4+
RESPONSE_TEXT = "Hello World!"
5+
6+
7+
def assert_response(response):
8+
assert response.status_code, 200
9+
assert response.reason, "OK"
10+
assert response.text, RESPONSE_TEXT
11+
assert response.content, RESPONSE_BYTE
12+
13+
14+
def make_request(request_fn, httpserver, is_assert_response: bool = True):
15+
httpserver.expect_request("/api").respond_with_data(RESPONSE_BYTE)
16+
response = request_fn(httpserver.url_for('/api'))
17+
if is_assert_response:
18+
assert_response(response)
19+
20+
return response
21+
22+
23+
def test_get(httpserver):
24+
make_request(tls_requests.get, httpserver)
25+
26+
27+
def test_post(httpserver):
28+
make_request(tls_requests.post, httpserver)
29+
30+
31+
def test_put(httpserver):
32+
make_request(tls_requests.put, httpserver)
33+
34+
35+
def test_patch(httpserver):
36+
make_request(tls_requests.patch, httpserver)
37+
38+
39+
def test_delete(httpserver):
40+
make_request(tls_requests.delete, httpserver)
41+
42+
43+
def test_options(httpserver):
44+
make_request(tls_requests.options, httpserver)
45+
46+
47+
def test_head(httpserver):
48+
response = make_request(tls_requests.head, httpserver, False)
49+
assert response.status_code == 200
50+
assert response.reason == "OK"
51+
assert response.text == ""
52+
assert response.content == b""

tests/test_auth.py

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from base64 import b64encode
2+
3+
import pytest
4+
5+
import tls_requests
6+
7+
auth = ("user", "pass")
8+
AUTH_TOKEN = "Basic %s" % b64encode(b":".join([s.encode() for s in auth])).decode()
9+
AUTH_HEADERS = {"authorization": AUTH_TOKEN}
10+
AUTH_FUNCTION_KEY = "x-authorization"
11+
AUTH_FUNCTION_VALUE = "123456"
12+
AUTH_FUNCTION_HEADERS = {AUTH_FUNCTION_KEY: AUTH_FUNCTION_VALUE}
13+
14+
15+
def auth_function(request):
16+
request.headers.update(AUTH_FUNCTION_HEADERS)
17+
18+
19+
@pytest.fixture
20+
def auth_url(httpserver):
21+
return httpserver.url_for('/auth')
22+
23+
24+
@pytest.fixture
25+
def http_auth_function(httpserver):
26+
httpserver.expect_request("/auth", headers=AUTH_FUNCTION_HEADERS).respond_with_data()
27+
return httpserver
28+
29+
30+
@pytest.fixture
31+
def http_auth(httpserver):
32+
httpserver.expect_request("/auth", headers=AUTH_HEADERS).respond_with_data()
33+
return httpserver
34+
35+
36+
def test_auth(http_auth, auth_url):
37+
response = tls_requests.get(auth_url, auth=auth)
38+
assert response.status_code == 200
39+
assert response.request.headers["Authorization"] == AUTH_TOKEN
40+
41+
42+
def test_auth_function(http_auth_function, auth_url):
43+
response = tls_requests.get(auth_url, auth=auth_function)
44+
assert response.status_code == 200
45+
assert response.request.headers[AUTH_FUNCTION_KEY] == AUTH_FUNCTION_VALUE
46+
47+
48+
def test_client_auth(http_auth, auth_url):
49+
with tls_requests.Client(auth=auth) as client:
50+
response = client.get(auth_url)
51+
52+
assert response.status_code == 200
53+
assert bool(response.closed == client.closed) is True
54+
assert response.request.headers["Authorization"] == AUTH_TOKEN
55+
56+
57+
def test_client_auth_cross_sharing(http_auth, auth_url):
58+
with tls_requests.Client(auth=('1', '2')) as client:
59+
response = client.get(auth_url, auth=auth)
60+
61+
assert response.status_code == 200
62+
assert bool(response.closed == client.closed) is True
63+
assert response.request.headers["Authorization"] == AUTH_TOKEN
64+
65+
66+
def test_client_auth_function_cross_sharing(http_auth_function, auth_url):
67+
with tls_requests.Client(auth=auth) as client:
68+
response = client.get(auth_url, auth=auth_function)
69+
70+
assert response.status_code == 200
71+
assert bool(response.closed == client.closed) is True
72+
assert response.request.headers[AUTH_FUNCTION_KEY] == AUTH_FUNCTION_VALUE
73+
74+
75+
@pytest.mark.asyncio
76+
async def test_async_auth(http_auth, auth_url):
77+
async with tls_requests.AsyncClient(auth=auth) as client:
78+
response = await client.get(auth_url)
79+
80+
assert response.status_code == 200
81+
assert bool(response.closed == client.closed) is True
82+
assert response.request.headers["Authorization"] == AUTH_TOKEN
83+
84+
85+
@pytest.mark.asyncio
86+
async def test_async_auth_function(http_auth_function, auth_url):
87+
async with tls_requests.AsyncClient(auth=auth_function) as client:
88+
response = await client.get(auth_url)
89+
90+
assert response.status_code == 200
91+
assert bool(response.closed == client.closed) is True
92+
assert response.request.headers[AUTH_FUNCTION_KEY] == AUTH_FUNCTION_VALUE
93+
94+
95+
@pytest.mark.asyncio
96+
async def test_async_auth_function_cross_sharing(http_auth_function, auth_url):
97+
async with tls_requests.AsyncClient(auth=auth) as client:
98+
response = await client.get(auth_url, auth=auth_function)
99+
100+
assert response.status_code == 200
101+
assert bool(response.closed == client.closed) is True
102+
assert response.request.headers[AUTH_FUNCTION_KEY] == AUTH_FUNCTION_VALUE

0 commit comments

Comments
 (0)