firecrawl-py 3.1.0__py3-none-any.whl → 3.1.1__py3-none-any.whl

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.

Potentially problematic release.


This version of firecrawl-py might be problematic. Click here for more details.

firecrawl/__init__.py CHANGED
@@ -17,7 +17,7 @@ from .v1 import (
17
17
  V1ChangeTrackingOptions,
18
18
  )
19
19
 
20
- __version__ = "3.1.0"
20
+ __version__ = "3.1.1"
21
21
 
22
22
  # Define the logger for the Firecrawl project
23
23
  logger: logging.Logger = logging.getLogger("firecrawl")
@@ -84,4 +84,4 @@ __all__ = [
84
84
  'V1JsonConfig',
85
85
  'V1ScrapeOptions',
86
86
  'V1ChangeTrackingOptions',
87
- ]
87
+ ]
@@ -11,7 +11,7 @@ class TestSearchValidation:
11
11
  request = SearchRequest(query="")
12
12
  with pytest.raises(ValueError, match="Query cannot be empty"):
13
13
  _validate_search_request(request)
14
-
14
+
15
15
  request = SearchRequest(query=" ")
16
16
  with pytest.raises(ValueError, match="Query cannot be empty"):
17
17
  _validate_search_request(request)
@@ -22,12 +22,12 @@ class TestSearchValidation:
22
22
  request = SearchRequest(query="test", limit=0)
23
23
  with pytest.raises(ValueError, match="Limit must be positive"):
24
24
  _validate_search_request(request)
25
-
25
+
26
26
  # Negative limit
27
27
  request = SearchRequest(query="test", limit=-1)
28
28
  with pytest.raises(ValueError, match="Limit must be positive"):
29
29
  _validate_search_request(request)
30
-
30
+
31
31
  # Too high limit
32
32
  request = SearchRequest(query="test", limit=101)
33
33
  with pytest.raises(ValueError, match="Limit cannot exceed 100"):
@@ -39,12 +39,12 @@ class TestSearchValidation:
39
39
  request = SearchRequest(query="test", timeout=0)
40
40
  with pytest.raises(ValueError, match="Timeout must be positive"):
41
41
  _validate_search_request(request)
42
-
42
+
43
43
  # Negative timeout
44
44
  request = SearchRequest(query="test", timeout=-1000)
45
45
  with pytest.raises(ValueError, match="Timeout must be positive"):
46
46
  _validate_search_request(request)
47
-
47
+
48
48
  # Too high timeout
49
49
  request = SearchRequest(query="test", timeout=300001)
50
50
  with pytest.raises(ValueError, match="Timeout cannot exceed 300000ms"):
@@ -56,12 +56,12 @@ class TestSearchValidation:
56
56
  request = SearchRequest(query="test", sources=["invalid_source"])
57
57
  with pytest.raises(ValueError, match="Invalid source type"):
58
58
  _validate_search_request(request)
59
-
59
+
60
60
  # Invalid object source
61
61
  request = SearchRequest(query="test", sources=[Source(type="invalid_source")])
62
62
  with pytest.raises(ValueError, match="Invalid source type"):
63
63
  _validate_search_request(request)
64
-
64
+
65
65
  # Mixed valid/invalid sources
66
66
  request = SearchRequest(query="test", sources=["web", "invalid_source"])
67
67
  with pytest.raises(ValueError, match="Invalid source type"):
@@ -73,7 +73,7 @@ class TestSearchValidation:
73
73
  request = SearchRequest(query="test", location="")
74
74
  with pytest.raises(ValueError, match="Location must be a non-empty string"):
75
75
  _validate_search_request(request)
76
-
76
+
77
77
  # Whitespace location
78
78
  request = SearchRequest(query="test", location=" ")
79
79
  with pytest.raises(ValueError, match="Location must be a non-empty string"):
@@ -82,19 +82,49 @@ class TestSearchValidation:
82
82
  def test_validate_invalid_tbs(self):
83
83
  """Test validation of invalid tbs values."""
84
84
  invalid_tbs_values = ["invalid", "qdr:x", "yesterday", "last_week"]
85
-
85
+
86
86
  for invalid_tbs in invalid_tbs_values:
87
87
  request = SearchRequest(query="test", tbs=invalid_tbs)
88
88
  with pytest.raises(ValueError, match="Invalid tbs value"):
89
89
  _validate_search_request(request)
90
90
 
