smooth-py 0.3.0.post2__tar.gz → 0.3.1.dev20251027__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.

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: smooth-py
3
- Version: 0.3.0.post2
3
+ Version: 0.3.1.dev20251027
4
4
  Summary:
5
5
  Author: Luca Pinchetti
6
6
  Author-email: luca@circlemind.co
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "smooth-py"
3
- version = "0.3.0.post2"
3
+ version = "0.3.1.dev20251027"
4
4
  description = ""
5
5
  authors = [
6
6
  {name = "Luca Pinchetti",email = "luca@circlemind.co"}
@@ -1,6 +1,7 @@
1
1
  """Smooth python SDK."""
2
2
 
3
3
  import asyncio
4
+ import base64
4
5
  import io
5
6
  import logging
6
7
  import os
@@ -8,7 +9,7 @@ import time
8
9
  import urllib.parse
9
10
  import warnings
10
11
  from pathlib import Path
11
- from typing import Any, Literal, Type
12
+ from typing import Any, Literal, NotRequired, Type, TypedDict
12
13
 
13
14
  import httpx
14
15
  import requests
@@ -33,7 +34,47 @@ def _encode_url(url: str, interactive: bool = True, embed: bool = False) -> str:
33
34
 
34
35
 
35
36
  # --- Models ---
36
- # These models define the data structures for API requests and responses.
37
+
38
+ class Certificate(TypedDict):
39
+ """Client certificate for accessing secure websites.
40
+
41
+ Attributes:
42
+ file: p12 file object to be uploaded (e.g., open("cert.p12", "rb")).
43
+ password: Password to decrypt the certificate file. Optional.
44
+ """
45
+
46
+ file: str | io.IOBase # Required - base64 string or binary IO
47
+ password: NotRequired[str] # Optional
48
+ filters: NotRequired[list[str]] # Optional - TODO: Reserved for future use to specify URL patterns where the certificate should be applied.
49
+
50
+
51
+ def _process_certificates(certificates: list[Certificate] | None) -> list[dict[str, Any]] | None:
52
+ """Process certificates, converting binary IO to base64-encoded strings.
53
+
54
+ Args:
55
+ certificates: List of certificates with file field as string or binary IO.
56
+
57
+ Returns:
58
+ List of certificates with file field as base64-encoded string, or None if input is None.
59
+ """
60
+ if certificates is None:
61
+ return None
62
+
63
+ processed_certs = []
64
+ for cert in certificates:
65
+ processed_cert = dict(cert) # Create a copy
66
+
67
+ file_content = processed_cert["file"]
68
+ if isinstance(file_content, io.IOBase):
69
+ # Read the binary content and encode to base64
70
+ binary_data = file_content.read()
71
+ processed_cert["file"] = base64.b64encode(binary_data).decode("utf-8")
72
+ elif not isinstance(file_content, str):
73
+ raise TypeError(f"Certificate file must be a string or binary IO, got {type(file_content)}")
74
+
75
+ processed_certs.append(processed_cert)
76
+
77
+ return processed_certs
37
78
 
38
79
 
39
80
  class TaskResponse(BaseModel):
@@ -48,6 +89,7 @@ class TaskResponse(BaseModel):
48
89
  device: Literal["desktop", "mobile"] | None = Field(default=None, description="The device type used for the task.")
49
90
  live_url: str | None = Field(default=None, description="The URL to view and interact with the task execution.")
50
91
  recording_url: str | None = Field(default=None, description="The URL to view the task recording.")
92
+ created_at: int | None = Field(default=None, description="The timestamp when the task was created.")
51
93
 
52
94
 
53
95
  class TaskRequest(BaseModel):
@@ -97,6 +139,15 @@ class TaskRequest(BaseModel):
97
139
  )
98
140
  proxy_username: str | None = Field(default=None, description="Proxy server username.")
99
141
  proxy_password: str | None = Field(default=None, description="Proxy server password.")
