browserbase 1.7.0__tar.gz → 1.8.0__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 (140) hide show
  1. {browserbase-1.7.0 → browserbase-1.8.0}/.gitignore +1 -0
  2. browserbase-1.8.0/.release-please-manifest.json +3 -0
  3. {browserbase-1.7.0 → browserbase-1.8.0}/CHANGELOG.md +35 -0
  4. {browserbase-1.7.0 → browserbase-1.8.0}/CONTRIBUTING.md +1 -1
  5. {browserbase-1.7.0 → browserbase-1.8.0}/PKG-INFO +2 -2
  6. {browserbase-1.7.0 → browserbase-1.8.0}/pyproject.toml +2 -2
  7. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_compat.py +9 -2
  8. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_qs.py +4 -1
  9. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/__init__.py +1 -0
  10. browserbase-1.8.0/src/browserbase/_utils/_path.py +127 -0
  11. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_version.py +1 -1
  12. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/contexts.py +7 -7
  13. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/extensions.py +5 -5
  14. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/projects.py +5 -4
  15. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/sessions/downloads.py +3 -2
  16. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/sessions/logs.py +3 -2
  17. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/sessions/recording.py +3 -2
  18. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/sessions/sessions.py +7 -7
  19. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/sessions/uploads.py +3 -3
  20. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/search_web_response.py +0 -2
  21. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session_create_params.py +3 -0
  22. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/test_sessions.py +2 -0
  23. browserbase-1.8.0/tests/test_utils/test_path.py +89 -0
  24. browserbase-1.7.0/.release-please-manifest.json +0 -3
  25. {browserbase-1.7.0 → browserbase-1.8.0}/LICENSE +0 -0
  26. {browserbase-1.7.0 → browserbase-1.8.0}/MIGRATION.md +0 -0
  27. {browserbase-1.7.0 → browserbase-1.8.0}/README.md +0 -0
  28. {browserbase-1.7.0 → browserbase-1.8.0}/SECURITY.md +0 -0
  29. {browserbase-1.7.0 → browserbase-1.8.0}/api.md +0 -0
  30. {browserbase-1.7.0 → browserbase-1.8.0}/bin/check-release-environment +0 -0
  31. {browserbase-1.7.0 → browserbase-1.8.0}/bin/publish-pypi +0 -0
  32. {browserbase-1.7.0 → browserbase-1.8.0}/examples/.keep +0 -0
  33. {browserbase-1.7.0 → browserbase-1.8.0}/examples/__init__.py +0 -0
  34. {browserbase-1.7.0 → browserbase-1.8.0}/examples/e2e/README.md +0 -0
  35. {browserbase-1.7.0 → browserbase-1.8.0}/examples/e2e/__init__.py +0 -0
  36. {browserbase-1.7.0 → browserbase-1.8.0}/examples/e2e/test_playwright.py +0 -0
  37. {browserbase-1.7.0 → browserbase-1.8.0}/examples/e2e/test_selenium.py +0 -0
  38. {browserbase-1.7.0 → browserbase-1.8.0}/examples/packages/extensions/.gitignore +0 -0
  39. {browserbase-1.7.0 → browserbase-1.8.0}/examples/packages/extensions/browserbase-test/hello.html +0 -0
  40. {browserbase-1.7.0 → browserbase-1.8.0}/examples/packages/extensions/browserbase-test/images/logo.png +0 -0
  41. {browserbase-1.7.0 → browserbase-1.8.0}/examples/packages/extensions/browserbase-test/manifest.json +0 -0
  42. {browserbase-1.7.0 → browserbase-1.8.0}/examples/packages/extensions/browserbase-test/scripts/content.js +0 -0
  43. {browserbase-1.7.0 → browserbase-1.8.0}/examples/packages/logo.png +0 -0
  44. {browserbase-1.7.0 → browserbase-1.8.0}/examples/playwright_basic.py +0 -0
  45. {browserbase-1.7.0 → browserbase-1.8.0}/examples/playwright_captcha.py +0 -0
  46. {browserbase-1.7.0 → browserbase-1.8.0}/examples/playwright_contexts.py +0 -0
  47. {browserbase-1.7.0 → browserbase-1.8.0}/examples/playwright_downloads.py +0 -0
  48. {browserbase-1.7.0 → browserbase-1.8.0}/examples/playwright_extensions.py +0 -0
  49. {browserbase-1.7.0 → browserbase-1.8.0}/examples/playwright_proxy.py +0 -0
  50. {browserbase-1.7.0 → browserbase-1.8.0}/examples/playwright_upload.py +0 -0
  51. {browserbase-1.7.0 → browserbase-1.8.0}/examples/selenium_basic.py +0 -0
  52. {browserbase-1.7.0 → browserbase-1.8.0}/noxfile.py +0 -0
  53. {browserbase-1.7.0 → browserbase-1.8.0}/release-please-config.json +0 -0
  54. {browserbase-1.7.0 → browserbase-1.8.0}/requirements-dev.lock +0 -0
  55. {browserbase-1.7.0 → browserbase-1.8.0}/requirements.lock +0 -0
  56. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/__init__.py +0 -0
  57. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_base_client.py +0 -0
  58. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_client.py +0 -0
  59. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_constants.py +0 -0
  60. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_exceptions.py +0 -0
  61. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_files.py +0 -0
  62. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_models.py +0 -0
  63. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_resource.py +0 -0
  64. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_response.py +0 -0
  65. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_streaming.py +0 -0
  66. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_types.py +0 -0
  67. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_compat.py +0 -0
  68. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_datetime_parse.py +0 -0
  69. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_json.py +0 -0
  70. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_logs.py +0 -0
  71. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_proxy.py +0 -0
  72. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_reflection.py +0 -0
  73. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_resources_proxy.py +0 -0
  74. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_streams.py +0 -0
  75. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_sync.py +0 -0
  76. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_transform.py +0 -0
  77. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_typing.py +0 -0
  78. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/_utils/_utils.py +0 -0
  79. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/lib/.keep +0 -0
  80. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/py.typed +0 -0
  81. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/__init__.py +0 -0
  82. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/fetch_api.py +0 -0
  83. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/search.py +0 -0
  84. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/resources/sessions/__init__.py +0 -0
  85. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/__init__.py +0 -0
  86. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/context.py +0 -0
  87. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/context_create_params.py +0 -0
  88. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/context_create_response.py +0 -0
  89. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/context_update_response.py +0 -0
  90. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/extension.py +0 -0
  91. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/extension_create_params.py +0 -0
  92. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/fetch_api_create_params.py +0 -0
  93. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/fetch_api_create_response.py +0 -0
  94. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/project.py +0 -0
  95. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/project_list_response.py +0 -0
  96. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/project_usage.py +0 -0
  97. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/search_web_params.py +0 -0
  98. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session.py +0 -0
  99. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session_create_response.py +0 -0
  100. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session_list_params.py +0 -0
  101. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session_list_response.py +0 -0
  102. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session_live_urls.py +0 -0
  103. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session_retrieve_response.py +0 -0
  104. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/session_update_params.py +0 -0
  105. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/sessions/__init__.py +0 -0
  106. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/sessions/log_list_response.py +0 -0
  107. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/sessions/recording_retrieve_response.py +0 -0
  108. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/sessions/session_log.py +0 -0
  109. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/sessions/session_recording.py +0 -0
  110. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/sessions/upload_create_params.py +0 -0
  111. {browserbase-1.7.0 → browserbase-1.8.0}/src/browserbase/types/sessions/upload_create_response.py +0 -0
  112. {browserbase-1.7.0 → browserbase-1.8.0}/tests/__init__.py +0 -0
  113. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/__init__.py +0 -0
  114. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/sessions/__init__.py +0 -0
  115. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/sessions/test_downloads.py +0 -0
  116. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/sessions/test_logs.py +0 -0
  117. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/sessions/test_recording.py +0 -0
  118. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/sessions/test_uploads.py +0 -0
  119. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/test_contexts.py +0 -0
  120. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/test_extensions.py +0 -0
  121. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/test_fetch_api.py +0 -0
  122. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/test_projects.py +0 -0
  123. {browserbase-1.7.0 → browserbase-1.8.0}/tests/api_resources/test_search.py +0 -0
  124. {browserbase-1.7.0 → browserbase-1.8.0}/tests/conftest.py +0 -0
  125. {browserbase-1.7.0 → browserbase-1.8.0}/tests/sample_file.txt +0 -0
  126. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_client.py +0 -0
  127. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_deepcopy.py +0 -0
  128. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_extract_files.py +0 -0
  129. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_files.py +0 -0
  130. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_models.py +0 -0
  131. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_qs.py +0 -0
  132. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_required_args.py +0 -0
  133. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_response.py +0 -0
  134. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_streaming.py +0 -0
  135. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_transform.py +0 -0
  136. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_utils/test_datetime_parse.py +0 -0
  137. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_utils/test_json.py +0 -0
  138. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_utils/test_proxy.py +0 -0
  139. {browserbase-1.7.0 → browserbase-1.8.0}/tests/test_utils/test_typing.py +0 -0
  140. {browserbase-1.7.0 → browserbase-1.8.0}/tests/utils.py +0 -0
