flaresolverr-cli 0.2.1__tar.gz → 0.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.
Files changed (21) hide show
  1. {flaresolverr_cli-0.2.1/flaresolverr_cli.egg-info → flaresolverr_cli-0.2.2}/PKG-INFO +11 -7
  2. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/README.md +10 -6
  3. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2/flaresolverr_cli.egg-info}/PKG-INFO +11 -7
  4. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/flaresolverr_cli.egg-info/SOURCES.txt +5 -3
  5. flaresolverr_cli-0.2.2/flaresolverr_cli.egg-info/entry_points.txt +2 -0
  6. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/flaresolverr_cli.egg-info/top_level.txt +0 -2
  7. flaresolverr_cli-0.2.2/flaresolverr_session/__init__.py +28 -0
  8. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2/flaresolverr_session}/cli.py +13 -9
  9. flaresolverr_cli-0.2.2/flaresolverr_session/exceptions.py +36 -0
  10. flaresolverr_cli-0.2.1/flaresolverr_rpc.py → flaresolverr_cli-0.2.2/flaresolverr_session/rpc.py +66 -45
  11. flaresolverr_cli-0.2.1/flaresolverr_session.py → flaresolverr_cli-0.2.2/flaresolverr_session/session.py +67 -120
  12. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/setup.py +7 -6
  13. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/tests/test_cli.py +74 -26
  14. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/tests/test_rpc.py +1 -14
  15. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/tests/test_session.py +17 -28
  16. flaresolverr_cli-0.2.1/flaresolverr_cli.egg-info/entry_points.txt +0 -2
  17. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/LICENSE +0 -0
  18. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/flaresolverr_cli.egg-info/dependency_links.txt +0 -0
  19. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/flaresolverr_cli.egg-info/requires.txt +0 -0
  20. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/setup.cfg +0 -0
  21. {flaresolverr_cli-0.2.1 → flaresolverr_cli-0.2.2}/tests/testconf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flaresolverr-cli
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A requests.Session that proxies through a FlareSolverr instance.
5
5
  Home-page: https://github.com/Xavier-Lam/FlareSolverrSession
6
6
  Author: Xavier-Lam
@@ -114,16 +114,14 @@ All exceptions defined in the module based on `FlareSolverrError`, which inherit
114
114
  requests.RequestException
115
115
  └── FlareSolverrError
116
116
  ├── FlareSolverrResponseError
117
- │ ├── FlareSolverrCaptchaError
118
- │ └── FlareSolverrTimeoutError
117
+ │ ├── FlareSolverrChallengeError
119
118
  └── FlareSolverrUnsupportedMethodError
120
119
 
121
120
 
122
121
  | Exception | Description |
123
122
  |---|---|
124
123
  | `FlareSolverrResponseError` | FlareSolverr returned an error response. The response dict is available as `response_data` attribute. |
125
- | `FlareSolverrCaptchaError` | CAPTCHA detected. |
126
- | `FlareSolverrTimeoutError` | Request timed out. |
124
+ | `FlareSolverrChallengeError` | Challenge solving failed. |
127
125
  | `FlareSolverrUnsupportedMethodError` | Unsupported HTTP method or content type. |
128
126
 
129
127
  ### Command-Line Interface
@@ -149,14 +147,20 @@ flaresolverr-cli https://example.com -d "key=value&foo=bar"
149
147
  #### Managing sessions
150
148
 
151
149
  ```bash
152
- # Create a session (auto-generated name)
150
+ # Create a session
153
151
  flaresolverr-cli -f http://localhost:8191/v1 session create my-session
154
152
 
153
+ # Create multiple sessions at once
154
+ flaresolverr-cli session create session1 session2 session3
155
+
155
156
  # List all active sessions
156
157
  flaresolverr-cli session list
157
158
 
158
159
  # Destroy a session
159
160
  flaresolverr-cli session destroy my-session
161
+
162
+ # Clear all sessions
163
+ flaresolverr-cli session clear
160
164
  ```
161
165
 
162
166
  ### RPC Tool
@@ -164,7 +168,7 @@ flaresolverr-cli session destroy my-session
164
168
  The `flaresolverr_rpc` module provides a programmatic interface to the FlareSolverr JSON API, useful when you need low-level access to the raw API responses.
165
169
 
166
170
  ```python
167
- from flaresolverr_rpc import RPC
171
+ from flaresolverr_session import RPC
168
172
 
169
173
  with RPC("http://localhost:8191/v1") as rpc:
170
174
  # Session management
@@ -70,16 +70,14 @@ All exceptions defined in the module based on `FlareSolverrError`, which inherit
70
70
  requests.RequestException
71
71
  └── FlareSolverrError
72
72
  ├── FlareSolverrResponseError
73
- │ ├── FlareSolverrCaptchaError
74
- │ └── FlareSolverrTimeoutError
73
+ │ ├── FlareSolverrChallengeError
75
74
  └── FlareSolverrUnsupportedMethodError
76
75
 
77
76
 
78
77
  | Exception | Description |
79
78
  |---|---|
80
79
  | `FlareSolverrResponseError` | FlareSolverr returned an error response. The response dict is available as `response_data` attribute. |
81
- | `FlareSolverrCaptchaError` | CAPTCHA detected. |
82
- | `FlareSolverrTimeoutError` | Request timed out. |
80
+ | `FlareSolverrChallengeError` | Challenge solving failed. |
83
81
  | `FlareSolverrUnsupportedMethodError` | Unsupported HTTP method or content type. |
84
82
 
85
83
  ### Command-Line Interface
@@ -105,14 +103,20 @@ flaresolverr-cli https://example.com -d "key=value&foo=bar"
105
103
  #### Managing sessions
106
104
 
107
105
  ```bash
108
- # Create a session (auto-generated name)
106
+ # Create a session
109
107
  flaresolverr-cli -f http://localhost:8191/v1 session create my-session
110
108
 
109
+ # Create multiple sessions at once
110
+ flaresolverr-cli session create session1 session2 session3
111
+
111
112
  # List all active sessions
112
113
  flaresolverr-cli session list
113
114
 
114
115
  # Destroy a session
115
116
  flaresolverr-cli session destroy my-session
117
+
118
+ # Clear all sessions
119
+ flaresolverr-cli session clear
116
120
  ```
117
121
 
118
122
  ### RPC Tool
@@ -120,7 +124,7 @@ flaresolverr-cli session destroy my-session
120
124
  The `flaresolverr_rpc` module provides a programmatic interface to the FlareSolverr JSON API, useful when you need low-level access to the raw API responses.
121
125
 
122
126
  ```python
