firecrawl-py 4.5.0__py3-none-any.whl → 4.6.0__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__ = "4.5.0"
20
+ __version__ = "4.6.0"
21
21
 
22
22
  # Define the logger for the Firecrawl project
23
23
  logger: logging.Logger = logging.getLogger("firecrawl")
@@ -0,0 +1,214 @@
1
+ import pytest
2
+ from unittest.mock import Mock, MagicMock
3
+ from firecrawl.v2.methods.scrape import scrape
4
+ from firecrawl.v2.types import ScrapeOptions, Document
5
+
6
+
7
+ class TestBrandingFormat:
8
+ """Unit tests for branding format support."""
9
+
10
+ def test_scrape_with_branding_format_returns_branding_data(self):
11
+ """Test that scraping with branding format returns branding data."""
12
+ mock_response = Mock()
13
+ mock_response.ok = True
14
+ mock_response.json.return_value = {
15
+ "success": True,
16
+ "data": {
17
+ "markdown": "# Example",
18
+ "branding": {
19
+ "colorScheme": "light",
20
+ "colors": {
21
+ "primary": "#E11D48",
22
+ "secondary": "#3B82F6",
23
+ "accent": "#F59E0B"
24
+ },
25
+ "typography": {
26
+ "fontFamilies": {
27
+ "primary": "Inter",
28
+ "heading": "Poppins"
29
+ },
30
+ "fontSizes": {
31
+ "h1": "2.5rem",
32
+ "body": "1rem"
33
+ }
34
+ },
35
+ "spacing": {
36
+ "baseUnit": 8
37
+ },
38
+ "components": {
39
+ "buttonPrimary": {
40
+ "background": "#E11D48",
41
+ "textColor": "#FFFFFF",
42
+ "borderRadius": "0.5rem"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ mock_client = Mock()
50
+ mock_client.post.return_value = mock_response
51
+
52
+ result = scrape(mock_client, "https://example.com", ScrapeOptions(formats=["branding"]))
53
+
54
+ assert result.branding is not None
55
+ assert result.branding.color_scheme == "light"
56
+ assert result.branding.colors["primary"] == "#E11D48"
57
+ assert result.branding.typography["fontFamilies"]["primary"] == "Inter"
58
+ assert result.branding.spacing["baseUnit"] == 8
59
+ assert result.branding.components["buttonPrimary"]["background"] == "#E11D48"
60
+
61
+ def test_scrape_with_branding_and_markdown_formats_returns_both(self):
62
+ """Test that scraping with both branding and markdown formats returns both."""
63
+ mock_response = Mock()
64
+ mock_response.ok = True
65
+ mock_response.json.return_value = {
66
+ "success": True,
67
+ "data": {
68
+ "markdown": "# Example Content",
69
+ "branding": {
70
+ "colorScheme": "dark",
71
+ "colors": {
72
+ "primary": "#10B981"
73
+ },
74
+ "typography": {
75
+ "fontFamilies": {
76
+ "primary": "Roboto"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ mock_client = Mock()
84
+ mock_client.post.return_value = mock_response
85
+
86
+ result = scrape(mock_client, "https://example.com", ScrapeOptions(formats=["markdown", "branding"]))
87
+
88
+ assert result.markdown == "# Example Content"
89
+ assert result.branding is not None
90
+ assert result.branding.color_scheme == "dark"
91
+ assert result.branding.colors["primary"] == "#10B981"
92
+
93
+ def test_scrape_without_branding_format_does_not_return_branding(self):
94
+ """Test that scraping without branding format does not return branding."""
95
+ mock_response = Mock()
96
+ mock_response.ok = True
97
+ mock_response.json.return_value = {
98
+ "success": True,
99
+ "data": {
100
+ "markdown": "# Example"
101
+ }
102
+ }
103
+
104
+ mock_client = Mock()
105
+ mock_client.post.return_value = mock_response
106
+
107
+ result = scrape(mock_client, "https://example.com", ScrapeOptions(formats=["markdown"]))
108
+
109
+ assert result.markdown == "# Example"
110
+ assert result.branding is None
111
+
112
+ def test_branding_format_with_all_nested_fields(self):
113
+ """Test branding format with all nested fields populated."""
114
+ mock_response = Mock()
115
+ mock_response.ok = True
116
+ mock_response.json.return_value = {
117
+ "success": True,
118
+ "data": {
119
+ "branding": {
120
+ "colorScheme": "light",
121
+ "logo": "https://example.com/logo.png",
122
+ "fonts": [
123
+ {"family": "Inter", "weight": 400},
124
+ {"family": "Poppins", "weight": 700}
125
+ ],
126
+ "colors": {
127
+ "primary": "#E11D48",
128
+ "background": "#FFFFFF"
129
+ },
130
+ "typography": {
131
+ "fontFamilies": {"primary": "Inter"},
132
+ "fontStacks": {"body": ["Inter", "sans-serif"]},
133
+ "fontSizes": {"h1": "2.5rem"},
134
+ "lineHeights": {"body": 1.5},
135
+ "fontWeights": {"regular": 400}
136
+ },
137
+ "spacing": {
138
+ "baseUnit": 8,
139
+ "padding": {"sm": 8, "md": 16}
140
+ },
141
+ "components": {
142
+ "buttonPrimary": {
143
+ "background": "#E11D48",
144
+ "textColor": "#FFFFFF"
145
+ }
146
+ },
147
+ "icons": {
148
+ "style": "outline",
149
+ "primaryColor": "#E11D48"
150
+ },
151
+ "images": {
152
+ "logo": "https://example.com/logo.png",
153
+ "favicon": "https://example.com/favicon.ico"
154
+ },
155
+ "animations": {
156
+ "transitionDuration": "200ms",
157
+ "easing": "ease-in-out"
158
+ },
159
+ "layout": {
160
+ "grid": {"columns": 12, "maxWidth": "1200px"},
161
+ "headerHeight": "64px"
162
+ },
163
+ "tone": {
164
+ "voice": "professional",
165
+ "emojiUsage": "minimal"
166
+ },
167
+ "personality": {
168
+ "tone": "professional",
169
+ "energy": "medium",
170
+ "targetAudience": "developers"
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ mock_client = Mock()
177
+ mock_client.post.return_value = mock_response
178
+
179
+ result = scrape(mock_client, "https://example.com", ScrapeOptions(formats=["branding"]))
180
+
181
+ assert result.branding is not None
182
+ assert result.branding.color_scheme == "light"
183
+ assert result.branding.logo == "https://example.com/logo.png"
184
+ assert len(result.branding.fonts) == 2
185
+ assert result.branding.typography["fontStacks"]["body"] == ["Inter", "sans-serif"]
186
+ assert result.branding.spacing["padding"] == {"sm": 8, "md": 16}
187
+ assert result.branding.icons["style"] == "outline"
188
+ assert result.branding.images["favicon"] == "https://example.com/favicon.ico"
189
+ assert result.branding.animations["easing"] == "ease-in-out"
190
+ assert result.branding.layout["grid"]["columns"] == 12
191
+ assert result.branding.personality["tone"] == "professional"
192
+
193
+ def test_branding_colorscheme_normalization(self):
194
+ """Test that colorScheme is normalized to color_scheme."""
195
+ mock_response = Mock()
196
+ mock_response.ok = True
197
+ mock_response.json.return_value = {
198
+ "success": True,
199
+ "data": {
200
+ "branding": {
201
+ "colorScheme": "dark",
202
+ "colors": {"primary": "#000000"}
203
+ }
204
+ }
205
+ }
206
+
207
+ mock_client = Mock()
208
+ mock_client.post.return_value = mock_response
209
+
210
+ result = scrape(mock_client, "https://example.com", ScrapeOptions(formats=["branding"]))
211
+
212
+ assert result.branding is not None
213
+ assert result.branding.color_scheme == "dark"
214
+ assert not hasattr(result.branding, "colorScheme")
firecrawl/v2/types.py CHANGED
@@ -124,6 +124,24 @@ class AttributeResult(BaseModel):
124
124
  attribute: str
125
125
  values: List[str]
126
126
 
127
+ class BrandingProfile(BaseModel):
128
+ """Branding information extracted from a website."""
129
+ model_config = {"extra": "allow"}
130
+
131
+ color_scheme: Optional[Literal["light", "dark"]] = None
132
+ logo: Optional[str] = None
133
+ fonts: Optional[List[Dict[str, Any]]] = None
134
+ colors: Optional[Dict[str, str]] = None
135
+ typography: Optional[Dict[str, Any]] = None
136
+ spacing: Optional[Dict[str, Any]] = None
137
+ components: Optional[Dict[str, Any]] = None
138
+ icons: Optional[Dict[str, str]] = None
139
+ images: Optional[Dict[str, Optional[str]]] = None
140
+ animations: Optional[Dict[str, str]] = None
141
+ layout: Optional[Dict[str, Any]] = None
142
+ tone: Optional[Dict[str, str]] = None
143
+ personality: Optional[Dict[str, Any]] = None
144
+
127
145
  class Document(BaseModel):
128
146
  """A scraped document."""
129
147
  markdown: Optional[str] = None
@@ -138,6 +156,7 @@ class Document(BaseModel):
138
156
  actions: Optional[Dict[str, Any]] = None
139
157
  warning: Optional[str] = None
140
158
  change_tracking: Optional[Dict[str, Any]] = None
159
+ branding: Optional[BrandingProfile] = None
141
160
 
142
161
  @property
143
162
  def metadata_typed(self) -> DocumentMetadata:
@@ -199,7 +218,7 @@ CategoryOption = Union[str, Category]
199
218
 
200
219
  FormatString = Literal[
201
220
  # camelCase versions (API format)
202
- "markdown", "html", "rawHtml", "links", "images", "screenshot", "summary", "changeTracking", "json", "attributes",
221
+ "markdown", "html", "rawHtml", "links", "images", "screenshot", "summary", "changeTracking", "json", "attributes", "branding",
203
222
  # snake_case versions (user-friendly)
204
223
  "raw_html", "change_tracking"
205
224
  ]
@@ -83,6 +83,7 @@ def normalize_document_input(doc: Dict[str, Any]) -> Dict[str, Any]:
83
83
  Normalize a raw Document dict from the API into the Python SDK's expected shape:
84
84
  - Convert top-level keys rawHtml->raw_html, changeTracking->change_tracking
85
85
  - Convert metadata keys from camelCase to snake_case
86
+ - Convert branding.colorScheme to branding.color_scheme
86
87
  """
87
88
  normalized = dict(doc)
88
89
 
@@ -102,6 +103,12 @@ def normalize_document_input(doc: Dict[str, Any]) -> Dict[str, Any]:
102
103
  # Fallback to mapped dict if model construction fails for any reason
103
104
  normalized["metadata"] = mapped
104
105
 
106
+ # Normalize branding top-level camelCase keys
107
+ branding = normalized.get("branding")
108
+ if isinstance(branding, dict):
109
+ if "colorScheme" in branding and "color_scheme" not in branding:
110
+ branding["color_scheme"] = branding.pop("colorScheme")
111
+
105
112
  return normalized
106
113
 
107
114
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firecrawl-py
3
- Version: 4.5.0
3
+ Version: 4.6.0
4
4
  Summary: Python SDK for Firecrawl API
5
5
  Home-page: https://github.com/firecrawl/firecrawl
6
6
  Author: Mendable.ai
@@ -1,4 +1,4 @@
1
- firecrawl/__init__.py,sha256=h5JuKDOp26ACul3BYZGHNqnTW8xeuPd7kiSSPtW1tVo,2192
1
+ firecrawl/__init__.py,sha256=6p6XlO31z9YF4NSnKZfUjXKA0s0xfp6q1biEfvnnlfU,2192
2
2
  firecrawl/client.py,sha256=Lmrg2jniCETU6_xVMn_fgLrgDXiBixK9hSkkdsCGiog,11840
3
3
  firecrawl/firecrawl.backup.py,sha256=v1FEN3jR4g5Aupg4xp6SLkuFvYMQuUKND2YELbYjE6c,200430
4
4
  firecrawl/types.py,sha256=RmLTq14Z-Nf883wgZxubrtn2HDu9mecsCEdcIdBCu14,2923
@@ -21,6 +21,7 @@ firecrawl/__tests__/e2e/v2/aio/test_aio_search.py,sha256=_IkHkIuvWY6vH99EsqrCZuK
21
21
  firecrawl/__tests__/e2e/v2/aio/test_aio_usage.py,sha256=lVGfwR79eaZamUZXgKStUJcpclCnnlpwHGo2pMOUhCY,1255
22
22
  firecrawl/__tests__/e2e/v2/aio/test_aio_watcher.py,sha256=hwES4Nu5c0hniZ9heIPDfvh_2JmJ2wPoX9ULTZ0Asjs,1471
23
23
  firecrawl/__tests__/unit/v2/methods/test_batch_request_preparation.py,sha256=xAx-aH4bD6uCWavg1cw_8-9FnLIFJNkvVPyOCVJ7r2E,4052
24
+ firecrawl/__tests__/unit/v2/methods/test_branding.py,sha256=f5DkvCMSQKcLEvzByOO0Ae3FuCSj8YzJjDvey6svJJM,8281
24
25
  firecrawl/__tests__/unit/v2/methods/test_crawl_params.py,sha256=p9hzg14uAs1iHKXPDSXhGU6hEzPBF_Ae34RAf5XYa10,2387
25
26
  firecrawl/__tests__/unit/v2/methods/test_crawl_request_preparation.py,sha256=PEKbooNXfQwPpvcPHXABJnveztgAA-RFBhtlSs8uPro,8780
26
27
  firecrawl/__tests__/unit/v2/methods/test_crawl_validation.py,sha256=kErOmHSD01eMjXiMd4rgsMVGd_aU2G9uVymBjbAFoGw,3918
@@ -46,7 +47,7 @@ firecrawl/v1/client.py,sha256=2Rq38RxGnuf2dMCmr4cc3f-ythavcBkUyJmRrwLmMHg,208104
46
47
  firecrawl/v2/__init__.py,sha256=Jc6a8tBjYG5OPkjDM5pl-notyys-7DEj7PLEfepv3fc,137
47
48
  firecrawl/v2/client.py,sha256=32gO-51LVD2RqPO4o-4WLHLWKQIq-mA8aAbhS8fRftA,33675
48
49
  firecrawl/v2/client_async.py,sha256=ZgCcU3Ro_3PfqJ_tilBWcRqJr7BbQVd9VIVVOcftvr4,13963
49
- firecrawl/v2/types.py,sha256=wQLlJ3gc-EBWI6zMr_01P3U8M02772I_prhB7JUGNYA,26428
50
+ firecrawl/v2/types.py,sha256=a884jU6YruCAqSu8BFqGMM82j6UqK9oK111XLtqv-cY,27227
50
51
  firecrawl/v2/watcher.py,sha256=FOU71tqSKxgeuGycu4ye0SLc2dw7clIcoQjPsi-4Csc,14229
51
52
  firecrawl/v2/watcher_async.py,sha256=dMMACMgeKrne_xSYeRvPu0m8nXqdNkDEsaiNBiD5ilw,10370
52
53
  firecrawl/v2/methods/batch.py,sha256=-eGnCGgB76pY-BFVKG1DC58XViETWukQXtDU0esU_UU,14865
@@ -69,13 +70,13 @@ firecrawl/v2/utils/error_handler.py,sha256=Iuf916dHphDY8ObNNlWy75628DFeJ0Rv8ljRp
69
70
  firecrawl/v2/utils/get_version.py,sha256=0CxW_41q2hlzIxEWOivUCaYw3GFiSIH32RPUMcIgwAY,492
70
71
  firecrawl/v2/utils/http_client.py,sha256=0hII3mnF_1Vd1nElu-hC9PipTUABGamUKb27q92_m5E,6068
71
72
  firecrawl/v2/utils/http_client_async.py,sha256=Mt6Dw_i2R_W81ONXnl9N_AlPiggfylOPfbD5Rpgi7tA,1991
72
- firecrawl/v2/utils/normalize.py,sha256=nlTU6QRghT1YKZzNZlIQj4STSRuSUGrS9cCErZIcY5w,3636
73
+ firecrawl/v2/utils/normalize.py,sha256=JywPBsUsv6dEf_lKb77r9EZF09ltsY6gmppEKZC51jA,3964
73
74
  firecrawl/v2/utils/validation.py,sha256=zzpCK4McM4P8Cag0_8s-d7Ww0idyTWKB4-yk92MT-rY,15405
74
- firecrawl_py-4.5.0.dist-info/licenses/LICENSE,sha256=nPCunEDwjRGHlmjvsiDUyIWbkqqyj3Ej84ntnh0g0zA,1084
75
+ firecrawl_py-4.6.0.dist-info/licenses/LICENSE,sha256=nPCunEDwjRGHlmjvsiDUyIWbkqqyj3Ej84ntnh0g0zA,1084
75
76
  tests/test_api_key_handling.py,sha256=iNaHp6zc9bIwpN3DdiWB2Rzk0j7HCP7VgpRE_1byNYc,1303
76
77
  tests/test_change_tracking.py,sha256=_IJ5ShLcoj2fHDBaw-nE4I4lHdmDB617ocK_XMHhXps,4177
77
78
  tests/test_timeout_conversion.py,sha256=PWlIEMASQNhu4cp1OW_ebklnE9NCiigPnEFCtI5N3w0,3996
78
- firecrawl_py-4.5.0.dist-info/METADATA,sha256=PH1cM0pGLdc5Tk9E4GQo8eJjmPFBwSx-6jvjc7_ijVk,7395
79
- firecrawl_py-4.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
80
- firecrawl_py-4.5.0.dist-info/top_level.txt,sha256=8T3jOaSN5mtLghO-R3MQ8KO290gIX8hmfxQmglBPdLE,16
81
- firecrawl_py-4.5.0.dist-info/RECORD,,
79
+ firecrawl_py-4.6.0.dist-info/METADATA,sha256=z7fkdTFXovkdZ1_th5LTnix5M6h2Bw1uvWI3-XHdAmc,7395
80
+ firecrawl_py-4.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
81
+ firecrawl_py-4.6.0.dist-info/top_level.txt,sha256=8T3jOaSN5mtLghO-R3MQ8KO290gIX8hmfxQmglBPdLE,16
82
+ firecrawl_py-4.6.0.dist-info/RECORD,,