@@ -1,4 +1,5 @@
1
1
  .prism.log
2
+ .stdy.log
2
3
  _dev
3
4
 
4
5
  __pycache__
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.8.0"
3
+ }
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.0 (2026-04-06)
4
+
5
+ Full Changelog: [v1.7.0...v1.8.0](https://github.com/browserbase/sdk-python/compare/v1.7.0...v1.8.0)
6
+
7
+ ### Features
8
+
9
+ * [CORE-000][apps/api] Add verified to SDK ([1579bf9](https://github.com/browserbase/sdk-python/commit/1579bf91b323f565abc7bdf54a131cd3e8e2f4d9))
10
+ * **internal:** implement indices array format for query and form serialization ([17a85d5](https://github.com/browserbase/sdk-python/commit/17a85d54d36f45acf242d066b13c49e90b4a2777))
11
+ * Update search docs by removing description from typebox schemas ([7913112](https://github.com/browserbase/sdk-python/commit/7913112d697666bf4381119f5962c284bd91b410))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **deps:** bump minimum typing-extensions version ([962e72a](https://github.com/browserbase/sdk-python/commit/962e72a121f27a603f92311917426c9aee84d805))
17
+ * **pydantic:** do not pass `by_alias` unless set ([3e6a912](https://github.com/browserbase/sdk-python/commit/3e6a912dc0a117c2deb229bb5230d2294de3b129))
18
+ * sanitize endpoint path params ([8a63eb5](https://github.com/browserbase/sdk-python/commit/8a63eb5e275eb25849f22cf210d42487e8283078))
19
+
20
+
21
+ ### Chores
22
+
23
+ * **ci:** skip lint on metadata-only changes ([80a6a3c](https://github.com/browserbase/sdk-python/commit/80a6a3c61b73be9c2f99fbdd20b2e47e9024fec5))
24
+ * **internal:** tweak CI branches ([9901c59](https://github.com/browserbase/sdk-python/commit/9901c595fa68e1ab213ac22eb06aeebc722edb03))
25
+ * **internal:** update gitignore ([2e3ba9b](https://github.com/browserbase/sdk-python/commit/2e3ba9b833a663b2b355b2b4a4f4d698de731e5e))
26
+ * **tests:** bump steady to v0.19.4 ([f81180d](https://github.com/browserbase/sdk-python/commit/f81180d7502d612aaaab8665740f2cc133da1e80))
27
+ * **tests:** bump steady to v0.19.5 ([10d0b70](https://github.com/browserbase/sdk-python/commit/10d0b70f04059ff66f199b9512ffb4ab11699262))
28
+ * **tests:** bump steady to v0.19.6 ([f9d4f90](https://github.com/browserbase/sdk-python/commit/f9d4f900ca05f245a5ac91b26df8e8a4520ec1d3))
29
+ * **tests:** bump steady to v0.19.7 ([27c797d](https://github.com/browserbase/sdk-python/commit/27c797da65010f04a0cd3d2f42cfef5d6eeb8105))
30
+ * **tests:** bump steady to v0.20.1 ([17f2e35](https://github.com/browserbase/sdk-python/commit/17f2e357b8c9f09c34eec9a3f7a055b3f9c72d14))
31
+ * **tests:** bump steady to v0.20.2 ([85ef397](https://github.com/browserbase/sdk-python/commit/85ef397598b8856217510a081fa5ebecc94819d3))
32
+
33
+
34
+ ### Refactors
35
+
36
+ * **tests:** switch from prism to steady ([39217ea](https://github.com/browserbase/sdk-python/commit/39217ea5ffd68b03900a6272378a3b3a8d5efc4a))
37
+
3
38
  ## 1.7.0 (2026-03-16)
4
39
 
5
40
  Full Changelog: [v1.6.0...v1.7.0](https://github.com/browserbase/sdk-python/compare/v1.6.0...v1.7.0)
@@ -85,7 +85,7 @@ $ pip install ./path-to-wheel-file.whl
85
85
 
86
86
  ## Running tests
87
87
 
88
- Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
88
+ Most tests require you to [set up a mock server](https://github.com/dgellow/steady) against the OpenAPI spec to run the tests.
89
89
 
90
90
  ```sh
91
91
  $ ./scripts/mock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: browserbase
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: The official Python library for the Browserbase API
5
5
  Project-URL: Homepage, https://github.com/browserbase/sdk-python
6
6
  Project-URL: Repository, https://github.com/browserbase/sdk-python
@@ -27,7 +27,7 @@ Requires-Dist: distro<2,>=1.7.0
27
27
  Requires-Dist: httpx<1,>=0.23.0
28
28
  Requires-Dist: pydantic<3,>=1.9.0
29
29
  Requires-Dist: sniffio
30
- Requires-Dist: typing-extensions<5,>=4.10
30
+ Requires-Dist: typing-extensions<5,>=4.14
31
31
  Provides-Extra: aiohttp
32
32
  Requires-Dist: aiohttp; extra == 'aiohttp'
33
33
  Requires-Dist: httpx-aiohttp>=0.1.9; extra == 'aiohttp'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "browserbase"
3
- version = "1.7.0"
3
+ version = "1.8.0"
4
4
  description = "The official Python library for the Browserbase API"
5
5
  dynamic = ["readme"]
6
6
  license = "Apache-2.0"
@@ -11,7 +11,7 @@ authors = [
11
11
  dependencies = [
12
12
  "httpx>=0.23.0, <1",
13
13
  "pydantic>=1.9.0, <3",
14
- "typing-extensions>=4.10, <5",
14
+ "typing-extensions>=4.14, <5",
15
15
  "anyio>=3.5.0, <5",
16
16
  "distro>=1.7.0, <2",
17
17
  "sniffio",
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
4
4
  from datetime import date, datetime
5
- from typing_extensions import Self, Literal
5
+ from typing_extensions import Self, Literal, TypedDict
6
6
 
7
7
  import pydantic
8
8
  from pydantic.fields import FieldInfo
@@ -131,6 +131,10 @@ def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
131
131
  return model.model_dump_json(indent=indent)
132
132
 
133
133
 
134
+ class _ModelDumpKwargs(TypedDict, total=False):
135
+ by_alias: bool
136
+
137
+
134
138
  def model_dump(
135
139
  model: pydantic.BaseModel,
136
140
  *,
@@ -142,6 +146,9 @@ def model_dump(
142
146
  by_alias: bool | None = None,
143
147
  ) -> dict[str, Any]:
144
148
  if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
149
+ kwargs: _ModelDumpKwargs = {}
150
+ if by_alias is not None:
151
+ kwargs["by_alias"] = by_alias
145
152
  return model.model_dump(
146
153
  mode=mode,
147
154
  exclude=exclude,
@@ -149,7 +156,7 @@ def model_dump(
149
156
  exclude_defaults=exclude_defaults,
150
157
  # warnings are not supported in Pydantic v1
151
158
  warnings=True if PYDANTIC_V1 else warnings,
152
- by_alias=by_alias,
159
+ **kwargs,
153
160
  )
154
161
  return cast(
155
162
  "dict[str, Any]",
@@ -101,7 +101,10 @@ class Querystring:
101
101
  items.extend(self._stringify_item(key, item, opts))
102
102
  return items
103
103
  elif array_format == "indices":
104
- raise NotImplementedError("The array indices format is not supported yet")
104
+ items = []
105
+ for i, item in enumerate(value):
106
+ items.extend(self._stringify_item(f"{key}[{i}]", item, opts))
107
+ return items
105
108
  elif array_format == "brackets":
106
109
  items = []
107
110
  key = key + "[]"
@@ -1,3 +1,4 @@
1
+ from ._path import path_template as path_template
1
2
  from ._sync import asyncify as asyncify
2
3
  from ._proxy import LazyProxy as LazyProxy
3
4
  from ._utils import (
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import (
5
+ Any,
6
+ Mapping,
7
+ Callable,
8
+ )
9
+ from urllib.parse import quote
10
+
11
+ # Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E).
12
+ _DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$")
13
+
14
+ _PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")
15
+
16
+
17
+ def _quote_path_segment_part(value: str) -> str:
18
+ """Percent-encode `value` for use in a URI path segment.
19
+
20
+ Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe.
21
+ https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
22
+ """
23
+ # quote() already treats unreserved characters (letters, digits, and -._~)
24
+ # as safe, so we only need to add sub-delims, ':', and '@'.
25
+ # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted.
26
+ return quote(value, safe="!$&'()*+,;=:@")
27
+
28
+
29
+ def _quote_query_part(value: str) -> str:
30
+ """Percent-encode `value` for use in a URI query string.
31
+
32
+ Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe.
33
+ https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
34
+ """
35
+ return quote(value, safe="!$'()*+,;:@/?")
36
+
37
+
38
+ def _quote_fragment_part(value: str) -> str:
39
+ """Percent-encode `value` for use in a URI fragment.
40
+
41
+ Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe.
42
+ https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
43
+ """
44
+ return quote(value, safe="!$&'()*+,;=:@/?")
45
+
46
+
47
+ def _interpolate(
48
+ template: str,
49
+ values: Mapping[str, Any],
50
+ quoter: Callable[[str], str],
51
+ ) -> str:
52
+ """Replace {name} placeholders in `template`, quoting each value with `quoter`.
53
+
54
+ Placeholder names are looked up in `values`.
55
+
56
+ Raises:
57
+ KeyError: If a placeholder is not found in `values`.
58
+ """
59
+ # re.split with a capturing group returns alternating
60
+ # [text, name, text, name, ..., text] elements.
61
+ parts = _PLACEHOLDER_RE.split(template)
62
+
63
+ for i in range(1, len(parts), 2):
64
+ name = parts[i]
65
+ if name not in values:
66
+ raise KeyError(f"a value for placeholder {{{name}}} was not provided")
67
+ val = values[name]
68
+ if val is None:
69
+ parts[i] = "null"
70
+ elif isinstance(val, bool):
71
+ parts[i] = "true" if val else "false"
72
+ else:
73
+ parts[i] = quoter(str(values[name]))
74
+
75
+ return "".join(parts)
76
+
77
+
78
+ def path_template(template: str, /, **kwargs: Any) -> str:
79
+ """Interpolate {name} placeholders in `template` from keyword arguments.
80
+
81
+ Args:
82
+ template: The template string containing {name} placeholders.
83
+ **kwargs: Keyword arguments to interpolate into the template.
84
+
85
+ Returns:
86
+ The template with placeholders interpolated and percent-encoded.
87
+
88
+ Safe characters for percent-encoding are dependent on the URI component.
89
+ Placeholders in path and fragment portions are percent-encoded where the `segment`
90
+ and `fragment` sets from RFC 3986 respectively are considered safe.
91
+ Placeholders in the query portion are percent-encoded where the `query` set from
92
+ RFC 3986 §3.3 is considered safe except for = and & characters.
93
+
94
+ Raises:
95
+ KeyError: If a placeholder is not found in `kwargs`.
96
+ ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments).
97
+ """
98
+ # Split the template into path, query, and fragment portions.
99
+ fragment_template: str | None = None
100
+ query_template: str | None = None
101
+
102
+ rest = template
103
+ if "#" in rest:
104
+ rest, fragment_template = rest.split("#", 1)
105
+ if "?" in rest:
106
+ rest, query_template = rest.split("?", 1)
107
+ path_template = rest
108
+
109
+ # Interpolate each portion with the appropriate quoting rules.
110
+ path_result = _interpolate(path_template, kwargs, _quote_path_segment_part)
111
+
112
+ # Reject dot-segments (. and ..) in the final assembled path. The check
113
+ # runs after interpolation so that adjacent placeholders or a mix of static
114
+ # text and placeholders that together form a dot-segment are caught.
115
+ # Also reject percent-encoded dot-segments to protect against incorrectly
116
+ # implemented normalization in servers/proxies.
117
+ for segment in path_result.split("/"):
118
+ if _DOT_SEGMENT_RE.match(segment):
119
+ raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed")
120
+
121
+ result = path_result
122
+ if query_template is not None:
123
+ result += "?" + _interpolate(query_template, kwargs, _quote_query_part)
124
+ if fragment_template is not None:
125
+ result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part)
126
+
127
+ return result
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "browserbase"
4
- __version__ = "1.7.0" # x-release-please-version
4
+ __version__ = "1.8.0" # x-release-please-version
@@ -6,7 +6,7 @@ import httpx
6
6
 
7
7
  from ..types import context_create_params
8
8
  from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
9
- from .._utils import maybe_transform, async_maybe_transform
9
+ from .._utils import path_template, maybe_transform, async_maybe_transform
10
10
  from .._compat import cached_property
11
11
  from .._resource import SyncAPIResource, AsyncAPIResource
12
12
  from .._response import (
@@ -106,7 +106,7 @@ class ContextsResource(SyncAPIResource):
106
106
  if not id:
107
107
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
108
108
  return self._get(
109
- f"/v1/contexts/{id}",
109
+ path_template("/v1/contexts/{id}", id=id),
110
110
  options=make_request_options(
111
111
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
112
112
  ),
@@ -139,7 +139,7 @@ class ContextsResource(SyncAPIResource):
139
139
  if not id:
140
140
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
141
141
  return self._put(
142
- f"/v1/contexts/{id}",
142
+ path_template("/v1/contexts/{id}", id=id),
143
143
  options=make_request_options(
144
144
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
145
145
  ),
@@ -173,7 +173,7 @@ class ContextsResource(SyncAPIResource):
173
173
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
174
174
  extra_headers = {"Accept": "*/*", **(extra_headers or {})}
175
175
  return self._delete(
176
- f"/v1/contexts/{id}",
176
+ path_template("/v1/contexts/{id}", id=id),
177
177
  options=make_request_options(
178
178
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
179
179
  ),
@@ -264,7 +264,7 @@ class AsyncContextsResource(AsyncAPIResource):
264
264
  if not id:
265
265
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
266
266
  return await self._get(
267
- f"/v1/contexts/{id}",
267
+ path_template("/v1/contexts/{id}", id=id),
268
268
  options=make_request_options(
269
269
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
270
270
  ),
@@ -297,7 +297,7 @@ class AsyncContextsResource(AsyncAPIResource):
297
297
  if not id:
298
298
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
299
299
  return await self._put(
300
- f"/v1/contexts/{id}",
300
+ path_template("/v1/contexts/{id}", id=id),
301
301
  options=make_request_options(
302
302
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
303
303
  ),
@@ -331,7 +331,7 @@ class AsyncContextsResource(AsyncAPIResource):
331
331
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
332
332
  extra_headers = {"Accept": "*/*", **(extra_headers or {})}
333
333
  return await self._delete(
334
- f"/v1/contexts/{id}",
334
+ path_template("/v1/contexts/{id}", id=id),
335
335
  options=make_request_options(
336
336
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
337
337
  ),
@@ -8,7 +8,7 @@ import httpx
8
8
 
9
9
  from ..types import extension_create_params
10
10
  from .._types import Body, Query, Headers, NoneType, NotGiven, FileTypes, not_given
11
- from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
11
+ from .._utils import extract_files, path_template, maybe_transform, deepcopy_minimal, async_maybe_transform
12
12
  from .._compat import cached_property
13
13
  from .._resource import SyncAPIResource, AsyncAPIResource
14
14
  from .._response import (
@@ -108,7 +108,7 @@ class ExtensionsResource(SyncAPIResource):
108
108
  if not id:
109
109
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
110
110
  return self._get(
111
- f"/v1/extensions/{id}",
111
+ path_template("/v1/extensions/{id}", id=id),
112
112
  options=make_request_options(
113
113
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
114
114
  ),
@@ -142,7 +142,7 @@ class ExtensionsResource(SyncAPIResource):
142
142
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
143
143
  extra_headers = {"Accept": "*/*", **(extra_headers or {})}
144
144
  return self._delete(
145
- f"/v1/extensions/{id}",
145
+ path_template("/v1/extensions/{id}", id=id),
146
146
  options=make_request_options(
147
147
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
148
148
  ),
@@ -235,7 +235,7 @@ class AsyncExtensionsResource(AsyncAPIResource):
235
235
  if not id:
236
236
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
237
237
  return await self._get(
238
- f"/v1/extensions/{id}",
238
+ path_template("/v1/extensions/{id}", id=id),
239
239
  options=make_request_options(
240
240
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
241
241
  ),
@@ -269,7 +269,7 @@ class AsyncExtensionsResource(AsyncAPIResource):
269
269
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
270
270
  extra_headers = {"Accept": "*/*", **(extra_headers or {})}
271
271
  return await self._delete(
272
- f"/v1/extensions/{id}",
272
+ path_template("/v1/extensions/{id}", id=id),
273
273
  options=make_request_options(
274
274
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
275
275
  ),
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import httpx
6
6
 
7
7
  from .._types import Body, Query, Headers, NotGiven, not_given
8
+ from .._utils import path_template
8
9
  from .._compat import cached_property
9
10
  from .._resource import SyncAPIResource, AsyncAPIResource
10
11
  from .._response import (
@@ -67,7 +68,7 @@ class ProjectsResource(SyncAPIResource):
67
68
  if not id:
68
69
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
69
70
  return self._get(
70
- f"/v1/projects/{id}",
71
+ path_template("/v1/projects/{id}", id=id),
71
72
  options=make_request_options(
72
73
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
73
74
  ),
@@ -119,7 +120,7 @@ class ProjectsResource(SyncAPIResource):
119
120
  if not id:
120
121
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
121
122
  return self._get(
122
- f"/v1/projects/{id}/usage",
123
+ path_template("/v1/projects/{id}/usage", id=id),
123
124
  options=make_request_options(
124
125
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
125
126
  ),
@@ -173,7 +174,7 @@ class AsyncProjectsResource(AsyncAPIResource):
173
174
  if not id:
174
175
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
175
176
  return await self._get(
176
- f"/v1/projects/{id}",
177
+ path_template("/v1/projects/{id}", id=id),
177
178
  options=make_request_options(
178
179
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
179
180
  ),
@@ -225,7 +226,7 @@ class AsyncProjectsResource(AsyncAPIResource):
225
226
  if not id:
226
227
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
227
228
  return await self._get(
228
- f"/v1/projects/{id}/usage",
229
+ path_template("/v1/projects/{id}/usage", id=id),
229
230
  options=make_request_options(
230
231
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
231
232
  ),
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import httpx
6
6
 
7
7
  from ..._types import Body, Query, Headers, NotGiven, not_given
8
+ from ..._utils import path_template
8
9
  from ..._compat import cached_property
9
10
  from ..._resource import SyncAPIResource, AsyncAPIResource
10
11
  from ..._response import (
@@ -69,7 +70,7 @@ class DownloadsResource(SyncAPIResource):
69
70
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
70
71
  extra_headers = {"Accept": "application/zip", **(extra_headers or {})}
71
72
  return self._get(
72
- f"/v1/sessions/{id}/downloads",
73
+ path_template("/v1/sessions/{id}/downloads", id=id),
73
74
  options=make_request_options(
74
75
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
75
76
  ),
@@ -124,7 +125,7 @@ class AsyncDownloadsResource(AsyncAPIResource):
124
125
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
125
126
  extra_headers = {"Accept": "application/zip", **(extra_headers or {})}
126
127
  return await self._get(
127
- f"/v1/sessions/{id}/downloads",
128
+ path_template("/v1/sessions/{id}/downloads", id=id),
128
129
  options=make_request_options(
129
130
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
130
131
  ),
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import httpx
6
6
 
7
7
  from ..._types import Body, Query, Headers, NotGiven, not_given
8
+ from ..._utils import path_template
8
9
  from ..._compat import cached_property
9
10
  from ..._resource import SyncAPIResource, AsyncAPIResource
10
11
  from ..._response import (
@@ -65,7 +66,7 @@ class LogsResource(SyncAPIResource):
65
66
  if not id:
66
67
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
67
68
  return self._get(
68
- f"/v1/sessions/{id}/logs",
69
+ path_template("/v1/sessions/{id}/logs", id=id),
69
70
  options=make_request_options(
70
71
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
71
72
  ),
@@ -119,7 +120,7 @@ class AsyncLogsResource(AsyncAPIResource):
119
120
  if not id:
120
121
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
121
122
  return await self._get(
122
- f"/v1/sessions/{id}/logs",
123
+ path_template("/v1/sessions/{id}/logs", id=id),
123
124
  options=make_request_options(
124
125
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
125
126
  ),
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import httpx
6
6
 
7
7
  from ..._types import Body, Query, Headers, NotGiven, not_given
8
+ from ..._utils import path_template
8
9
  from ..._compat import cached_property
9
10
  from ..._resource import SyncAPIResource, AsyncAPIResource
10
11
  from ..._response import (
@@ -65,7 +66,7 @@ class RecordingResource(SyncAPIResource):
65
66
  if not id:
66
67
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
67
68
  return self._get(
68
- f"/v1/sessions/{id}/recording",
69
+ path_template("/v1/sessions/{id}/recording", id=id),
69
70
  options=make_request_options(
70
71
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
71
72
  ),
@@ -119,7 +120,7 @@ class AsyncRecordingResource(AsyncAPIResource):
119
120
  if not id:
120
121
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
121
122
  return await self._get(
122
- f"/v1/sessions/{id}/recording",
123
+ path_template("/v1/sessions/{id}/recording", id=id),
123
124
  options=make_request_options(
124
125
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
125
126
  ),
@@ -25,7 +25,7 @@ from .uploads import (
25
25
  AsyncUploadsResourceWithStreamingResponse,
26
26
  )
27
27
  from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
28
- from ..._utils import maybe_transform, async_maybe_transform
28
+ from ..._utils import path_template, maybe_transform, async_maybe_transform
29
29
  from ..._compat import cached_property
30
30
  from .downloads import (
31
31
  DownloadsResource,
@@ -195,7 +195,7 @@ class SessionsResource(SyncAPIResource):
195
195
  if not id:
196
196
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
197
197
  return self._get(
198
- f"/v1/sessions/{id}",
198
+ path_template("/v1/sessions/{id}", id=id),
199
199
  options=make_request_options(
200
200
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
201
201
  ),
@@ -237,7 +237,7 @@ class SessionsResource(SyncAPIResource):
237
237
  if not id:
238
238
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
239
239
  return self._post(
240
- f"/v1/sessions/{id}",
240
+ path_template("/v1/sessions/{id}", id=id),
241
241
  body=maybe_transform(
242
242
  {
243
243
  "status": status,
@@ -324,7 +324,7 @@ class SessionsResource(SyncAPIResource):
324
324
  if not id:
325
325
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
326
326
  return self._get(
327
- f"/v1/sessions/{id}/debug",
327
+ path_template("/v1/sessions/{id}/debug", id=id),
328
328
  options=make_request_options(
329
329
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
330
330
  ),
@@ -467,7 +467,7 @@ class AsyncSessionsResource(AsyncAPIResource):
467
467
  if not id:
468
468
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
469
469
  return await self._get(
470
- f"/v1/sessions/{id}",
470
+ path_template("/v1/sessions/{id}", id=id),
471
471
  options=make_request_options(
472
472
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
473
473
  ),
@@ -509,7 +509,7 @@ class AsyncSessionsResource(AsyncAPIResource):
509
509
  if not id:
510
510
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
511
511
  return await self._post(
512
- f"/v1/sessions/{id}",
512
+ path_template("/v1/sessions/{id}", id=id),
513
513
  body=await async_maybe_transform(
514
514
  {
515
515
  "status": status,
@@ -596,7 +596,7 @@ class AsyncSessionsResource(AsyncAPIResource):
596
596
  if not id:
597
597
  raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
598
598
  return await self._get(
599
- f"/v1/sessions/{id}/debug",
599
+ path_template("/v1/sessions/{id}/debug", id=id),
600
600
  options=make_request_options(
601
601
  extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
602
602
  ),
@@ -7,7 +7,7 @@ from typing import Mapping, cast
7
7
  import httpx
8
8
 
9
9
  from ..._types import Body, Query, Headers, NotGiven, FileTypes, not_given
10
- from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
10
+ from ..._utils import extract_files, path_template, maybe_transform, deepcopy_minimal, async_maybe_transform
11
11
  from ..._compat import cached_property
12
12
  from ..._resource import SyncAPIResource, AsyncAPIResource
13
13
  from ..._response import (
@@ -76,7 +76,7 @@ class UploadsResource(SyncAPIResource):
76
76
  # multipart/form-data; boundary=---abc--
77
77
  extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
78
78
  return self._post(
79
- f"/v1/sessions/{id}/uploads",
79
+ path_template("/v1/sessions/{id}/uploads", id=id),
80
80
  body=maybe_transform(body, upload_create_params.UploadCreateParams),
81
81
  files=files,
82
82
  options=make_request_options(
@@ -139,7 +139,7 @@ class AsyncUploadsResource(AsyncAPIResource):
139
139
  # multipart/form-data; boundary=---abc--
140
140
  extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
141
141
  return await self._post(
142
- f"/v1/sessions/{id}/uploads",
142
+ path_template("/v1/sessions/{id}/uploads", id=id),
143
143
  body=await async_maybe_transform(body, upload_create_params.UploadCreateParams),
144
144
  files=files,
145
145
  options=make_request_options(
@@ -34,8 +34,6 @@ class Result(BaseModel):
34
34
 
35
35
 
36
36
  class SearchWebResponse(BaseModel):
37
- """Response body for web search"""
38
-
39
37
  query: str
40
38
  """The search query that was executed"""
41
39
 
@@ -123,6 +123,9 @@ class BrowserSettings(TypedDict, total=False):
123
123
  solve_captchas: Annotated[bool, PropertyInfo(alias="solveCaptchas")]
124
124
  """Enable or disable captcha solving in the browser. Defaults to `true`."""
125
125
 
126
+ verified: bool
127
+ """Verified Browser Mode"""
128
+
126
129
  viewport: BrowserSettingsViewport
127
130
 
128
131
 
@@ -45,6 +45,7 @@ class TestSessions:
45
45
  "os": "windows",
46
46
  "record_session": True,
47
47
  "solve_captchas": True,
48
+ "verified": True,
48
49
  "viewport": {
49
50
  "height": 0,
50
51
  "width": 0,
@@ -278,6 +279,7 @@ class TestAsyncSessions:
278
279
  "os": "windows",
279
280
  "record_session": True,
280
281
  "solve_captchas": True,
282
+ "verified": True,
281
283
  "viewport": {
282
284
  "height": 0,
283
285
  "width": 0,