123
- from flaresolverr_rpc import RPC
127
+ from flaresolverr_session import RPC
124
128
 
125
129
  with RPC("http://localhost:8191/v1") as rpc:
126
130
  # Session management
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flaresolverr-cli
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A requests.Session that proxies through a FlareSolverr instance.
5
5
  Home-page: https://github.com/Xavier-Lam/FlareSolverrSession
6
6
  Author: Xavier-Lam
@@ -114,16 +114,14 @@ All exceptions defined in the module based on `FlareSolverrError`, which inherit
114
114
  requests.RequestException
115
115
  └── FlareSolverrError
116
116
  ├── FlareSolverrResponseError
117
- │ ├── FlareSolverrCaptchaError
118
- │ └── FlareSolverrTimeoutError
117
+ │ ├── FlareSolverrChallengeError
119
118
  └── FlareSolverrUnsupportedMethodError
120
119
 
121
120
 
122
121
  | Exception | Description |
123
122
  |---|---|
124
123
  | `FlareSolverrResponseError` | FlareSolverr returned an error response. The response dict is available as `response_data` attribute. |
125
- | `FlareSolverrCaptchaError` | CAPTCHA detected. |
126
- | `FlareSolverrTimeoutError` | Request timed out. |
124
+ | `FlareSolverrChallengeError` | Challenge solving failed. |
127
125
  | `FlareSolverrUnsupportedMethodError` | Unsupported HTTP method or content type. |
128
126
 
129
127
  ### Command-Line Interface
@@ -149,14 +147,20 @@ flaresolverr-cli https://example.com -d "key=value&foo=bar"
149
147
  #### Managing sessions
150
148
 
151
149
  ```bash
152
- # Create a session (auto-generated name)
150
+ # Create a session
153
151
  flaresolverr-cli -f http://localhost:8191/v1 session create my-session
154
152
 
153
+ # Create multiple sessions at once
154
+ flaresolverr-cli session create session1 session2 session3
155
+
155
156
  # List all active sessions
156
157
  flaresolverr-cli session list
157
158
 
158
159
  # Destroy a session
159
160
  flaresolverr-cli session destroy my-session
161
+
162
+ # Clear all sessions
163
+ flaresolverr-cli session clear
160
164
  ```
161
165
 
162
166
  ### RPC Tool
@@ -164,7 +168,7 @@ flaresolverr-cli session destroy my-session
164
168
  The `flaresolverr_rpc` module provides a programmatic interface to the FlareSolverr JSON API, useful when you need low-level access to the raw API responses.
165
169
 
