python3-capsolver 1.2.0__tar.gz → 1.2.2__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.
- {python3_capsolver-1.2.0/src/python3_capsolver.egg-info → python3_capsolver-1.2.2}/PKG-INFO +37 -7
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/README.md +33 -5
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/pyproject.toml +3 -1
- python3_capsolver-1.2.2/src/python3_capsolver/__version__.py +1 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/aio_captcha_instrument.py +8 -3
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/sio_captcha_instrument.py +8 -3
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2/src/python3_capsolver.egg-info}/PKG-INFO +37 -7
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver.egg-info/SOURCES.txt +2 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver.egg-info/requires.txt +1 -1
- python3_capsolver-1.2.2/tests/test_aio_captcha_instrument.py +186 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_instrument.py +16 -2
- python3_capsolver-1.2.2/tests/test_sio_captcha_instrument.py +185 -0
- python3_capsolver-1.2.0/src/python3_capsolver/__version__.py +0 -1
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/LICENSE +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/MANIFEST.in +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/setup.cfg +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/__init__.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/aws_waf.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/cloudflare.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/control.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/__init__.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/base.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/captcha_instrument.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/const.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/context_instr.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/enum.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/serializer.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/utils.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/datadome_slider.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/friendly_captcha.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/gee_test.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/image_to_text.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/mt_captcha.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/recaptcha.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/vision_engine.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/yandex.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver.egg-info/dependency_links.txt +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver.egg-info/top_level.txt +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_aws_waf.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_cloudflare.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_control.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_core.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_datadome.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_friendly.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_gee_test.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_image_to_text.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_mt_captcha.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_recaptcha.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_vision_engine.py +0 -0
- {python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/tests/test_yandex.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-capsolver
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Python 3.8+ Capsolver library with AIO module.
|
|
5
5
|
Author-email: AndreiDrang <python-captcha@pm.me>
|
|
6
6
|
License: MIT License
|
|
@@ -21,6 +21,8 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
26
|
Classifier: Framework :: AsyncIO
|
|
25
27
|
Classifier: Operating System :: Unix
|
|
26
28
|
Classifier: Operating System :: Microsoft :: Windows
|
|
@@ -30,7 +32,7 @@ Description-Content-Type: text/markdown
|
|
|
30
32
|
License-File: LICENSE
|
|
31
33
|
Requires-Dist: requests>=2.21.0
|
|
32
34
|
Requires-Dist: aiohttp>=3.9.2
|
|
33
|
-
Requires-Dist: msgspec
|
|
35
|
+
Requires-Dist: msgspec>=0.18
|
|
34
36
|
Requires-Dist: tenacity<10,>=8
|
|
35
37
|
Dynamic: license-file
|
|
36
38
|
|
|
@@ -62,6 +64,7 @@ Tested on UNIX based OS.
|
|
|
62
64
|
The library is intended for software developers and is used to work with the [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1) service API.
|
|
63
65
|
|
|
64
66
|
## Features
|
|
67
|
+
- **Modern Tooling**: Uses `uv` for fast, reliable dependency management and environment isolation.
|
|
65
68
|
- **Sync & Async Support**: Full support for both synchronous (`requests`) and asynchronous (`aiohttp`) operations.
|
|
66
69
|
- **Type Safety**: Enums for captcha types and response statuses.
|
|
67
70
|
- **Resilience**: Built-in retries using `tenacity`.
|
|
@@ -70,9 +73,21 @@ The library is intended for software developers and is used to work with the [Ca
|
|
|
70
73
|
|
|
71
74
|
## How to install?
|
|
72
75
|
|
|
73
|
-
We recommend using the latest version of Python. `python3-capsolver` supports Python 3.
|
|
76
|
+
We recommend using the latest version of Python. `python3-capsolver` supports Python 3.8+.
|
|
74
77
|
|
|
75
|
-
###
|
|
78
|
+
### Development (using uv)
|
|
79
|
+
|
|
80
|
+
This project uses [uv](https://github.com/astral-sh/uv) for fast, reliable dependency management.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Install uv (if not already installed)
|
|
84
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
85
|
+
|
|
86
|
+
# Install project dependencies
|
|
87
|
+
uv sync --all-groups
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Production (pip)
|
|
76
91
|
|
|
77
92
|
```bash
|
|
78
93
|
pip install python3-capsolver
|
|
@@ -151,13 +166,28 @@ if __name__ == "__main__":
|
|
|
151
166
|
|
|
152
167
|
## How to test?
|
|
153
168
|
|
|
154
|
-
|
|
155
|
-
|
|
169
|
+
The project uses `uv` for testing and development tasks.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# 1. Set API_KEY in your environment (get this value from your account)
|
|
173
|
+
export API_KEY="your_api_key_here"
|
|
174
|
+
|
|
175
|
+
# 2. Run tests (uses uv internally)
|
|
176
|
+
make tests
|
|
177
|
+
|
|
178
|
+
# Other useful make targets:
|
|
179
|
+
# make install # Install dependencies with uv sync
|
|
180
|
+
# make lint # Run linting checks
|
|
181
|
+
# make build # Build the package
|
|
182
|
+
# make doc # Generate documentation
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
All `make` commands automatically use `uv` to run commands in the isolated environment.
|
|
156
186
|
|
|
157
187
|
|
|
158
188
|
### Changelog
|
|
159
189
|
|
|
160
|
-
|
|
190
|
+
See [CHANGELOG.md](https://github.com/AndreiDrang/python3-capsolver/blob/main/CHANGELOG.md) for version history and detailed changes.
|
|
161
191
|
|
|
162
192
|
### How to get API Key to work with the library
|
|
163
193
|
1. On the page - https://dashboard.capsolver.com/overview/user-center
|
|
@@ -26,6 +26,7 @@ Tested on UNIX based OS.
|
|
|
26
26
|
The library is intended for software developers and is used to work with the [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1) service API.
|
|
27
27
|
|
|
28
28
|
## Features
|
|
29
|
+
- **Modern Tooling**: Uses `uv` for fast, reliable dependency management and environment isolation.
|
|
29
30
|
- **Sync & Async Support**: Full support for both synchronous (`requests`) and asynchronous (`aiohttp`) operations.
|
|
30
31
|
- **Type Safety**: Enums for captcha types and response statuses.
|
|
31
32
|
- **Resilience**: Built-in retries using `tenacity`.
|
|
@@ -34,9 +35,21 @@ The library is intended for software developers and is used to work with the [Ca
|
|
|
34
35
|
|
|
35
36
|
## How to install?
|
|
36
37
|
|
|
37
|
-
We recommend using the latest version of Python. `python3-capsolver` supports Python 3.
|
|
38
|
+
We recommend using the latest version of Python. `python3-capsolver` supports Python 3.8+.
|
|
38
39
|
|
|
39
|
-
###
|
|
40
|
+
### Development (using uv)
|
|
41
|
+
|
|
42
|
+
This project uses [uv](https://github.com/astral-sh/uv) for fast, reliable dependency management.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Install uv (if not already installed)
|
|
46
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
47
|
+
|
|
48
|
+
# Install project dependencies
|
|
49
|
+
uv sync --all-groups
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Production (pip)
|
|
40
53
|
|
|
41
54
|
```bash
|
|
42
55
|
pip install python3-capsolver
|
|
@@ -115,13 +128,28 @@ if __name__ == "__main__":
|
|
|
115
128
|
|
|
116
129
|
## How to test?
|
|
117
130
|
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
The project uses `uv` for testing and development tasks.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# 1. Set API_KEY in your environment (get this value from your account)
|
|
135
|
+
export API_KEY="your_api_key_here"
|
|
136
|
+
|
|
137
|
+
# 2. Run tests (uses uv internally)
|
|
138
|
+
make tests
|
|
139
|
+
|
|
140
|
+
# Other useful make targets:
|
|
141
|
+
# make install # Install dependencies with uv sync
|
|
142
|
+
# make lint # Run linting checks
|
|
143
|
+
# make build # Build the package
|
|
144
|
+
# make doc # Generate documentation
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
All `make` commands automatically use `uv` to run commands in the isolated environment.
|
|
120
148
|
|
|
121
149
|
|
|
122
150
|
### Changelog
|
|
123
151
|
|
|
124
|
-
|
|
152
|
+
See [CHANGELOG.md](https://github.com/AndreiDrang/python3-capsolver/blob/main/CHANGELOG.md) for version history and detailed changes.
|
|
125
153
|
|
|
126
154
|
### How to get API Key to work with the library
|
|
127
155
|
1. On the page - https://dashboard.capsolver.com/overview/user-center
|
|
@@ -78,6 +78,8 @@ classifiers = [
|
|
|
78
78
|
"Programming Language :: Python :: 3.10",
|
|
79
79
|
"Programming Language :: Python :: 3.11",
|
|
80
80
|
"Programming Language :: Python :: 3.12",
|
|
81
|
+
"Programming Language :: Python :: 3.13",
|
|
82
|
+
"Programming Language :: Python :: 3.14",
|
|
81
83
|
"Framework :: AsyncIO",
|
|
82
84
|
"Operating System :: Unix",
|
|
83
85
|
"Operating System :: Microsoft :: Windows",
|
|
@@ -86,7 +88,7 @@ classifiers = [
|
|
|
86
88
|
dependencies = [
|
|
87
89
|
"requests>=2.21.0",
|
|
88
90
|
"aiohttp>=3.9.2",
|
|
89
|
-
"msgspec>=0.18
|
|
91
|
+
"msgspec>=0.18",
|
|
90
92
|
"tenacity>=8,<10"
|
|
91
93
|
]
|
|
92
94
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.2"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import logging
|
|
3
|
-
from typing import
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
4
6
|
from urllib import parse
|
|
5
7
|
|
|
6
8
|
import aiohttp
|
|
@@ -11,6 +13,9 @@ from .utils import attempts_generator
|
|
|
11
13
|
from .serializer import CaptchaResponseSer
|
|
12
14
|
from .captcha_instrument import CaptchaInstrumentBase
|
|
13
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .base import CaptchaParams
|
|
18
|
+
|
|
14
19
|
__all__ = ("AIOCaptchaInstrument",)
|
|
15
20
|
|
|
16
21
|
|
|
@@ -19,7 +24,7 @@ class AIOCaptchaInstrument(CaptchaInstrumentBase):
|
|
|
19
24
|
Instrument for working with async captcha
|
|
20
25
|
"""
|
|
21
26
|
|
|
22
|
-
def __init__(self, captcha_params:
|
|
27
|
+
def __init__(self, captcha_params: CaptchaParams):
|
|
23
28
|
super().__init__()
|
|
24
29
|
self.captcha_params = captcha_params
|
|
25
30
|
self.created_task_data = CaptchaResponseSer
|
|
@@ -99,7 +104,7 @@ class AIOCaptchaInstrument(CaptchaInstrumentBase):
|
|
|
99
104
|
|
|
100
105
|
@staticmethod
|
|
101
106
|
async def send_post_request(
|
|
102
|
-
payload:
|
|
107
|
+
payload: dict | None = None,
|
|
103
108
|
url_postfix: EndpointPostfixEnm = EndpointPostfixEnm.GET_BALANCE,
|
|
104
109
|
) -> dict:
|
|
105
110
|
"""
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import time
|
|
2
4
|
import logging
|
|
3
|
-
from typing import
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
4
6
|
from urllib import parse
|
|
5
7
|
|
|
6
8
|
import requests
|
|
@@ -12,6 +14,9 @@ from .utils import attempts_generator
|
|
|
12
14
|
from .serializer import CaptchaResponseSer
|
|
13
15
|
from .captcha_instrument import CaptchaInstrumentBase
|
|
14
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .base import CaptchaParams
|
|
19
|
+
|
|
15
20
|
__all__ = ("SIOCaptchaInstrument",)
|
|
16
21
|
|
|
17
22
|
|
|
@@ -20,7 +25,7 @@ class SIOCaptchaInstrument(CaptchaInstrumentBase):
|
|
|
20
25
|
Instrument for working with sync captcha
|
|
21
26
|
"""
|
|
22
27
|
|
|
23
|
-
def __init__(self, captcha_params:
|
|
28
|
+
def __init__(self, captcha_params: CaptchaParams):
|
|
24
29
|
super().__init__()
|
|
25
30
|
self.captcha_params = captcha_params
|
|
26
31
|
self.created_task_data = CaptchaResponseSer
|
|
@@ -106,7 +111,7 @@ class SIOCaptchaInstrument(CaptchaInstrumentBase):
|
|
|
106
111
|
|
|
107
112
|
@staticmethod
|
|
108
113
|
def send_post_request(
|
|
109
|
-
payload:
|
|
114
|
+
payload: dict | None = None,
|
|
110
115
|
session: requests.Session = requests.Session(),
|
|
111
116
|
url_postfix: EndpointPostfixEnm = EndpointPostfixEnm.GET_BALANCE,
|
|
112
117
|
) -> dict:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-capsolver
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Python 3.8+ Capsolver library with AIO module.
|
|
5
5
|
Author-email: AndreiDrang <python-captcha@pm.me>
|
|
6
6
|
License: MIT License
|
|
@@ -21,6 +21,8 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
26
|
Classifier: Framework :: AsyncIO
|
|
25
27
|
Classifier: Operating System :: Unix
|
|
26
28
|
Classifier: Operating System :: Microsoft :: Windows
|
|
@@ -30,7 +32,7 @@ Description-Content-Type: text/markdown
|
|
|
30
32
|
License-File: LICENSE
|
|
31
33
|
Requires-Dist: requests>=2.21.0
|
|
32
34
|
Requires-Dist: aiohttp>=3.9.2
|
|
33
|
-
Requires-Dist: msgspec
|
|
35
|
+
Requires-Dist: msgspec>=0.18
|
|
34
36
|
Requires-Dist: tenacity<10,>=8
|
|
35
37
|
Dynamic: license-file
|
|
36
38
|
|
|
@@ -62,6 +64,7 @@ Tested on UNIX based OS.
|
|
|
62
64
|
The library is intended for software developers and is used to work with the [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1) service API.
|
|
63
65
|
|
|
64
66
|
## Features
|
|
67
|
+
- **Modern Tooling**: Uses `uv` for fast, reliable dependency management and environment isolation.
|
|
65
68
|
- **Sync & Async Support**: Full support for both synchronous (`requests`) and asynchronous (`aiohttp`) operations.
|
|
66
69
|
- **Type Safety**: Enums for captcha types and response statuses.
|
|
67
70
|
- **Resilience**: Built-in retries using `tenacity`.
|
|
@@ -70,9 +73,21 @@ The library is intended for software developers and is used to work with the [Ca
|
|
|
70
73
|
|
|
71
74
|
## How to install?
|
|
72
75
|
|
|
73
|
-
We recommend using the latest version of Python. `python3-capsolver` supports Python 3.
|
|
76
|
+
We recommend using the latest version of Python. `python3-capsolver` supports Python 3.8+.
|
|
74
77
|
|
|
75
|
-
###
|
|
78
|
+
### Development (using uv)
|
|
79
|
+
|
|
80
|
+
This project uses [uv](https://github.com/astral-sh/uv) for fast, reliable dependency management.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Install uv (if not already installed)
|
|
84
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
85
|
+
|
|
86
|
+
# Install project dependencies
|
|
87
|
+
uv sync --all-groups
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Production (pip)
|
|
76
91
|
|
|
77
92
|
```bash
|
|
78
93
|
pip install python3-capsolver
|
|
@@ -151,13 +166,28 @@ if __name__ == "__main__":
|
|
|
151
166
|
|
|
152
167
|
## How to test?
|
|
153
168
|
|
|
154
|
-
|
|
155
|
-
|
|
169
|
+
The project uses `uv` for testing and development tasks.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# 1. Set API_KEY in your environment (get this value from your account)
|
|
173
|
+
export API_KEY="your_api_key_here"
|
|
174
|
+
|
|
175
|
+
# 2. Run tests (uses uv internally)
|
|
176
|
+
make tests
|
|
177
|
+
|
|
178
|
+
# Other useful make targets:
|
|
179
|
+
# make install # Install dependencies with uv sync
|
|
180
|
+
# make lint # Run linting checks
|
|
181
|
+
# make build # Build the package
|
|
182
|
+
# make doc # Generate documentation
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
All `make` commands automatically use `uv` to run commands in the isolated environment.
|
|
156
186
|
|
|
157
187
|
|
|
158
188
|
### Changelog
|
|
159
189
|
|
|
160
|
-
|
|
190
|
+
See [CHANGELOG.md](https://github.com/AndreiDrang/python3-capsolver/blob/main/CHANGELOG.md) for version history and detailed changes.
|
|
161
191
|
|
|
162
192
|
### How to get API Key to work with the library
|
|
163
193
|
1. On the page - https://dashboard.capsolver.com/overview/user-center
|
{python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver.egg-info/SOURCES.txt
RENAMED
|
@@ -30,6 +30,7 @@ src/python3_capsolver/core/enum.py
|
|
|
30
30
|
src/python3_capsolver/core/serializer.py
|
|
31
31
|
src/python3_capsolver/core/sio_captcha_instrument.py
|
|
32
32
|
src/python3_capsolver/core/utils.py
|
|
33
|
+
tests/test_aio_captcha_instrument.py
|
|
33
34
|
tests/test_aws_waf.py
|
|
34
35
|
tests/test_cloudflare.py
|
|
35
36
|
tests/test_control.py
|
|
@@ -41,5 +42,6 @@ tests/test_image_to_text.py
|
|
|
41
42
|
tests/test_instrument.py
|
|
42
43
|
tests/test_mt_captcha.py
|
|
43
44
|
tests/test_recaptcha.py
|
|
45
|
+
tests/test_sio_captcha_instrument.py
|
|
44
46
|
tests/test_vision_engine.py
|
|
45
47
|
tests/test_yandex.py
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from tests.conftest import BaseTest
|
|
6
|
+
from python3_capsolver.core.base import CaptchaParams
|
|
7
|
+
from python3_capsolver.core.enum import CaptchaTypeEnm, EndpointPostfixEnm
|
|
8
|
+
from python3_capsolver.core.const import REQUEST_URL
|
|
9
|
+
from python3_capsolver.core.aio_captcha_instrument import AIOCaptchaInstrument
|
|
10
|
+
|
|
11
|
+
API_KEY_STUB = "unit-test-api-key-000000000000000000000000"
|
|
12
|
+
CREATE_TASK_URL = "https://unit.test/createTask"
|
|
13
|
+
GET_TASK_RESULT_URL = "https://unit.test/getTaskResult"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_captcha_params() -> CaptchaParams:
|
|
17
|
+
"""
|
|
18
|
+
Build params pointing to a fake API host with zero sleep between polls:
|
|
19
|
+
no real request can leave the mocked `aiohttp.ClientSession.post` boundary
|
|
20
|
+
"""
|
|
21
|
+
return CaptchaParams(
|
|
22
|
+
api_key=API_KEY_STUB,
|
|
23
|
+
captcha_type=CaptchaTypeEnm.Control,
|
|
24
|
+
sleep_time=0,
|
|
25
|
+
request_url="https://unit.test/",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def aio_response(payload=None, status: int = 200, reason: str = "unit test failure") -> MagicMock:
|
|
30
|
+
"""
|
|
31
|
+
Build fake response for the `aiohttp` boundary, usable as async context manager.
|
|
32
|
+
|
|
33
|
+
For non-parsable bodies (``payload is None``) any ``json()`` call raises -
|
|
34
|
+
an error status must never be parsed as a success body
|
|
35
|
+
"""
|
|
36
|
+
response = MagicMock()
|
|
37
|
+
response.status = status
|
|
38
|
+
response.reason = reason
|
|
39
|
+
if payload is None:
|
|
40
|
+
response.json = AsyncMock(side_effect=AssertionError("body of an error response must not be parsed"))
|
|
41
|
+
else:
|
|
42
|
+
response.json = AsyncMock(return_value=payload)
|
|
43
|
+
response.__aenter__.return_value = response
|
|
44
|
+
return response
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestAIOCaptchaInstrument(BaseTest):
|
|
48
|
+
"""
|
|
49
|
+
Mocked unit tests for AIOCaptchaInstrument
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
53
|
+
async def test_returns_created_result_without_polling_when_task_is_ready(self, mock_post):
|
|
54
|
+
mock_post.return_value = aio_response(
|
|
55
|
+
{"errorId": 0, "taskId": "task-ready-1", "status": "ready", "solution": {"text": "wg6x"}}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
result = await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
59
|
+
|
|
60
|
+
assert result["errorId"] == 0
|
|
61
|
+
assert result["status"] == "ready"
|
|
62
|
+
assert result["taskId"] == "task-ready-1"
|
|
63
|
+
assert result["solution"] == {"text": "wg6x"}
|
|
64
|
+
# task was solved at creation - no `getTaskResult` polling must follow
|
|
65
|
+
mock_post.assert_called_once()
|
|
66
|
+
assert mock_post.call_args.args[0] == CREATE_TASK_URL
|
|
67
|
+
assert mock_post.call_args.kwargs["json"]["task"] == {"type": "Control"}
|
|
68
|
+
assert mock_post.call_args.kwargs["json"]["clientKey"] == API_KEY_STUB
|
|
69
|
+
|
|
70
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
71
|
+
async def test_polls_get_task_result_until_ready(self, mock_post):
|
|
72
|
+
mock_post.side_effect = [
|
|
73
|
+
aio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
74
|
+
aio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
75
|
+
aio_response({"errorId": 0, "taskId": "task-42", "status": "ready", "solution": {"text": "ab12"}}),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
result = await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
79
|
+
|
|
80
|
+
assert result["errorId"] == 0
|
|
81
|
+
assert result["status"] == "ready"
|
|
82
|
+
assert result["solution"] == {"text": "ab12"}
|
|
83
|
+
assert [call.args[0] for call in mock_post.call_args_list] == [
|
|
84
|
+
CREATE_TASK_URL,
|
|
85
|
+
GET_TASK_RESULT_URL,
|
|
86
|
+
GET_TASK_RESULT_URL,
|
|
87
|
+
]
|
|
88
|
+
# created taskId must be propagated to every polling request
|
|
89
|
+
assert mock_post.call_args_list[1].kwargs["json"]["taskId"] == "task-42"
|
|
90
|
+
assert mock_post.call_args_list[2].kwargs["json"]["taskId"] == "task-42"
|
|
91
|
+
|
|
92
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
93
|
+
async def test_returns_failed_result_when_polling_reports_failure(self, mock_post):
|
|
94
|
+
mock_post.side_effect = [
|
|
95
|
+
aio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
96
|
+
aio_response({"errorId": 1, "errorCode": "ERROR_TASK_NOT_FOUND", "status": "failed"}),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
result = await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
100
|
+
|
|
101
|
+
assert result["errorId"] == 1
|
|
102
|
+
assert result["status"] == "failed"
|
|
103
|
+
assert result["errorCode"] == "ERROR_TASK_NOT_FOUND"
|
|
104
|
+
assert len(mock_post.call_args_list) == 2
|
|
105
|
+
|
|
106
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
107
|
+
async def test_marks_result_failed_when_task_creation_is_rejected(self, mock_post):
|
|
108
|
+
mock_post.return_value = aio_response(
|
|
109
|
+
{"errorId": 1, "errorCode": "ERROR_KEY_DOES_NOT_EXIST", "errorDescription": "Client key is invalid"}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
result = await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
113
|
+
|
|
114
|
+
assert result["errorId"] == 1
|
|
115
|
+
assert result["status"] == "failed"
|
|
116
|
+
assert result["errorCode"] == "ERROR_KEY_DOES_NOT_EXIST"
|
|
117
|
+
assert result["errorDescription"] == "Client key is invalid"
|
|
118
|
+
# rejected creation must not trigger result polling
|
|
119
|
+
mock_post.assert_called_once()
|
|
120
|
+
|
|
121
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
122
|
+
async def test_parses_error_payload_when_status_code_is_valid_error_status(self, mock_post):
|
|
123
|
+
mock_post.return_value = aio_response({"errorId": 1, "errorCode": "ERROR_WRONG_USERKEY"}, status=400)
|
|
124
|
+
|
|
125
|
+
result = await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
126
|
+
|
|
127
|
+
assert result["status"] == "failed"
|
|
128
|
+
assert result["errorCode"] == "ERROR_WRONG_USERKEY"
|
|
129
|
+
|
|
130
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
131
|
+
async def test_returns_unsolvable_default_when_server_never_finishes(self, mock_post):
|
|
132
|
+
mock_post.side_effect = [aio_response({"errorId": 0, "taskId": "task-silent", "status": "processing"})] * 16
|
|
133
|
+
|
|
134
|
+
result = await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
135
|
+
|
|
136
|
+
assert result["errorId"] == 1
|
|
137
|
+
assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE"
|
|
138
|
+
assert result["errorDescription"] == "Captcha not recognized"
|
|
139
|
+
assert result["status"] == "failed"
|
|
140
|
+
assert result["taskId"] == "task-silent"
|
|
141
|
+
# 1 `createTask` + 15 polling attempts, then the instrument gives up
|
|
142
|
+
assert len(mock_post.call_args_list) == 16
|
|
143
|
+
|
|
144
|
+
@pytest.mark.parametrize("bad_status", [404, 500])
|
|
145
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
146
|
+
async def test_raises_when_create_task_returns_invalid_status(self, mock_post, bad_status):
|
|
147
|
+
mock_post.return_value = aio_response(status=bad_status, reason="Internal Server Error")
|
|
148
|
+
|
|
149
|
+
with pytest.raises(ValueError):
|
|
150
|
+
await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
151
|
+
|
|
152
|
+
mock_post.assert_called_once()
|
|
153
|
+
|
|
154
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
155
|
+
async def test_raises_when_get_task_result_returns_invalid_status(self, mock_post):
|
|
156
|
+
mock_post.side_effect = [
|
|
157
|
+
aio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
158
|
+
aio_response(status=503, reason="Service Unavailable"),
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
with pytest.raises(ValueError):
|
|
162
|
+
await AIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
163
|
+
|
|
164
|
+
assert len(mock_post.call_args_list) == 2
|
|
165
|
+
|
|
166
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
167
|
+
async def test_send_post_request_returns_decoded_payload(self, mock_post):
|
|
168
|
+
mock_post.return_value = aio_response({"errorId": 0, "balance": 107.7})
|
|
169
|
+
|
|
170
|
+
result = await AIOCaptchaInstrument.send_post_request(
|
|
171
|
+
payload={"clientKey": API_KEY_STUB},
|
|
172
|
+
url_postfix=EndpointPostfixEnm.GET_BALANCE,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
assert result == {"errorId": 0, "balance": 107.7}
|
|
176
|
+
assert mock_post.call_args.args[0] == f"{REQUEST_URL}/{EndpointPostfixEnm.GET_BALANCE.value}"
|
|
177
|
+
assert mock_post.call_args.kwargs["json"] == {"clientKey": API_KEY_STUB}
|
|
178
|
+
|
|
179
|
+
@patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
|
|
180
|
+
async def test_send_post_request_raises_for_error_status(self, mock_post):
|
|
181
|
+
mock_post.return_value = aio_response(status=403)
|
|
182
|
+
|
|
183
|
+
with pytest.raises(ValueError):
|
|
184
|
+
await AIOCaptchaInstrument.send_post_request(payload={"clientKey": API_KEY_STUB})
|
|
185
|
+
|
|
186
|
+
mock_post.assert_called_once()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
|
|
3
5
|
from tests.conftest import BaseTest
|
|
@@ -11,8 +13,8 @@ class TestFileInstrument(BaseTest):
|
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
15
|
def test_file_processing_exist(self):
|
|
14
|
-
assert "file_processing" in FileInstrument.__dict__
|
|
15
|
-
assert "aio_file_processing" in FileInstrument.__dict__
|
|
16
|
+
assert "file_processing" in FileInstrument.__dict__
|
|
17
|
+
assert "aio_file_processing" in FileInstrument.__dict__
|
|
16
18
|
|
|
17
19
|
def test_captcha_base64(self):
|
|
18
20
|
assert self.read_image_as_str() == FileInstrument().file_processing(captcha_base64=self.read_image())
|
|
@@ -55,3 +57,15 @@ class TestFileInstrument(BaseTest):
|
|
|
55
57
|
async def test_aio_file_processing_err(self):
|
|
56
58
|
with pytest.raises(ValueError):
|
|
57
59
|
await FileInstrument().aio_file_processing()
|
|
60
|
+
|
|
61
|
+
@patch("python3_capsolver.core.captcha_instrument.aiohttp.ClientSession.get")
|
|
62
|
+
async def test_aio_captcha_link_download_err(self, mock_get):
|
|
63
|
+
mock_response = MagicMock()
|
|
64
|
+
mock_response.status = 404
|
|
65
|
+
mock_response.__aenter__.return_value = mock_response
|
|
66
|
+
mock_get.return_value = mock_response
|
|
67
|
+
|
|
68
|
+
result = await FileInstrument().aio_file_processing(captcha_link=self.image_captcha_url_example)
|
|
69
|
+
|
|
70
|
+
# unreachable link returns empty content instead of raising
|
|
71
|
+
assert result == ""
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from tests.conftest import BaseTest
|
|
6
|
+
from python3_capsolver.core.base import CaptchaParams
|
|
7
|
+
from python3_capsolver.core.enum import CaptchaTypeEnm, EndpointPostfixEnm
|
|
8
|
+
from python3_capsolver.core.const import REQUEST_URL
|
|
9
|
+
from python3_capsolver.core.sio_captcha_instrument import SIOCaptchaInstrument
|
|
10
|
+
|
|
11
|
+
API_KEY_STUB = "unit-test-api-key-000000000000000000000000"
|
|
12
|
+
CREATE_TASK_URL = "https://unit.test/createTask"
|
|
13
|
+
GET_TASK_RESULT_URL = "https://unit.test/getTaskResult"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_captcha_params() -> CaptchaParams:
|
|
17
|
+
"""
|
|
18
|
+
Build params pointing to a fake API host with zero sleep between polls:
|
|
19
|
+
no real request can leave the mocked `Session.post` boundary
|
|
20
|
+
"""
|
|
21
|
+
return CaptchaParams(
|
|
22
|
+
api_key=API_KEY_STUB,
|
|
23
|
+
captcha_type=CaptchaTypeEnm.Control,
|
|
24
|
+
sleep_time=0,
|
|
25
|
+
request_url="https://unit.test/",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def sio_response(payload=None, status_code: int = 200) -> MagicMock:
|
|
30
|
+
"""
|
|
31
|
+
Build fake response for the `requests` boundary.
|
|
32
|
+
|
|
33
|
+
For non-parsable bodies (``payload is None``) any ``json()`` call raises -
|
|
34
|
+
an error status must never be parsed as a success body
|
|
35
|
+
"""
|
|
36
|
+
response = MagicMock()
|
|
37
|
+
response.status_code = status_code
|
|
38
|
+
if payload is None:
|
|
39
|
+
response.json.side_effect = AssertionError("body of an error response must not be parsed")
|
|
40
|
+
else:
|
|
41
|
+
response.json.return_value = payload
|
|
42
|
+
response.raise_for_status.return_value = None
|
|
43
|
+
return response
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestSIOCaptchaInstrument(BaseTest):
|
|
47
|
+
"""
|
|
48
|
+
Mocked unit tests for SIOCaptchaInstrument
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
52
|
+
def test_returns_created_result_without_polling_when_task_is_ready(self, mock_post):
|
|
53
|
+
mock_post.return_value = sio_response(
|
|
54
|
+
{"errorId": 0, "taskId": "task-ready-1", "status": "ready", "solution": {"text": "wg6x"}}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
result = SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
58
|
+
|
|
59
|
+
assert result["errorId"] == 0
|
|
60
|
+
assert result["status"] == "ready"
|
|
61
|
+
assert result["taskId"] == "task-ready-1"
|
|
62
|
+
assert result["solution"] == {"text": "wg6x"}
|
|
63
|
+
# task was solved at creation - no `getTaskResult` polling must follow
|
|
64
|
+
mock_post.assert_called_once()
|
|
65
|
+
assert mock_post.call_args.args[0] == CREATE_TASK_URL
|
|
66
|
+
assert mock_post.call_args.kwargs["json"]["task"] == {"type": "Control"}
|
|
67
|
+
assert mock_post.call_args.kwargs["json"]["clientKey"] == API_KEY_STUB
|
|
68
|
+
|
|
69
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
70
|
+
def test_polls_get_task_result_until_ready(self, mock_post):
|
|
71
|
+
mock_post.side_effect = [
|
|
72
|
+
sio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
73
|
+
sio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
74
|
+
sio_response({"errorId": 0, "taskId": "task-42", "status": "ready", "solution": {"text": "ab12"}}),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
result = SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
78
|
+
|
|
79
|
+
assert result["errorId"] == 0
|
|
80
|
+
assert result["status"] == "ready"
|
|
81
|
+
assert result["solution"] == {"text": "ab12"}
|
|
82
|
+
assert [call.args[0] for call in mock_post.call_args_list] == [
|
|
83
|
+
CREATE_TASK_URL,
|
|
84
|
+
GET_TASK_RESULT_URL,
|
|
85
|
+
GET_TASK_RESULT_URL,
|
|
86
|
+
]
|
|
87
|
+
# created taskId must be propagated to every polling request
|
|
88
|
+
assert mock_post.call_args_list[1].kwargs["json"]["taskId"] == "task-42"
|
|
89
|
+
assert mock_post.call_args_list[2].kwargs["json"]["taskId"] == "task-42"
|
|
90
|
+
|
|
91
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
92
|
+
def test_returns_failed_result_when_polling_reports_failure(self, mock_post):
|
|
93
|
+
mock_post.side_effect = [
|
|
94
|
+
sio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
95
|
+
sio_response({"errorId": 1, "errorCode": "ERROR_TASK_NOT_FOUND", "status": "failed"}),
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
result = SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
99
|
+
|
|
100
|
+
assert result["errorId"] == 1
|
|
101
|
+
assert result["status"] == "failed"
|
|
102
|
+
assert result["errorCode"] == "ERROR_TASK_NOT_FOUND"
|
|
103
|
+
assert len(mock_post.call_args_list) == 2
|
|
104
|
+
|
|
105
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
106
|
+
def test_marks_result_failed_when_task_creation_is_rejected(self, mock_post):
|
|
107
|
+
mock_post.return_value = sio_response(
|
|
108
|
+
{"errorId": 1, "errorCode": "ERROR_KEY_DOES_NOT_EXIST", "errorDescription": "Client key is invalid"}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
result = SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
112
|
+
|
|
113
|
+
assert result["errorId"] == 1
|
|
114
|
+
assert result["status"] == "failed"
|
|
115
|
+
assert result["errorCode"] == "ERROR_KEY_DOES_NOT_EXIST"
|
|
116
|
+
assert result["errorDescription"] == "Client key is invalid"
|
|
117
|
+
# rejected creation must not trigger result polling
|
|
118
|
+
mock_post.assert_called_once()
|
|
119
|
+
|
|
120
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
121
|
+
def test_parses_error_payload_when_status_code_is_valid_error_status(self, mock_post):
|
|
122
|
+
mock_post.return_value = sio_response({"errorId": 1, "errorCode": "ERROR_WRONG_USERKEY"}, status_code=400)
|
|
123
|
+
|
|
124
|
+
result = SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
125
|
+
|
|
126
|
+
assert result["status"] == "failed"
|
|
127
|
+
assert result["errorCode"] == "ERROR_WRONG_USERKEY"
|
|
128
|
+
|
|
129
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
130
|
+
def test_returns_unsolvable_default_when_server_never_finishes(self, mock_post):
|
|
131
|
+
mock_post.side_effect = [sio_response({"errorId": 0, "taskId": "task-silent", "status": "processing"})] * 16
|
|
132
|
+
|
|
133
|
+
result = SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
134
|
+
|
|
135
|
+
assert result["errorId"] == 1
|
|
136
|
+
assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE"
|
|
137
|
+
assert result["errorDescription"] == "Captcha not recognized"
|
|
138
|
+
assert result["status"] == "failed"
|
|
139
|
+
assert result["taskId"] == "task-silent"
|
|
140
|
+
# 1 `createTask` + 15 polling attempts, then the instrument gives up
|
|
141
|
+
assert len(mock_post.call_args_list) == 16
|
|
142
|
+
|
|
143
|
+
@pytest.mark.parametrize("bad_status", [404, 500])
|
|
144
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
145
|
+
def test_raises_when_create_task_returns_invalid_status(self, mock_post, bad_status):
|
|
146
|
+
mock_post.return_value = sio_response(status_code=bad_status)
|
|
147
|
+
|
|
148
|
+
with pytest.raises(ValueError):
|
|
149
|
+
SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
150
|
+
|
|
151
|
+
mock_post.assert_called_once()
|
|
152
|
+
|
|
153
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
154
|
+
def test_raises_when_get_task_result_returns_invalid_status(self, mock_post):
|
|
155
|
+
mock_post.side_effect = [
|
|
156
|
+
sio_response({"errorId": 0, "taskId": "task-42", "status": "processing"}),
|
|
157
|
+
sio_response(status_code=503),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
with pytest.raises(ValueError):
|
|
161
|
+
SIOCaptchaInstrument(captcha_params=create_captcha_params()).processing_captcha()
|
|
162
|
+
|
|
163
|
+
assert len(mock_post.call_args_list) == 2
|
|
164
|
+
|
|
165
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
166
|
+
def test_send_post_request_returns_decoded_payload(self, mock_post):
|
|
167
|
+
mock_post.return_value = sio_response({"errorId": 0, "balance": 107.7})
|
|
168
|
+
|
|
169
|
+
result = SIOCaptchaInstrument.send_post_request(
|
|
170
|
+
payload={"clientKey": API_KEY_STUB},
|
|
171
|
+
url_postfix=EndpointPostfixEnm.GET_BALANCE,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
assert result == {"errorId": 0, "balance": 107.7}
|
|
175
|
+
assert mock_post.call_args.args[0] == f"{REQUEST_URL}/{EndpointPostfixEnm.GET_BALANCE.value}"
|
|
176
|
+
assert mock_post.call_args.kwargs["json"] == {"clientKey": API_KEY_STUB}
|
|
177
|
+
|
|
178
|
+
@patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
|
|
179
|
+
def test_send_post_request_raises_for_error_status(self, mock_post):
|
|
180
|
+
mock_post.return_value = sio_response(status_code=403)
|
|
181
|
+
|
|
182
|
+
with pytest.raises(ValueError):
|
|
183
|
+
SIOCaptchaInstrument.send_post_request(payload={"clientKey": API_KEY_STUB})
|
|
184
|
+
|
|
185
|
+
mock_post.assert_called_once()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/captcha_instrument.py
RENAMED
|
File without changes
|
|
File without changes
|
{python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/context_instr.py
RENAMED
|
File without changes
|
|
File without changes
|
{python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/core/serializer.py
RENAMED
|
File without changes
|
|
File without changes
|
{python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/datadome_slider.py
RENAMED
|
File without changes
|
{python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver/friendly_captcha.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python3_capsolver-1.2.0 → python3_capsolver-1.2.2}/src/python3_capsolver.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|