91
+ def test_validate_custom_date_ranges(self):
92
+ """Test validation of custom date range formats."""
93
+ valid_custom_ranges = [
94
+ "cdr:1,cd_min:1/1/2024,cd_max:12/31/2024",
95
+ "cdr:1,cd_min:12/1/2024,cd_max:12/31/2024",
96
+ "cdr:1,cd_min:2/28/2023,cd_max:3/1/2023",
97
+ "cdr:1,cd_min:10/15/2023,cd_max:11/15/2023"
98
+ ]
99
+
100
+ for valid_range in valid_custom_ranges:
101
+ request = SearchRequest(query="test", tbs=valid_range)
102
+ validated = _validate_search_request(request)
103
+ assert validated == request
104
+
105
+ def test_validate_invalid_custom_date_ranges(self):
106
+ """Test validation of invalid custom date range formats."""
107
+ # Invalid custom date ranges
108
+ invalid_custom_ranges = [
109
+ "cdr:1,cd_min:2/28/2023", # Missing cd_max
110
+ "cdr:1,cd_max:2/28/2023", # Missing cd_min
111
+ "cdr:2,cd_min:1/1/2024,cd_max:12/31/2024", # Wrong cdr value
112
+ "cdr:cd_min:1/1/2024,cd_max:12/31/2024", # Missing :1
113
+ "custom:1,cd_min:1/1/2024,cd_max:12/31/2024" # Wrong prefix
114
+ ]
115
+
116
+ for invalid_range in invalid_custom_ranges:
117
+ request = SearchRequest(query="test", tbs=invalid_range)
118
+ with pytest.raises(ValueError, match="Invalid"):
119
+ _validate_search_request(request)
120
+
91
121
  def test_validate_valid_requests(self):
92
122
  """Test that valid requests pass validation."""
93
123
  # Minimal valid request
94
124
  request = SearchRequest(query="test")
95
125
  validated = _validate_search_request(request)
96
126
  assert validated == request
97
-
127
+
98
128
  # Request with all optional parameters
99
129
  request = SearchRequest(
100
130
  query="test query",
@@ -107,7 +137,7 @@ class TestSearchValidation:
107
137
  )
108
138
  validated = _validate_search_request(request)
109
139
  assert validated == request
110
-
140
+
111
141
  # Request with object sources