166
170
  ```python
167
- from flaresolverr_rpc import RPC
171
+ from flaresolverr_session import RPC
168
172
 
169
173
  with RPC("http://localhost:8191/v1") as rpc:
170
174
  # Session management
@@ -1,8 +1,5 @@
1
1
  LICENSE
2
2
  README.md
3
- cli.py
4
- flaresolverr_rpc.py
5
- flaresolverr_session.py
6
3
  setup.py
7
4
  flaresolverr_cli.egg-info/PKG-INFO
8
5
  flaresolverr_cli.egg-info/SOURCES.txt
@@ -10,6 +7,11 @@ flaresolverr_cli.egg-info/dependency_links.txt
10
7
  flaresolverr_cli.egg-info/entry_points.txt
11
8
  flaresolverr_cli.egg-info/requires.txt
12
9
  flaresolverr_cli.egg-info/top_level.txt
10
+ flaresolverr_session/__init__.py
11
+ flaresolverr_session/cli.py
12
+ flaresolverr_session/exceptions.py
13
+ flaresolverr_session/rpc.py
14
+ flaresolverr_session/session.py
13
15
  tests/test_cli.py
14
16
  tests/test_rpc.py
15
17
  tests/test_session.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ flaresolverr-cli = flaresolverr_session.cli:main
@@ -1,3 +1 @@
1
- cli
2
- flaresolverr_rpc
3
1
  flaresolverr_session
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from flaresolverr_session.exceptions import (
4
+ FlareSolverrError,
5
+ FlareSolverrResponseError,
6
+ FlareSolverrChallengeError,
7
+ FlareSolverrUnsupportedMethodError,
8
+ )
9
+ from flaresolverr_session.rpc import RPC
10
+ from flaresolverr_session.session import Session, Response
11
+
12
+ __title__ = "flaresolverr-session"
13
+ __description__ = "A requests.Session that proxies through a FlareSolverr instance."
14
+ __url__ = "https://github.com/Xavier-Lam/FlareSolverrSession"
15
+ __version__ = "0.2.2"
16
+ __author__ = "Xavier-Lam"
17
+ __author_email__ = "xavierlam7@hotmail.com"
18
+
19
+ __all__ = [
20
+ "Session",
21
+ "Response",
22
+ "RPC",
23
+ "FlareSolverrError",
24
+ "FlareSolverrResponseError",
25
+ "FlareSolverrChallengeError",
26
+ "FlareSolverrUnsupportedMethodError",
27
+ "__version__",
28
+ ]
@@ -9,8 +9,8 @@ import json
9
9
  import os
10
10
  import sys
11
11
 
12
- from flaresolverr_rpc import RPC
13
- from flaresolverr_session import FlareSolverrResponseError
12
+ from flaresolverr_session.rpc import RPC
13
+ from flaresolverr_session.exceptions import FlareSolverrResponseError
14
14
 
15
15
 
16
16
  def main(argv=None):
@@ -101,9 +101,8 @@ def _build_session_parser():
101
101
  create_parser = session_sub.add_parser("create", help="Create a session")
102
102
  create_parser.add_argument(
103
103
  "name",
104
- nargs="?",
105
- default=None,
106
- help="Optional session name",
104
+ nargs="+",
105
+ help="One or more session names to create",
107
106
  )
108
107
  create_parser.add_argument(
109
108
  "--proxy",
@@ -121,6 +120,9 @@ def _build_session_parser():
121
120
  help="Session identifier to destroy",
122
121
  )
123
122
 
123
+ # session clear
124
+ session_sub.add_parser("clear", help="Destroy all sessions")
125
+
124
126
  return parser
125
127
 
126
128
 
@@ -213,14 +215,16 @@ def _handle_session(rpc, args):
213
215
  action = args.session_action
214
216
 
215
217
  if action == "create":
216
- return rpc.session.create(
217
- session_id=getattr(args, "name", None),
218
- proxy=getattr(args, "proxy", None),
219
- )
218
+ names = getattr(args, "name", None)
219
+ proxy = getattr(args, "proxy", None)
220
+ return [rpc.session.create(session_id=n, proxy=proxy) for n in names]
220
221
  elif action == "list":
221
222
  return rpc.session.list()
222
223
  elif action == "destroy":
223
224
  return rpc.session.destroy(args.session_id)
225
+ elif action == "clear":
226
+ payload = rpc.session.list()
227
+ return [rpc.session.destroy(s) for s in payload["sessions"]]
224
228
  else:
225
229
  raise ValueError("Unknown session action: %s" % action)
226
230
 
@@ -0,0 +1,36 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import requests
4
+
5
+ __all__ = [
6
+ "FlareSolverrError",
7
+ "FlareSolverrResponseError",
8
+ "FlareSolverrChallengeError",
9
+ "FlareSolverrUnsupportedMethodError",
10
+ ]
11
+
12
+
13
+ class FlareSolverrError(requests.RequestException):
14
+ """Base exception for FlareSolverr errors."""
15
+
16
+
17
+ class FlareSolverrResponseError(FlareSolverrError):
18
+ """Raised when FlareSolverr returns a non-ok response.
19
+
20
+ Attributes:
21
+ message (str): The error message from FlareSolverr.
22
+ response_data (dict or None): The original FlareSolverr response dict.
23
+ """
24
+
25
+ def __init__(self, message, response_data=None, **kwargs):
26
+ super(FlareSolverrResponseError, self).__init__(message, **kwargs)
27
+ self.message = message or ""
28
+ self.response_data = response_data
29
+
30
+
31
+ class FlareSolverrChallengeError(FlareSolverrResponseError):
32
+ """Raised when a challenge could not be solved."""
33
+
34
+
35
+ class FlareSolverrUnsupportedMethodError(FlareSolverrError):
36
+ """Raised when an unsupported HTTP method or content type is used."""
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  from __future__ import print_function
3
3
 
4
+ import json
4
5
  import os
5
6
 
6
7
  try:
@@ -10,10 +11,69 @@ except ImportError:
10
11
 
11
12
  import requests
12
13
 
13
- from flaresolverr_session import Session
14
+ from flaresolverr_session.exceptions import FlareSolverrError, FlareSolverrResponseError
14
15
 
16
+ __all__ = [
17
+ "RPC",
18
+ "SessionCommand",
19
+ "RequestCommand",
20
+ "FlareSolverrError",
21
+ "FlareSolverrResponseError",
22
+ ]
15
23
 
16
- __all__ = ["RPC", "SessionCommand", "RequestCommand"]
24
+
25
+ class RPC(object):
26
+ """RPC client for FlareSolverr.
27
+
28
+ Provides two sub-command namespaces:
29
+
30
+ - :attr:`session` -- :class:`SessionCommand` for session management.
31
+ - :attr:`request` -- :class:`RequestCommand` for HTTP requests.
32
+
33
+ Parameters:
34
+ flaresolverr_url (str): The FlareSolverr API endpoint
35
+ (e.g. ``"http://localhost:8191/v1"``).
36
+ api_session (requests.Session or None): An optional pre-configured
37
+ session to use for API calls.
38
+ """
39
+
40
+ def __init__(self, flaresolverr_url=None, api_session=None):
41
+ if flaresolverr_url is None:
42
+ flaresolverr_url = os.environ.get(
43
+ "FLARESOLVERR_URL", "http://localhost:8191/v1"
44
+ )
45
+ if api_session is None:
46
+ api_session = requests.Session()
47
+
48
+ self._flaresolverr_url = flaresolverr_url
49
+ self._api_session = api_session
50
+ self.session = SessionCommand(self)
51
+ self.request = RequestCommand(self)
52
+
53
+ def send(self, payload):
54
+ """Send a JSON payload to the FlareSolverr endpoint.
55
+
56
+ Parameters:
57
+ payload (dict): The JSON-serialisable payload to send.
58
+
59
+ Returns:
60
+ dict: The parsed JSON response from FlareSolverr.
61
+
62
+ Raises:
63
+ FlareSolverrResponseError: If the response status is
64
+ not ``"ok"``.
65
+ """
66
+ headers = {"Content-Type": "application/json"}
67
+ resp = self._api_session.post(
68
+ self._flaresolverr_url,
69
+ headers=headers,
70
+ data=json.dumps(payload),
71
+ )
72
+ data = resp.json()
73
+ status = data.get("status", "")
74
+ if status != "ok":
75
+ raise FlareSolverrResponseError(data.get("message"), data, response=resp)
76
+ return data
17
77
 
18
78
 
19
79
  class SessionCommand(object):
@@ -59,7 +119,7 @@ class SessionCommand(object):
59
119
  payload["proxy"] = proxy
60
120
  else:
61
121
  payload["proxy"] = {"url": proxy}
62
- return self._rpc._session.send(payload)
122
+ return self._rpc.send(payload)
63
123
 
64
124
  def list(self):
65
125
  """List all active FlareSolverr browser sessions.
@@ -78,7 +138,7 @@ class SessionCommand(object):
78
138
  timestamp (ms).
79
139
  """
80
140
  payload = {"cmd": "sessions.list"}
81
- return self._rpc._session.send(payload)
141
+ return self._rpc.send(payload)
82
142
 
83
143
  def destroy(self, session_id):
84
144
  """Destroy an existing FlareSolverr browser session.
@@ -102,7 +162,7 @@ class SessionCommand(object):
102
162
  "cmd": "sessions.destroy",
103
163
  "session": session_id,
104
164
  }
105
- return self._rpc._session.send(payload)
165
+ return self._rpc.send(payload)
106
166
 
107
167
 
108
168
  class RequestCommand(object):
@@ -344,43 +404,4 @@ class RequestCommand(object):
344
404
  elif cmd == "request.post":
345
405
  # FlareSolverr requires postData for request.post; send empty string
346
406
  payload["postData"] = ""
347
- return self._rpc._session.send(payload)
348
-
349
-
350
- class RPC(object):
351
- """RPC client for FlareSolverr.
352
-
353
- Provides two sub-command namespaces:
354
-
355
- - :attr:`session` -- :class:`SessionCommand` for session management.
356
- - :attr:`request` -- :class:`RequestCommand` for HTTP requests.
357
-
358
- The underlying transport reuses
359
- :meth:`flaresolverr_session.Session.send`, which is the same
360
- low-level method used by the high-level
361
- :class:`~flaresolverr_session.Session`.
362
-
363
- Parameters:
364
- flaresolverr_url (str): The FlareSolverr API endpoint
365
- (e.g. ``"http://localhost:8191/v1"``).
366
- api_session (requests.Session or None): An optional pre-configured
367
- session to use for API calls.
368
- """
369
-
370
- def __init__(self, flaresolverr_url=None, api_session=None):
371
- if flaresolverr_url is None:
372
- flaresolverr_url = os.environ.get(
373
- "FLARESOLVERR_URL", "http://localhost:8191/v1"
374
- )
375
- if api_session is None:
376
- api_session = requests.Session()
377
-
378
- self._session = Session(flaresolverr_url, session=api_session)
379
- self.session = SessionCommand(self)
380
- self.request = RequestCommand(self)
381
-
382
- def __enter__(self):
383
- return self
384
-
385
- def __exit__(self, *args):
386
- pass
407
+ return self._rpc.send(payload)
@@ -1,6 +1,6 @@
1
- # -*- coding: utf-8 -*-
1
+ # -*- coding: utf-8 -*-
2
2
 
3
- import json
3
+ import warnings
4
4
 
5
5
  try:
6
6
  from urllib import urlencode
@@ -10,47 +10,12 @@ except ImportError:
10
10
  import requests
11
11
  from requests.structures import CaseInsensitiveDict
12
12
 
13
- __title__ = "flaresolverr-session"
14
- __description__ = "A requests.Session that proxies through a FlareSolverr instance."
15
- __url__ = "https://github.com/Xavier-Lam/FlareSolverrSession"
16
- __version__ = "0.2.1"
17
- __author__ = "Xavier-Lam"
18
- __author_email__ = "xavierlam7@hotmail.com"
19
-
20
- __all__ = ["Session", "FlareSolverr", "Response", "FlareSolverrError",
21
- "FlareSolverrResponseError", "FlareSolverrCaptchaError",
22
- "FlareSolverrTimeoutError", "FlareSolverrUnsupportedMethodError",
23
- "__version__",]
24
-
25
-
26
- class FlareSolverrError(requests.RequestException):
27
- """Base exception for FlareSolverr errors."""
28
-
29
-
30
- class FlareSolverrResponseError(FlareSolverrError):
31
- """Raised when FlareSolverr returns a non-ok response.
32
-
33
- Attributes:
34
- message (str): The error message from FlareSolverr.
35
- response_data (dict or None): The original FlareSolverr response dict.
36
- """
37
-
38
- def __init__(self, message, response_data=None, **kwargs):
39
- super(FlareSolverrResponseError, self).__init__(message, **kwargs)
40
- self.message = message or ""
41
- self.response_data = response_data
42
-
43
-
44
- class FlareSolverrCaptchaError(FlareSolverrResponseError):
45
- """Raised when a CAPTCHA was encountered but could not be solved."""
46
-
47
-
48
- class FlareSolverrTimeoutError(FlareSolverrResponseError):
49
- """Raised when FlareSolverr timed out while solving the challenge."""
50
-
51
-
52
- class FlareSolverrUnsupportedMethodError(FlareSolverrError):
53
- """Raised when an unsupported HTTP method or content type is used."""
13
+ from flaresolverr_session.rpc import RPC
14
+ from flaresolverr_session.exceptions import (
15
+ FlareSolverrResponseError,
16
+ FlareSolverrChallengeError,
17
+ FlareSolverrUnsupportedMethodError,
18
+ )
54
19
 
55
20
 
56
21
  class Session(requests.Session):
@@ -62,8 +27,9 @@ class Session(requests.Session):
62
27
  FlareSolverr-specific attributes.
63
28
 
64
29
  Parameters:
65
- flaresolverr_url (str): The FlareSolverr API endpoint
66
- (e.g. ``"http://localhost:8191/v1"``).
30
+ flaresolverr_url (str or None): The FlareSolverr API endpoint
31
+ (e.g. ``"http://localhost:8191/v1"``). Ignored when *rpc*
32
+ is provided.
67
33
  session_id (str or None): An optional FlareSolverr session id.
68
34
  When *None* a new session is automatically created.
69
35
  proxy (str, dict or None): A proxy specification.
@@ -72,8 +38,9 @@ class Session(requests.Session):
72
38
  ``"username"`` and ``"password"`` keys.
73
39
  timeout (int or None): ``maxTimeout`` in **milliseconds**
74
40
  passed to FlareSolverr. Defaults to *60000* (60 s).
75
- session (requests.Session or None): An optional pre-configured
76
- session to use for API calls.
41
+ rpc (RPC or None): An optional pre-configured
42
+ :class:`~flaresolverr_session.rpc.RPC` instance. When provided,
43
+ *flaresolverr_url* is ignored.
77
44
 
78
45
  .. note::
79
46
 
@@ -87,15 +54,28 @@ class Session(requests.Session):
87
54
 
88
55
  _SUPPORTED_METHODS = ("GET", "POST")
89
56
 
90
- def __init__(self, flaresolverr_url, session_id=None, proxy=None,
91
- timeout=None, session=None):
57
+ def __init__(
58
+ self, flaresolverr_url=None, session_id=None, proxy=None, timeout=None, rpc=None
59
+ ):
92
60
  super(Session, self).__init__()
93
- self._flaresolverr_url = flaresolverr_url
94
- self._timeout = timeout or self.DEFAULT_TIMEOUT
95
- self._proxy = self._normalise_proxy(proxy)
96
61
 
62
+ if rpc is not None and flaresolverr_url is not None:
63
+ warnings.warn(
64
+ "Both 'rpc' and 'flaresolverr_url' are provided. "
65
+ "The 'rpc' instance will be used and 'flaresolverr_url' "
66
+ "will be ignored.",
67
+ stacklevel=2,
68
+ )
69
+
70
+ if not rpc:
71
+ rpc = RPC(flaresolverr_url)
72
+ self._rpc = rpc
73
+
74
+ self._timeout = timeout or self.DEFAULT_TIMEOUT
75
+ if proxy and not isinstance(proxy, dict):
76
+ proxy = {"url": proxy}
77
+ self._proxy = proxy
97
78
  self._session_id = session_id
98
- self._api_session = session or requests.Session()
99
79
  self._session_created = False
100
80
 
101
81
  @property
@@ -126,12 +106,9 @@ class Session(requests.Session):
126
106
  FlareSolverrUnsupportedMethodError: If *method* is not
127
107
  ``GET`` or ``POST``, or if ``json`` data is passed.
128
108
  FlareSolverrResponseError: If FlareSolverr returns a
129
- non-ok status for any reason (challenge not solved,
130
- timeout, session error, etc.). The original response
131
- dict is available on the exception's ``response``
132
- attribute.
133
- FlareSolverrCaptchaError: If a CAPTCHA was detected.
134
- FlareSolverrTimeoutError: If FlareSolverr timed out.
109
+ non-ok status for a reason not related to a challenge.
110
+ FlareSolverrChallengeError: If a challenge, CAPTCHA, or
111
+ timeout was encountered.
135
112
  """
136
113
  method = method.upper()
137
114
  if method not in self._SUPPORTED_METHODS:
@@ -146,28 +123,31 @@ class Session(requests.Session):
146
123
  "POST requests. JSON POST is not supported."
147
124
  )
148
125
 
149
- payload = self._build_payload(method, url, **kwargs)
126
+ request_kwargs = self._build_request_kwargs(method, url, **kwargs)
150
127
  try:
151
- resp_data = self.send(payload)
128
+ if method == "GET":
129
+ resp_data = self._rpc.request.get(**request_kwargs)
130
+ else:
131
+ resp_data = self._rpc.request.post(**request_kwargs)
152
132
  except FlareSolverrResponseError as e:
153
- if "captcha" in e.message.lower():
154
- raise FlareSolverrCaptchaError(e.message, response_data=e.response_data)
155
- elif "timeout" in e.message.lower():
156
- raise FlareSolverrTimeoutError(e.message, response_data=e.response_data)
133
+ msg = e.message.lower()
134
+ if "captcha" in msg or "timeout" in msg or "challenge" in msg:
135
+ raise FlareSolverrChallengeError(
136
+ e.message, response_data=e.response_data
137
+ )
157
138
  raise
158
139
  return Response(resp_data)
159
140
 
160
141
  def close(self):
161
- """Destroy the FlareSolverr session and close both the API
162
- session and the inherited ``requests.Session``."""
142
+ """Destroy the FlareSolverr session and close the inherited
143
+ ``requests.Session``."""
163
144
  try:
164
145
  if self._session_created:
165
146
  self._destroy_session()
166
147
  finally:
167
- self._api_session.close()
168
148
  super(Session, self).close()
169
149
 
170
- def _build_payload(self, method, url, **kwargs):
150
+ def _build_request_kwargs(self, method, url, **kwargs):
171
151
  params = kwargs.get("params")
172
152
  if params:
173
153
  if isinstance(params, dict):
@@ -177,12 +157,10 @@ class Session(requests.Session):
177
157
  else:
178
158
  url = url + "?" + encoded_params
179
159
 
180
- cmd = "request.get" if method == "GET" else "request.post"
181
- payload = {
182
- "cmd": cmd,
160
+ request_kwargs = {
183
161
  "url": url,
184
- "session": self.session_id,
185
- "maxTimeout": kwargs.get("timeout", self._timeout),
162
+ "session_id": self.session_id,
163
+ "max_timeout": kwargs.get("timeout", self._timeout),
186
164
  }
187
165
 
188
166
  # POST data
@@ -199,64 +177,29 @@ class Session(requests.Session):
199
177
  except NameError:
200
178
  pass
201
179
  data = str(data)
202
- payload["postData"] = data
180
+ request_kwargs["data"] = data
203
181
 
204
182
  # Optional cookies
205
183
  cookies = kwargs.get("cookies")
206
184
  if cookies:
207
- payload["cookies"] = cookies
208
-
209
- return payload
210
-
211
- @staticmethod
212
- def _normalise_proxy(proxy):
213
- if proxy is None:
214
- return None
215
- if isinstance(proxy, dict):
216
- return proxy
217
- return {"url": proxy}
218
-
219
- def send(self, payload):
220
- headers = {"Content-Type": "application/json"}
221
- resp = self._api_session.post(
222
- self._flaresolverr_url,
223
- headers=headers,
224
- data=json.dumps(payload),
225
- )
226
- data = resp.json()
227
- status = data.get("status", "")
228
- if status != "ok":
229
- raise FlareSolverrResponseError(
230
- data.get("message"), data, response=resp)
231
- return data
232
-
233
- def _create_session(self):
234
- payload = {
235
- "cmd": "sessions.create",
236
- }
237
- if self._session_id:
238
- payload["session"] = self._session_id
239
- if self._proxy:
240
- payload["proxy"] = self._proxy
185
+ request_kwargs["cookies"] = cookies
241
186
 
242
- data = self.send(payload)
187
+ return request_kwargs
243
188
 
189
+ def _create_session(self):
190
+ """Create a FlareSolverr browser session via RPC."""
191
+ data = self._rpc.session.create(session_id=self._session_id, proxy=self._proxy)
244
192
  self._session_id = data.get("session", self._session_id)
245
193
  self._session_created = True
246
194
 
247
195
  def _destroy_session(self):
196
+ """Destroy the FlareSolverr browser session via RPC."""
248
197
  if not self._session_id:
249
198
  return
250
-
251
- payload = {
252
- "cmd": "sessions.destroy",
253
- "session": self._session_id,
254
- }
255
199
  try:
256
- self.send(payload)
200
+ self._rpc.session.destroy(self._session_id)
257
201
  except Exception:
258
202
  return # Best-effort cleanup
259
-
260
203
  self._session_created = False
261
204
 
262
205
 
@@ -273,8 +216,9 @@ class FlareSolverr(object):
273
216
  version (str): FlareSolverr server version.
274
217
  """
275
218
 
276
- def __init__(self, status="", message="", user_agent="",
277
- start=0, end=0, version=""):
219
+ def __init__(
220
+ self, status="", message="", user_agent="", start=0, end=0, version=""
221
+ ):
278
222
  self.status = status
279
223
  self.message = message
280
224
  self.user_agent = user_agent
@@ -284,7 +228,10 @@ class FlareSolverr(object):
284
228
 
285
229
  def __repr__(self):
286
230
  return "FlareSolverr(status=%r, message=%r, version=%r)" % (
287
- self.status, self.message, self.version)
231
+ self.status,
232
+ self.message,
233
+ self.version,
234
+ )
288
235
 
289
236
 
290
237
  class Response(requests.Response):
@@ -11,15 +11,16 @@ with io.open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
11
11
  long_description = f.read()
12
12
 
13
13
  package = dict()
14
- with io.open(os.path.join(this_directory, "flaresolverr_session.py"),
15
- encoding="utf-8") as f:
14
+ with io.open(
15
+ os.path.join(this_directory, "flaresolverr_session", "__init__.py"),
16
+ encoding="utf-8",
17
+ ) as f:
16
18
  for line in f:
17
19
  match = re.match(r"(__\w+?__)\s*=\s*(.+)$", line)
18
20
  if match and match.group(1) != "__all__":
19
21
  package[match.group(1)] = eval(match.group(2))
20
22
 
21
- package_name = os.environ.get("FLARESOLVERR_PACKAGE_NAME",
22
- package["__title__"])
23
+ package_name = os.environ.get("FLARESOLVERR_PACKAGE_NAME", package["__title__"])
23
24
 
24
25
  setup(
25
26
  name=package_name,
@@ -31,14 +32,14 @@ setup(
31
32
  long_description=long_description,
32
33
  long_description_content_type="text/markdown",
33
34
  license="MIT",
34
- py_modules=["flaresolverr_session", "flaresolverr_rpc", "cli"],
35
+ packages=["flaresolverr_session"],
35
36
  python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*",
36
37
  install_requires=[
37
38
  "requests",
38
39
  ],
39
40
  entry_points={
40
41
  "console_scripts": [
41
- "flaresolverr-cli=cli:main",
42
+ "flaresolverr-cli=flaresolverr_session.cli:main",
42
43
  ],
43
44
  },
44
45
  extras_require={
@@ -21,11 +21,9 @@ if sys.version_info[0] >= 3:
21
21
  else:
22
22
  from StringIO import StringIO # Python 2
23
23
 
24
- from cli import main, _truncate_response_body
24
+ from flaresolverr_session.cli import main, _truncate_response_body
25
25
  from flaresolverr_session import (
26
26
  FlareSolverrResponseError,
27
- FlareSolverrCaptchaError,
28
- FlareSolverrTimeoutError,
29
27
  FlareSolverrError,
30
28
  )
31
29
 
@@ -60,6 +58,14 @@ def _fake_rpc():
60
58
  "startTimestamp": 100,
61
59
  "endTimestamp": 200,
62
60
  }
61
+ rpc.session.clear.return_value = {
62
+ "status": "ok",
63
+ "message": "All sessions cleared successfully.",
64
+ "sessions": ["s1", "s2"],
65
+ "version": "3.3.21",
66
+ "startTimestamp": 100,
67
+ "endTimestamp": 200,
68
+ }
63
69
  rpc.request.get.return_value = {
64
70
  "status": "ok",
65
71
  "message": "Challenge solved!",
@@ -107,7 +113,7 @@ def _run_cli(argv, rpc=None):
107
113
  if rpc is None:
108
114
  rpc = _fake_rpc()
109
115
 
110
- with mock.patch("cli.RPC", return_value=rpc):
116
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc):
111
117
  old_stdout = sys.stdout
112
118
  old_stderr = sys.stderr
113
119
  sys.stdout = captured_out = StringIO()
@@ -126,23 +132,28 @@ class TestSessionCreate(unittest.TestCase):
126
132
 
127
133
  def test_create_no_args(self):
128
134
  """session create with no extra args."""
129
- code, out, _err, rpc = _run_cli(["session", "create"])
130
- self.assertEqual(code, 0)
131
- rpc.session.create.assert_called_once_with(session_id=None, proxy=None)
132
- data = json.loads(out)
133
- self.assertEqual(data["status"], "ok")
135
+ with self.assertRaises(SystemExit):
136
+ main(["session", "create"])
134
137
 
135
138
  def test_create_with_name(self):
136
139
  """session create with a session name."""
137
140
  code, out, _err, rpc = _run_cli(["session", "create", "my-sess"])
138
141
  self.assertEqual(code, 0)
142
+ self.assertEqual(rpc.session.create.call_count, 1)
139
143
  rpc.session.create.assert_called_once_with(session_id="my-sess", proxy=None)
144
+ data = json.loads(out)
145
+ self.assertIsInstance(data, list)
146
+ self.assertEqual(len(data), 1)
140
147
 
141
148
  def test_create_with_proxy(self):
142
149
  """session create with --proxy."""
143
- code, out, _err, rpc = _run_cli(["session", "create", "--proxy", "http://p:80"])
150
+ code, out, _err, rpc = _run_cli(
151
+ ["session", "create", "my-sess", "--proxy", "http://p:80"]
152
+ )
144
153
  self.assertEqual(code, 0)
145
- rpc.session.create.assert_called_once_with(session_id=None, proxy="http://p:80")
154
+ rpc.session.create.assert_called_once_with(
155
+ session_id="my-sess", proxy="http://p:80"
156
+ )
146
157
 
147
158
  def test_create_with_name_and_proxy(self):
148
159
  """session create with name and proxy."""
@@ -157,15 +168,26 @@ class TestSessionCreate(unittest.TestCase):
157
168
  def test_create_with_flaresolverr_url(self):
158
169
  """session create with -f flag."""
159
170
  rpc = _fake_rpc()
160
- with mock.patch("cli.RPC", return_value=rpc) as rpc_cls:
171
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc) as rpc_cls:
161
172
  old_stdout = sys.stdout
162
173
  sys.stdout = StringIO()
163
174
  try:
164
- main(["-f", "http://custom:9999/v1", "session", "create"])
175
+ main(["-f", "http://custom:9999/v1", "session", "create", "n1"])
165
176
  finally:
166
177
  sys.stdout = old_stdout
167
178
  rpc_cls.assert_called_once_with("http://custom:9999/v1")
168
179
 
180
+ def test_create_with_multiple_names(self):
181
+ """session create with multiple names."""
182
+ code, out, _err, rpc = _run_cli(["session", "create", "a", "b"])
183
+ self.assertEqual(code, 0)
184
+ self.assertEqual(rpc.session.create.call_count, 2)
185
+ rpc.session.create.assert_any_call(session_id="a", proxy=None)
186
+ rpc.session.create.assert_any_call(session_id="b", proxy=None)
187
+ data = json.loads(out)
188
+ self.assertIsInstance(data, list)
189
+ self.assertEqual(len(data), 2)
190
+
169
191
 
170
192
  class TestSessionList(unittest.TestCase):
171
193
  """Tests for 'session list' CLI command."""
@@ -191,6 +213,22 @@ class TestSessionDestroy(unittest.TestCase):
191
213
  self.assertEqual(data["status"], "ok")
192
214
 
193
215
 
216
+ class TestSessionClear(unittest.TestCase):
217
+ """Tests for 'session clear' CLI command."""
218
+
219
+ def test_clear(self):
220
+ """session clear invokes RPC.session.clear()."""
221
+ code, out, _err, rpc = _run_cli(["session", "clear"])
222
+ self.assertEqual(code, 0)
223
+ rpc.session.list.assert_called_once()
224
+ self.assertEqual(rpc.session.destroy.call_count, 2)
225
+ rpc.session.destroy.assert_any_call("s1")
226
+ rpc.session.destroy.assert_any_call("s2")
227
+ data = json.loads(out)
228
+ self.assertIsInstance(data, list)
229
+ self.assertEqual(len(data), 2)
230
+
231
+
194
232
  class TestRequestDefault(unittest.TestCase):
195
233
  """Tests for the default request command (URL as first arg)."""
196
234
 
@@ -368,7 +406,7 @@ class TestRequestWithOptions(unittest.TestCase):
368
406
  def test_flaresolverr_url_passed_to_rpc(self):
369
407
  """The -f flag is forwarded to RPC constructor."""
370
408
  rpc = _fake_rpc()
371
- with mock.patch("cli.RPC", return_value=rpc) as rpc_cls:
409
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc) as rpc_cls:
372
410
  old_stdout = sys.stdout
373
411
  sys.stdout = StringIO()
374
412
  try:
@@ -380,7 +418,7 @@ class TestRequestWithOptions(unittest.TestCase):
380
418
  def test_flaresolverr_url_with_request_command(self):
381
419
  """The -f flag works with explicit request command."""
382
420
  rpc = _fake_rpc()
383
- with mock.patch("cli.RPC", return_value=rpc) as rpc_cls:
421
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc) as rpc_cls:
384
422
  old_stdout = sys.stdout
385
423
  sys.stdout = StringIO()
386
424
  try:
@@ -396,10 +434,15 @@ class TestRequestOutputFile(unittest.TestCase):
396
434
  def test_output_file(self):
397
435
  """Response body is written to file."""
398
436
  rpc = _fake_rpc()
399
- with mock.patch("cli.RPC", return_value=rpc):
437
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc):
400
438
  m = mock.mock_open()
401
439
  with mock.patch(
402
- "cli.open" if sys.version_info[0] >= 3 else "__builtin__.open", m
440
+ (
441
+ "flaresolverr_session.cli.open"
442
+ if sys.version_info[0] >= 3
443
+ else "__builtin__.open"
444
+ ),
445
+ m,
403
446
  ):
404
447
  old_stdout = sys.stdout
405
448
  sys.stdout = StringIO()
@@ -434,10 +477,15 @@ class TestRequestOutputFile(unittest.TestCase):
434
477
  "endTimestamp": 200,
435
478
  }
436
479
 
437
- with mock.patch("cli.RPC", return_value=rpc):
480
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc):
438
481
  m = mock.mock_open()
439
482
  with mock.patch(
440
- "cli.open" if sys.version_info[0] >= 3 else "__builtin__.open", m
483
+ (
484
+ "flaresolverr_session.cli.open"
485
+ if sys.version_info[0] >= 3
486
+ else "__builtin__.open"
487
+ ),
488
+ m,
441
489
  ):
442
490
  old_stdout = sys.stdout
443
491
  sys.stdout = StringIO()
@@ -537,7 +585,7 @@ class TestTwoPassParsing(unittest.TestCase):
537
585
  def test_f_flag_before_command(self):
538
586
  """-f before session command."""
539
587
  rpc = _fake_rpc()
540
- with mock.patch("cli.RPC", return_value=rpc) as rpc_cls:
588
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc) as rpc_cls:
541
589
  old_stdout = sys.stdout
542
590
  sys.stdout = StringIO()
543
591
  try:
@@ -549,7 +597,7 @@ class TestTwoPassParsing(unittest.TestCase):
549
597
  def test_f_flag_before_url(self):
550
598
  """-f before URL (implicit request)."""
551
599
  rpc = _fake_rpc()
552
- with mock.patch("cli.RPC", return_value=rpc) as rpc_cls:
600
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc) as rpc_cls:
553
601
  old_stdout = sys.stdout
554
602
  sys.stdout = StringIO()
555
603
  try:
@@ -562,7 +610,7 @@ class TestTwoPassParsing(unittest.TestCase):
562
610
  def test_f_flag_after_url(self):
563
611
  """-f after URL (implicit request)."""
564
612
  rpc = _fake_rpc()
565
- with mock.patch("cli.RPC", return_value=rpc) as rpc_cls:
613
+ with mock.patch("flaresolverr_session.cli.RPC", return_value=rpc) as rpc_cls:
566
614
  old_stdout = sys.stdout
567
615
  sys.stdout = StringIO()
568
616
  try:
@@ -652,9 +700,9 @@ class TestCliErrorHandling(unittest.TestCase):
652
700
  self.assertEqual(str(e), "Connection refused")
653
701
 
654
702
  def test_captcha_error_exits_nonzero(self):
655
- """FlareSolverrCaptchaError causes exit code 1."""
703
+ """FlareSolverrResponseError with captcha message causes exit code 1."""
656
704
  fake_resp = {"status": "error", "message": "Captcha detected"}
657
- exc = FlareSolverrCaptchaError("Captcha detected", response_data=fake_resp)
705
+ exc = FlareSolverrResponseError("Captcha detected", response_data=fake_resp)
658
706
  rpc = self._make_rpc_raising(exc)
659
707
  code, out, err, _ = _run_cli(["https://example.com"], rpc=rpc)
660
708
  self.assertEqual(code, 1)
@@ -662,9 +710,9 @@ class TestCliErrorHandling(unittest.TestCase):
662
710
  self.assertEqual(data["message"], "Captcha detected")
663
711
 
664
712
  def test_timeout_error_exits_nonzero(self):
665
- """FlareSolverrTimeoutError causes exit code 1."""
713
+ """FlareSolverrResponseError with timeout message causes exit code 1."""
666
714
  fake_resp = {"status": "error", "message": "Error: Timeout reached"}
667
- exc = FlareSolverrTimeoutError(
715
+ exc = FlareSolverrResponseError(
668
716
  "Error: Timeout reached", response_data=fake_resp
669
717
  )
670
718
  rpc = self._make_rpc_raising(exc)
@@ -2,7 +2,7 @@
2
2
 
3
3
  import unittest
4
4
 
5
- from flaresolverr_rpc import RPC
5
+ from flaresolverr_session import RPC
6
6
 
7
7
  # Python 2/3 compatible string type check
8
8
  try:
@@ -254,18 +254,5 @@ class TestRequestPost(RPCTestCase):
254
254
  _assert_solution(result["solution"])
255
255
 
256
256
 
257
- class TestRPCContextManager(unittest.TestCase):
258
- def test_context_manager_returns_rpc(self):
259
- """The context manager yields the RPC instance itself."""
260
- with RPC() as rpc:
261
- assert isinstance(rpc, RPC)
262
-
263
- def test_context_manager_executes_request(self):
264
- """Requests work inside a context manager block."""
265
- with RPC() as rpc:
266
- result = rpc.session.list()
267
- _assert_ok(result)
268
-
269
-
270
257
  if __name__ == "__main__":
271
258
  unittest.main()
@@ -28,10 +28,9 @@ except ImportError:
28
28
  import requests
29
29
 
30
30
  from flaresolverr_session import (
31
- FlareSolverrCaptchaError,
31
+ FlareSolverrChallengeError,
32
32
  FlareSolverrError,
33
33
  FlareSolverrResponseError,
34
- FlareSolverrTimeoutError,
35
34
  FlareSolverrUnsupportedMethodError,
36
35
  Session,
37
36
  Response,
@@ -84,8 +83,7 @@ def _assert_response(response, entry):
84
83
 
85
84
 
86
85
  def _list_sessions(session=None):
87
- payload = {"cmd": "sessions.list"}
88
- data = session.send(payload)
86
+ data = session._rpc.session.list()
89
87
  return data.get("sessions", [])
90
88
 
91
89
 
@@ -157,7 +155,7 @@ class TestUnsolvedChallenge(unittest.TestCase):
157
155
  """Mock an unsolved challenge to verify exception handling."""
158
156
 
159
157
  def test_challenge_not_solved(self):
160
- """FlareSolverrResponseError raised on failed challenge."""
158
+ """FlareSolverrChallengeError raised on failed challenge."""
161
159
  fake_json = {
162
160
  "status": "error",
163
161
  "message": "Challenge not solved",
@@ -169,19 +167,19 @@ class TestUnsolvedChallenge(unittest.TestCase):
169
167
  with _make_session() as session:
170
168
  # Force session creation before mock
171
169
  _ = session.session_id
172
- with mock.patch.object(session._api_session, "post") as mocked_post:
170
+ with mock.patch.object(session._rpc._api_session, "post") as mocked_post:
173
171
  mock_resp = mock.MagicMock()
174
172
  mock_resp.json.return_value = fake_json
175
173
  mocked_post.return_value = mock_resp
176
174
 
177
- with self.assertRaises(FlareSolverrResponseError) as ctx:
175
+ with self.assertRaises(FlareSolverrChallengeError) as ctx:
178
176
  session.get("https://example.com")
179
177
  self.assertEqual(ctx.exception.response_data, fake_json)
180
178
  self.assertIsInstance(ctx.exception, FlareSolverrResponseError)
181
179
  self.assertEqual(ctx.exception.message, "Challenge not solved")
182
180
 
183
181
  def test_captcha_detected(self):
184
- """FlareSolverrCaptchaError raised when captcha is detected."""
182
+ """FlareSolverrChallengeError raised when captcha is detected."""
185
183
  fake_json = {
186
184
  "status": "error",
187
185
  "message": ("Captcha detected but no automatic solver is configured."),
@@ -193,18 +191,18 @@ class TestUnsolvedChallenge(unittest.TestCase):
193
191
  with _make_session() as session:
194
192
  # Force session creation before mock
195
193
  _ = session.session_id
196
- with mock.patch.object(session._api_session, "post") as mocked_post:
194
+ with mock.patch.object(session._rpc._api_session, "post") as mocked_post:
197
195
  mock_resp = mock.MagicMock()
198
196
  mock_resp.json.return_value = fake_json
199
197
  mocked_post.return_value = mock_resp
200
198
 
201
- with self.assertRaises(FlareSolverrCaptchaError) as ctx:
199
+ with self.assertRaises(FlareSolverrChallengeError) as ctx:
202
200
  session.get("https://example.com")
203
201
  self.assertEqual(ctx.exception.response_data, fake_json)
204
202
  self.assertIsInstance(ctx.exception, FlareSolverrResponseError)
205
203
 
206
204
  def test_timeout_error(self):
207
- """FlareSolverrTimeoutError raised on timeout."""
205
+ """FlareSolverrChallengeError raised on timeout."""
208
206
  fake_json = {
209
207
  "status": "error",
210
208
  "message": "Error: Timeout reached",
@@ -216,12 +214,12 @@ class TestUnsolvedChallenge(unittest.TestCase):
216
214
  with _make_session() as session:
217
215
  # Force session creation before mock
218
216
  _ = session.session_id
219
- with mock.patch.object(session._api_session, "post") as mocked_post:
217
+ with mock.patch.object(session._rpc._api_session, "post") as mocked_post:
220
218
  mock_resp = mock.MagicMock()
221
219
  mock_resp.json.return_value = fake_json
222
220
  mocked_post.return_value = mock_resp
223
221
 
224
- with self.assertRaises(FlareSolverrTimeoutError) as ctx:
222
+ with self.assertRaises(FlareSolverrChallengeError) as ctx:
225
223
  session.get("https://example.com")
226
224
  self.assertEqual(ctx.exception.response_data, fake_json)
227
225
  self.assertIsInstance(ctx.exception, FlareSolverrResponseError)
@@ -535,11 +533,8 @@ class TestExceptionHierarchy(unittest.TestCase):
535
533
  def test_response_error_inherits_from_base(self):
536
534
  assert issubclass(FlareSolverrResponseError, FlareSolverrError)
537
535
 
538
- def test_captcha_error_inherits_from_response_error(self):
539
- assert issubclass(FlareSolverrCaptchaError, FlareSolverrResponseError)
540
-
541
- def test_timeout_error_inherits_from_response_error(self):
542
- assert issubclass(FlareSolverrTimeoutError, FlareSolverrResponseError)
536
+ def test_challenge_error_inherits_from_response_error(self):
537
+ assert issubclass(FlareSolverrChallengeError, FlareSolverrResponseError)
543
538
 
544
539
  def test_unsupported_method_error(self):
545
540
  assert issubclass(FlareSolverrUnsupportedMethodError, FlareSolverrError)
@@ -550,16 +545,10 @@ class TestExceptionHierarchy(unittest.TestCase):
550
545
  exc = FlareSolverrResponseError("oops", response_data=data)
551
546
  assert exc.response_data is data
552
547
 
553
- def test_captcha_error_carries_response_dict(self):
554
- """FlareSolverrCaptchaError stores the raw response."""
555
- data = {"status": "error", "message": "captcha"}
556
- exc = FlareSolverrCaptchaError("captcha", response_data=data)
557
- assert exc.response_data is data
558
-
559
- def test_timeout_error_carries_response_dict(self):
560
- """FlareSolverrTimeoutError stores the raw response."""
561
- data = {"status": "error", "message": "timeout"}
562
- exc = FlareSolverrTimeoutError("timeout", response_data=data)
548
+ def test_challenge_error_carries_response_dict(self):
549
+ """FlareSolverrChallengeError stores the raw response."""
550
+ data = {"status": "error", "message": "challenge"}
551
+ exc = FlareSolverrChallengeError("challenge", response_data=data)
563
552
  assert exc.response_data is data
564
553
 
565
554
 
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- flaresolverr-cli = cli:main