smooth-py 0.1.2.post0__py3-none-any.whl → 0.1.3.post0__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 smooth-py might be problematic. Click here for more details.

smooth/__init__.py CHANGED
@@ -8,6 +8,7 @@ from typing import (
8
8
  Any,
9
9
  Literal,
10
10
  )
11
+ from urllib.parse import urlencode
11
12
 
12
13
  import httpx
13
14
  import requests
@@ -42,7 +43,7 @@ class TaskRequest(BaseModel):
42
43
  agent: Literal["smooth"] = Field(default="smooth", description="The agent to use for the task.")
43
44
  max_steps: int = Field(default=32, ge=2, le=128, description="Maximum number of steps the agent can take (min 2, max 128).")
44
45
  device: Literal["desktop", "mobile"] = Field(default="mobile", description="Device type for the task. Default is mobile.")
45
- enable_recording: bool = Field(default=False, description="Enable video recording of the task execution. Default is False")
46
+ enable_recording: bool = Field(default=True, description="Enable video recording of the task execution. Default is True")
46
47
  session_id: str | None = Field(
47
48
  default=None,
48
49
  description="Browser session ID to use. Each session maintains its own state, such as login credentials.",
@@ -51,7 +52,7 @@ class TaskRequest(BaseModel):
51
52
  proxy_server: str | None = Field(
52
53
  default=None,
53
54
  description=(
54
- "Proxy server url to route browser traffic through." " Must include the protocol to use (e.g. http:// or https://)"
55
+ "Proxy server url to route browser traffic through. Must include the protocol to use (e.g. http:// or https://)"
55
56
  ),
56
57
  )
57
58
  proxy_username: str | None = Field(default=None, description="Proxy server username.")
@@ -153,29 +154,65 @@ class BaseClient:
153
154
  class TaskHandle:
154
155
  """A handle to a running task."""
155
156
 
156
- def __init__(self, task_id: str, client: "SmoothClient", live_url: str | None, poll_interval: int, timeout: int | None):
157
+ def __init__(self, task_id: str, client: "SmoothClient"):
157
158
  """Initializes the task handle."""
158
159
  self._client = client
159
- self._poll_interval = poll_interval
160
- self._timeout = timeout
161
160
  self._task_response: TaskResponse | None = None
162
161
 
163
162
  self.id = task_id
164
- self.live_url = live_url
165
163
 
166
- def result(self) -> TaskResponse:
164
+ def result(self, timeout: int | None = None, poll_interval: float = 1) -> TaskResponse:
167
165
  """Waits for the task to complete and returns the result."""
168
166
  if self._task_response and self._task_response.status not in ["running", "waiting"]:
169
167
  return self._task_response
170
168
 
169
+ if timeout is not None and timeout < 1:
170
+ raise ValueError("Timeout must be at least 1 second.")
171
+ if poll_interval < 0.1:
172
+ raise ValueError("Poll interval must be at least 100 milliseconds.")
173
+
171
174
  start_time = time.time()
172
- while self._timeout is None or (time.time() - start_time) < self._timeout:
175
+ while timeout is None or (time.time() - start_time) < timeout:
173
176
  task_response = self._client._get_task(self.id) # pyright: ignore [reportPrivateUsage]
177
+ self._task_response = task_response
174
178
  if task_response.status not in ["running", "waiting"]:
175
- self._task_response = task_response
176
179
  return task_response
177
- time.sleep(self._poll_interval)
178
- raise TimeoutError(f"Task {self.id} did not complete within {self._timeout} seconds.")
180
+ time.sleep(poll_interval)
181
+ raise TimeoutError(f"Task {self.id} did not complete within {timeout} seconds.")
182
+
183
+ def live_url(self, interactive: bool = True, embed: bool = False, timeout: int | None = None):
184
+ """Returns the live URL for the task."""
185
+ params = {
186
+ "interactive": interactive,
187
+ "embed": embed
188
+ }
189
+
190
+ if self._task_response and self._task_response.live_url:
191
+ return f"{self._task_response.live_url}?{urlencode(params)}"
192
+
193
+ start_time = time.time()
194
+ while timeout is None or (time.time() - start_time) < timeout:
195
+ task_response = self._client._get_task(self.id) # pyright: ignore [reportPrivateUsage]
196
+ self._task_response = task_response
197
+ if self._task_response.live_url:
198
+ return f"{self._task_response.live_url}?{urlencode(params)}"
199
+ time.sleep(1)
200
+
201
+ raise TimeoutError(f"Live URL not available for task {self.id}.")
202
+
203
+ def recording_url(self, timeout: int | None = None) -> str:
204
+ """Returns the recording URL for the task."""
205
+ if self._task_response and self._task_response.recording_url is not None:
206
+ return self._task_response.recording_url
207
+
208
+ start_time = time.time()
209
+ while timeout is None or (time.time() - start_time) < timeout:
210
+ task_response = self._client._get_task(self.id) # pyright: ignore [reportPrivateUsage]
211
+ self._task_response = task_response
212
+ if task_response.recording_url is not None:
213
+ return task_response.recording_url
214
+ time.sleep(1)
215
+ raise TimeoutError(f"Recording URL not available for task {self.id}.")
179
216
 
180
217
 
181
218
  class SmoothClient(BaseClient):
@@ -226,8 +263,6 @@ class SmoothClient(BaseClient):
226
263
  def run(
227
264
  self,
228
265
  task: str,
229
- poll_interval: int = 1,
230
- timeout: int = 60 * 15,
231
266
  agent: Literal["smooth"] = "smooth",
232
267
  max_steps: int = 32,
233
268
  device: Literal["desktop", "mobile"] = "mobile",
@@ -245,8 +280,6 @@ class SmoothClient(BaseClient):
245
280
 
246
281
  Args:
247
282
  task: The task to run.
248
- poll_interval: The time in seconds to wait between polling for status.
249
- timeout: The maximum time in seconds to wait for the task to complete.
250
283
  agent: The agent to use for the task.
251
284
  max_steps: Maximum number of steps the agent can take (max 64).
252
285
  device: Device type for the task. Default is mobile.
@@ -263,11 +296,6 @@ class SmoothClient(BaseClient):
263
296
  Raises:
264
297
  ApiException: If the API request fails.
265
298
  """
266
- if poll_interval < 0.1:
267
- raise ValueError("Poll interval must be at least 100 milliseconds.")
268
- if timeout < 1:
269
- raise ValueError("Timeout must be at least 1 second.")
270
-
271
299
  payload = TaskRequest(
272
300
  task=task,
273
301
  agent=agent,
@@ -281,12 +309,8 @@ class SmoothClient(BaseClient):
281
309
  proxy_password=proxy_password,
282
310
  )
283
311
  initial_response = self._submit_task(payload)
284
- start_time = time.time()
285
- while time.time() - start_time < 16 and initial_response.live_url is None:
286
- initial_response = self._get_task(initial_response.id)
287
- time.sleep(poll_interval)
288
312
 
289
- return TaskHandle(initial_response.id, self, initial_response.live_url, poll_interval, timeout)
313
+ return TaskHandle(initial_response.id, self)
290
314
 
291
315
  def open_session(self, session_id: str | None = None) -> BrowserSessionResponse:
292
316
  """Gets an interactive browser instance.
@@ -344,30 +368,65 @@ class SmoothClient(BaseClient):
344
368
  class AsyncTaskHandle:
345
369
  """An asynchronous handle to a running task."""
346
370
 
347
- def __init__(self, task_id: str, client: "SmoothAsyncClient", live_url: str | None, poll_interval: int, timeout: int | None):
371
+ def __init__(self, task_id: str, client: "SmoothAsyncClient"):
348
372
  """Initializes the asynchronous task handle."""
349
373
  self._client = client
350
- self._poll_interval = poll_interval
351
- self._timeout = timeout
352
374
  self._task_response: TaskResponse | None = None
353
375
 
354
376
  self.id = task_id
355
- self.live_url = live_url
356
377
 
357
- async def result(self) -> TaskResponse:
378
+ async def result(self, timeout: int | None = None, poll_interval: float = 1) -> TaskResponse:
358
379
  """Waits for the task to complete and returns the result."""
359
380
  if self._task_response and self._task_response.status not in ["running", "waiting"]:
360
381
  return self._task_response
361
382
 
383
+ if timeout is not None and timeout < 1:
384
+ raise ValueError("Timeout must be at least 1 second.")
385
+ if poll_interval < 0.1:
386
+ raise ValueError("Poll interval must be at least 100 milliseconds.")
387
+
362
388
  start_time = time.time()
363
- while self._timeout is None or (time.time() - start_time) < self._timeout:
389
+ while timeout is None or (time.time() - start_time) < timeout:
364
390
  task_response = await self._client._get_task(self.id) # pyright: ignore [reportPrivateUsage]
391
+ self._task_response = task_response
365
392
  if task_response.status not in ["running", "waiting"]:
366
- self._task_response = task_response
367
393
  return task_response
368
- await asyncio.sleep(self._poll_interval)
369
- raise TimeoutError(f"Task {self.id} did not complete within {self._timeout} seconds.")
394
+ await asyncio.sleep(poll_interval)
395
+ raise TimeoutError(f"Task {self.id} did not complete within {timeout} seconds.")
396
+
397
+ async def live_url(self, interactive: bool = True, embed: bool = False, timeout: int | None = None):
398
+ """Returns the live URL for the task."""
399
+ params = {
400
+ "interactive": interactive,
401
+ "embed": embed
402
+ }
403
+ if self._task_response and self._task_response.live_url:
404
+ return f"{self._task_response.live_url}?{urlencode(params)}"
405
+
406
+ start_time = time.time()
407
+ while timeout is None or (time.time() - start_time) < timeout:
408
+ task_response = await self._client._get_task(self.id) # pyright: ignore [reportPrivateUsage]
409
+ self._task_response = task_response
410
+ if task_response.live_url is not None:
411
+ return f"{task_response.live_url}?{urlencode(params)}"
412
+ await asyncio.sleep(1)
413
+
414
+ raise TimeoutError(f"Live URL not available for task {self.id}.")
415
+
416
+ async def recording_url(self, timeout: int | None = None):
417
+ """Returns the recording URL for the task."""
418
+ if self._task_response and self._task_response.recording_url is not None:
419
+ return self._task_response.recording_url
420
+
421
+ start_time = time.time()
422
+ while timeout is None or (time.time() - start_time) < timeout:
423
+ task_response = await self._client._get_task(self.id) # pyright: ignore [reportPrivateUsage]
424
+ self._task_response = task_response
425
+ if task_response.recording_url is not None:
426
+ return task_response.recording_url
427
+ await asyncio.sleep(1)
370
428
 
429
+ raise TimeoutError(f"Recording URL not available for task {self.id}.")
371
430
 
372
431
  class SmoothAsyncClient(BaseClient):
373
432
  """An asynchronous client for the API."""
@@ -411,8 +470,6 @@ class SmoothAsyncClient(BaseClient):
411
470
  async def run(
412
471
  self,
413
472
  task: str,
414
- poll_interval: int = 1,
415
- timeout: int = 60 * 15,
416
473
  agent: Literal["smooth"] = "smooth",
417
474
  max_steps: int = 32,
418
475
  device: Literal["desktop", "mobile"] = "mobile",
@@ -430,8 +487,6 @@ class SmoothAsyncClient(BaseClient):
430
487
 
431
488
  Args:
432
489
  task: The task to run.
433
- poll_interval: The time in seconds to wait between polling for status.
434
- timeout: The maximum time in seconds to wait for the task to complete.
435
490
  agent: The agent to use for the task.
436
491
  max_steps: Maximum number of steps the agent can take (max 64).
437
492
  device: Device type for the task. Default is mobile.
@@ -441,6 +496,8 @@ class SmoothAsyncClient(BaseClient):
441
496
  proxy_server: Proxy server url to route browser traffic through.
442
497
  proxy_username: Proxy server username.
443
498
  proxy_password: Proxy server password.
499
+ poll_interval: The time in seconds to wait between polling for status.
500
+ timeout: The maximum time in seconds to wait for the task to complete.
444
501
 
445
502
  Returns:
446
503
  A handle to the running task.
@@ -448,11 +505,6 @@ class SmoothAsyncClient(BaseClient):
448
505
  Raises:
449
506
  ApiException: If the API request fails.
450
507
  """
451
- if poll_interval < 0.1:
452
- raise ValueError("Poll interval must be at least 100 milliseconds.")
453
- if timeout < 1:
454
- raise ValueError("Timeout must be at least 1 second.")
455
-
456
508
  payload = TaskRequest(
457
509
  task=task,
458
510
  agent=agent,
@@ -467,11 +519,7 @@ class SmoothAsyncClient(BaseClient):
467
519
  )
468
520
 
469
521
  initial_response = await self._submit_task(payload)
470
- start_time = time.time()
471
- while time.time() - start_time < 16 and initial_response.live_url is None:
472
- initial_response = await self._get_task(initial_response.id)
473
- await asyncio.sleep(poll_interval)
474
- return AsyncTaskHandle(initial_response.id, self, initial_response.live_url, poll_interval, timeout)
522
+ return AsyncTaskHandle(initial_response.id, self)
475
523
 
476
524
  async def open_session(self, session_id: str | None = None) -> BrowserSessionResponse:
477
525
  """Opens an interactive browser instance asynchronously.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: smooth-py
3
- Version: 0.1.2.post0
3
+ Version: 0.1.3.post0
4
4
  Summary:
5
5
  Author: Luca Pinchetti
6
6
  Author-email: luca@circlemind.co
@@ -0,0 +1,4 @@
1
+ smooth/__init__.py,sha256=jIEM1usuwrZWq-m1YgsUsLCkvUkhS9BNqgFc3HUPEvw,21263
2
+ smooth_py-0.1.3.post0.dist-info/METADATA,sha256=x4u365t5CvrRvMPsrCrLy2sVNT4ojf8nK8pdntodEdw,5394
3
+ smooth_py-0.1.3.post0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
4
+ smooth_py-0.1.3.post0.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- smooth/__init__.py,sha256=65IQjCGmh5xRFfOk-48V_NNgItnh7uUCSva_PYGHd5I,19326
2
- smooth_py-0.1.2.post0.dist-info/METADATA,sha256=WveUBUPZIOGiNvYT1fEB47GOk4SuGRkxIhv_4oqYQpA,5394
3
- smooth_py-0.1.2.post0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
4
- smooth_py-0.1.2.post0.dist-info/RECORD,,