112
142
  request = SearchRequest(
113
143
  query="test",
@@ -122,17 +152,17 @@ class TestSearchValidation:
122
152
  request = SearchRequest(query="test", limit=100)
123
153
  validated = _validate_search_request(request)
124
154
  assert validated == request
125
-
155
+
126
156
  # Maximum valid timeout
127
157
  request = SearchRequest(query="test", timeout=300000)
128
158
  validated = _validate_search_request(request)
129
159
  assert validated == request
130
-
160
+
131
161
  # Minimum valid limit
132
162
  request = SearchRequest(query="test", limit=1)
133
163
  validated = _validate_search_request(request)
134
164
  assert validated == request
135
-
165
+
136
166
  # Minimum valid timeout
137
167
  request = SearchRequest(query="test", timeout=1)
138
168
  validated = _validate_search_request(request)
@@ -191,16 +221,16 @@ class TestSearchRequestModel:
191
221
  data1 = request1.model_dump(by_alias=True)
192
222
  assert "ignore_invalid_urls" in data1 # No alias, uses snake_case
193
223
  assert data1["ignore_invalid_urls"] is None
194
-
224
+
195
225
  # Test with explicit False value
196
226
  request2 = SearchRequest(
197
227
  query="test",
198
228
  ignore_invalid_urls=False,
199
229
  scrape_options=ScrapeOptions(formats=["markdown"])
200
230
  )
201
-
231
+
202
232
  # Check that aliases are used in model_dump with by_alias=True
203
233
  data2 = request2.model_dump(by_alias=True)
204
234
  assert "ignore_invalid_urls" in data2 # No alias, uses snake_case
205
235
  assert "scrape_options" in data2 # No alias, uses snake_case
206
- assert data2["ignore_invalid_urls"] is False
236
+ assert data2["ignore_invalid_urls"] is False
@@ -2,6 +2,7 @@
2
2
  Search functionality for Firecrawl v2 API.
3
3
  """
4
4
 
5
+ import re
5
6
  from typing import Optional, Dict, Any, Union
6
7
  from ..types import SearchRequest, SearchData, SearchResult, Document
7
8
  from ..utils.normalize import normalize_document_input
@@ -119,11 +120,18 @@ def _validate_search_request(request: SearchRequest) -> SearchRequest:
119
120
  # Validate tbs (time-based search, if provided)
120
121
  if request.tbs is not None:
121
122
  valid_tbs_values = {
122
- "qdr:d", "qdr:w", "qdr:m", "qdr:y", # Google time filters
123
+ "qdr:h", "qdr:d", "qdr:w", "qdr:m", "qdr:y", # Google time filters
123
124
  "d", "w", "m", "y" # Short forms
124
125
  }
125
- if request.tbs not in valid_tbs_values:
126
- raise ValueError(f"Invalid tbs value: {request.tbs}. Valid values: {valid_tbs_values}")
126
+
127
+ if request.tbs in valid_tbs_values:
128
+ pass # Valid predefined value
129
+ elif request.tbs.startswith("cdr:"):
130
+ custom_date_pattern = r"^cdr:1,cd_min:\d{1,2}/\d{1,2}/\d{4},cd_max:\d{1,2}/\d{1,2}/\d{4}$"
131
+ if not re.match(custom_date_pattern, request.tbs):
132
+ raise ValueError(f"Invalid custom date range format: {request.tbs}. Expected format: cdr:1,cd_min:MM/DD/YYYY,cd_max:MM/DD/YYYY")
133
+ else:
134
+ raise ValueError(f"Invalid tbs value: {request.tbs}. Valid values: {valid_tbs_values} or custom date range format: cdr:1,cd_min:MM/DD/YYYY,cd_max:MM/DD/YYYY")
127
135
 
128
136
  # Validate scrape_options (if provided)
129
137
  if request.scrape_options is not None:
@@ -166,4 +174,4 @@ def _prepare_search_request(request: SearchRequest) -> Dict[str, Any]:
166
174
  data["scrapeOptions"] = scrape_data
167
175
  data.pop("scrape_options", None)
168
176
 
169
- return data
177
+ return data
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: firecrawl-py
3
- Version: 3.1.0
3
+ Version: 3.1.1
4
4
  Summary: Python SDK for Firecrawl API
5
5
  Home-page: https://github.com/firecrawl/firecrawl
6
6
  Author: Mendable.ai
@@ -40,6 +40,10 @@ Requires-Dist: websockets
40
40
  Requires-Dist: nest-asyncio
41
41
  Requires-Dist: pydantic
42
42
  Requires-Dist: aiohttp
43
+ Dynamic: author
44
+ Dynamic: home-page
45
+ Dynamic: license-file
46
+ Dynamic: requires-python
43
47
 
44
48
  # Firecrawl Python SDK
45
49
 
@@ -1,4 +1,4 @@
1
- firecrawl/__init__.py,sha256=OoUlrci9zB9_QHxXASXoaO90qLMv0B1ck7yUsRhX7gE,2191
1
+ firecrawl/__init__.py,sha256=-BUn8vFl0b_PPw4VJ0lV9y0BKsHBlOSfxGTPqGMZK7U,2192
2
2
  firecrawl/client.py,sha256=2BGIRTiW2eR6q3wu_g2s3VTQtrHYauoDeNF1YklQpHo,11089
3
3
  firecrawl/firecrawl.backup.py,sha256=v1FEN3jR4g5Aupg4xp6SLkuFvYMQuUKND2YELbYjE6c,200430
4
4
  firecrawl/types.py,sha256=yZ4iza0M1T2kxNbt-tLEOKH7o6mFKZZ11VAZGodHSq4,2734
@@ -27,7 +27,7 @@ firecrawl/__tests__/unit/v2/methods/test_crawl_validation.py,sha256=kErOmHSD01eM
27
27
  firecrawl/__tests__/unit/v2/methods/test_map_request_preparation.py,sha256=toVcgnMp_cFeYsIUuyKGEWZGp0nAAkzaeFGUbY0zY0o,1868
28
28
  firecrawl/__tests__/unit/v2/methods/test_scrape_request_preparation.py,sha256=wDOslsA5BN4kyezlaT5GeMv_Ifn8f461EaA7i5ujnaQ,3482
29
29
  firecrawl/__tests__/unit/v2/methods/test_search_request_preparation.py,sha256=14lUgFpQsiosgMKjDustBRVE0zXnHujBI76F8BC5PZ4,6072
30
- firecrawl/__tests__/unit/v2/methods/test_search_validation.py,sha256=PaV_kSgzjW8A3eFBCCn1-y4WFZBR2nf84NZk4UEBPX8,8275
30
+ firecrawl/__tests__/unit/v2/methods/test_search_validation.py,sha256=7UGcNHpQzCpZbAPYjthfdPFWmAPcoApY-ED-khtuANs,9498
31
31
  firecrawl/__tests__/unit/v2/methods/test_usage_types.py,sha256=cCHHfa6agSjD0brQ9rcAcw2kaI9riUH5C0dXV-fqktg,591
32
32
  firecrawl/__tests__/unit/v2/methods/test_webhook.py,sha256=AvvW-bKpUA--Lvtif2bmUIp-AxiaMJ29ie1i9dk8WbI,4586
33
33
  firecrawl/__tests__/unit/v2/methods/aio/test_aio_crawl_params.py,sha256=9azJxVvDOBqUevLp-wBF9gF7Ptj-7nN6LOkPQncFX2M,456
@@ -53,7 +53,7 @@ firecrawl/v2/methods/crawl.py,sha256=4ZUmanHNuNtq9wbKMAZ3lenuPcNdOaV0kYXqMI5XJJ8
53
53
  firecrawl/v2/methods/extract.py,sha256=-Jr4BtraU3b7hd3JIY73V-S69rUclxyXyUpoQb6DCQk,4274
54
54
  firecrawl/v2/methods/map.py,sha256=4SADb0-lkbdOWDmO6k8_TzK0yRti5xsN40N45nUl9uA,2592
55
55
  firecrawl/v2/methods/scrape.py,sha256=CSHBwC-P91UfrW3zHirjNAs2h899FKcWvd1DY_4fJdo,1921
56
- firecrawl/v2/methods/search.py,sha256=AdiaisKW4I5_Cjr_UQZYiRqDHu3nNrZqpfG4U2OhM6c,6131
56
+ firecrawl/v2/methods/search.py,sha256=HB17OorEHfZXZh8tvfSqVKxS9uYtqBX3Me4YAFMF7w0,6640
57
57
  firecrawl/v2/methods/usage.py,sha256=OJlkxwaB-AAtgO3WLr9QiqBRmjdh6GVhroCgleegupQ,1460
58
58
  firecrawl/v2/methods/aio/__init__.py,sha256=RocMJnGwnLIvGu3G8ZvY8INkipC7WHZiu2bE31eSyJs,35
59
59
  firecrawl/v2/methods/aio/batch.py,sha256=GS_xsd_Uib1fxFITBK1sH88VGzFMrIcqJVQqOvMQ540,3735
@@ -70,10 +70,10 @@ firecrawl/v2/utils/http_client.py,sha256=_n8mp4xi6GGihg662Lsv6TSlvw9zykyADwEk0fg
70
70
  firecrawl/v2/utils/http_client_async.py,sha256=P4XG6nTz6kKH3vCPTz6i7DRhbpK4IImRGaFvQFGBFRc,1874
71
71
  firecrawl/v2/utils/normalize.py,sha256=nlTU6QRghT1YKZzNZlIQj4STSRuSUGrS9cCErZIcY5w,3636
72
72
  firecrawl/v2/utils/validation.py,sha256=L8by7z-t6GuMGIYkK7il1BM8d-4_-sAdG9hDMF_LeG4,14518
73
+ firecrawl_py-3.1.1.dist-info/licenses/LICENSE,sha256=nPCunEDwjRGHlmjvsiDUyIWbkqqyj3Ej84ntnh0g0zA,1084
73
74
  tests/test_change_tracking.py,sha256=_IJ5ShLcoj2fHDBaw-nE4I4lHdmDB617ocK_XMHhXps,4177
74
75
  tests/test_timeout_conversion.py,sha256=PWlIEMASQNhu4cp1OW_ebklnE9NCiigPnEFCtI5N3w0,3996
75
- firecrawl_py-3.1.0.dist-info/LICENSE,sha256=nPCunEDwjRGHlmjvsiDUyIWbkqqyj3Ej84ntnh0g0zA,1084
76
- firecrawl_py-3.1.0.dist-info/METADATA,sha256=h_fUaPhhCXCBDp4ezfQjwvdyw64Xi8fFtypU2aIzBjM,7308
77
- firecrawl_py-3.1.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
78
- firecrawl_py-3.1.0.dist-info/top_level.txt,sha256=8T3jOaSN5mtLghO-R3MQ8KO290gIX8hmfxQmglBPdLE,16
79
- firecrawl_py-3.1.0.dist-info/RECORD,,
76
+ firecrawl_py-3.1.1.dist-info/METADATA,sha256=JGp1wtMdsywSEAY520vzMJtol2Mq14v3CbC_ec6DSKA,7390
77
+ firecrawl_py-3.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
+ firecrawl_py-3.1.1.dist-info/top_level.txt,sha256=8T3jOaSN5mtLghO-R3MQ8KO290gIX8hmfxQmglBPdLE,16
79
+ firecrawl_py-3.1.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.38.4)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5