142
+ certificates: list[dict[str, Any]] | None = Field(
143
+ default=None,
144
+ description=(
145
+ "List of client certificates to use when accessing secure websites. "
146
+ "Each certificate is a dictionary with the following fields:\n"
147
+ " - `file`: p12 file object to be uploaded (e.g., open('cert.p12', 'rb')).\n"
148
+ " - `password` (optional): Password to decrypt the certificate file."
149
+ ),
150
+ )
100
151
  experimental_features: dict[str, Any] | None = Field(
101
152
  default=None, description="Experimental features to enable for the task."
102
153
  )
@@ -490,6 +541,7 @@ class SmoothClient(BaseClient):
490
541
  proxy_server: str | None = None,
491
542
  proxy_username: str | None = None,
492
543
  proxy_password: str | None = None,
544
+ certificates: list[Certificate] | None = None,
493
545
  experimental_features: dict[str, Any] | None = None,
494
546
  ) -> TaskHandle:
495
547
  """Runs a task and returns a handle to the task.
@@ -516,6 +568,10 @@ class SmoothClient(BaseClient):
516
568
  proxy_server: Proxy server url to route browser traffic through.
517
569
  proxy_username: Proxy server username.
518
570
  proxy_password: Proxy server password.
571
+ certificates: List of client certificates to use when accessing secure websites.
572
+ Each certificate is a dictionary with the following fields:
573
+ - `file` (required): p12 file object to be uploaded (e.g., open("cert.p12", "rb")).
574
+ - `password` (optional): Password to decrypt the certificate file, if password-protected.
519
575
  experimental_features: Experimental features to enable for the task.
520
576
 
521
577
  Returns:
@@ -541,6 +597,7 @@ class SmoothClient(BaseClient):
541
597
  proxy_server=proxy_server,
542
598
  proxy_username=proxy_username,
543
599
  proxy_password=proxy_password,
600
+ certificates=_process_certificates(certificates),
544
601
  experimental_features=experimental_features,
545
602
  )
546
603
  initial_response = self._submit_task(payload)
@@ -811,6 +868,7 @@ class SmoothAsyncClient(BaseClient):
811
868
  proxy_server: str | None = None,
812
869
  proxy_username: str | None = None,
813
870
  proxy_password: str | None = None,
871
+ certificates: list[Certificate] | None = None,
814
872
  experimental_features: dict[str, Any] | None = None,
815
873
  ) -> AsyncTaskHandle:
816
874
  """Runs a task and returns a handle to the task asynchronously.
@@ -837,6 +895,10 @@ class SmoothAsyncClient(BaseClient):
837
895
  proxy_server: Proxy server url to route browser traffic through.
838
896
  proxy_username: Proxy server username.
839
897
  proxy_password: Proxy server password.
898
+ certificates: List of client certificates to use when accessing secure websites.
899
+ Each certificate is a dictionary with the following fields:
900
+ - `file` (required): p12 file object to be uploaded (e.g., open("cert.p12", "rb")).
901
+ - `password` (optional): Password to decrypt the certificate file.
840
902
  experimental_features: Experimental features to enable for the task.
841
903
 
842
904
  Returns:
@@ -862,6 +924,7 @@ class SmoothAsyncClient(BaseClient):
862
924
  proxy_server=proxy_server,
863
925
  proxy_username=proxy_username,
864
926
  proxy_password=proxy_password,
927
+ certificates=_process_certificates(certificates),
865
928
  experimental_features=experimental_features,
866
929
  )
867
930
 
@@ -1000,6 +1063,7 @@ __all__ = [
1000
1063
  "BrowserSessionResponse",
1001
1064
  "BrowserSessionsResponse",
1002
1065
  "UploadFileResponse",
1066
+ "Certificate",
1003
1067
  "ApiError",
1004
1068
  "TimeoutError",
1005
1069
  ]