Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/urlscan/pro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from urlscan.client import BaseClient
from urlscan.iterator import SearchIterator
from urlscan.pro.visibility import Visibility
from urlscan.utils import _compact

from .brand import Brand
Expand Down Expand Up @@ -153,6 +154,25 @@ def subscription(self) -> Subscription:
retry=self._retry,
)

@cached_property
def visibility(self) -> Visibility:
"""Visibility API client instance.

Returns:
Visibility: Visibility API client instance.

"""
return Visibility(
api_key=self._api_key,
base_url=self._base_url,
user_agent=self._user_agent,
trust_env=self._trust_env,
timeout=self._timeout,
proxy=self._proxy,
verify=self._verify,
retry=self._retry,
)

def structure_search(
self,
scan_id: str,
Expand Down
43 changes: 43 additions & 0 deletions src/urlscan/pro/visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Visibility API client module."""

from urlscan.client import BaseClient
from urlscan.types import UpdateVisibilityType


class Visibility(BaseClient):
"""Visibility API client."""

def update(self, uuid: str, visibility: UpdateVisibilityType) -> dict:
"""Update visibility of a scan owned by you or your team.

Args:
uuid (str): The UUID of a scan.
visibility (UpdateVisibilityType): The new visibility of the scan result: public, unlisted, private, deleted

Reference:
https://docs.urlscan.io/apis/urlscan-openapi/scanning/updateresultvisibility

"""
if visibility not in ["public", "private", "unlisted", "deleted"]:
raise ValueError(
"Visibility must be either 'public', 'private', 'unlisted', or 'deleted'"
)

res = self._put(
f"/api/v1/result/{uuid}/visibility/",
json={"visibility": visibility},
)
return self._response_to_json(res)

def reset(self, uuid: str) -> dict:
"""Reset the visibility of a scan owned by you or your team to its original visibility.

Args:
uuid (str): The UUID of a scan.

Reference:
https://docs.urlscan.io/apis/urlscan-openapi/scanning/deleteresultvisibility

"""
res = self._delete(f"/api/v1/result/{uuid}/visibility/")
return self._response_to_json(res)
1 change: 1 addition & 0 deletions src/urlscan/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scans", "hostnames", "incidents", "notifications", "certificates"
]
VisibilityType = Literal["public", "private", "unlisted"]
UpdateVisibilityType = VisibilityType | Literal["deleted"]
SearchType = Literal["search"]
RetrieveType = Literal["retrieve"]
ActionType = VisibilityType | SearchType | RetrieveType
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/pro/test_visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest

from urlscan import Client, Pro


@pytest.mark.integration
def test_change_and_reset_visibility(client: Client, pro: Pro, url: str):
result = client.scan(url, visibility="private")
uuid: str = result["uuid"]
client.wait_for_result(uuid)

# Change visibility to public
res = pro.visibility.update(uuid, "public")
assert res["uuid"] == uuid
assert res["visibility"] == "public"

# Reset visibility to original (private)
res = pro.visibility.reset(uuid)
assert res["uuid"] == uuid
38 changes: 38 additions & 0 deletions tests/unit/pro/test_visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Any

import pytest
from pytest_httpserver import HTTPServer

from urlscan.pro import Pro


def test_update(pro: Pro, httpserver: HTTPServer):
expected: dict[str, Any] = {
"uuid": "dummy",
"message": "Visibility updated",
"visibility": "public",
}
httpserver.expect_request(
"/api/v1/result/dummy/visibility/", method="PUT", json={"visibility": "public"}
).respond_with_json(expected)

got = pro.visibility.update("dummy", "public")
assert got == expected


def test_update_with_invalid_visibility(pro: Pro):
with pytest.raises(ValueError):
pro.visibility.update("dummy", "invalid") # type: ignore


def test_reset(pro: Pro, httpserver: HTTPServer):
expected: dict[str, Any] = {
"uuid": "dummy",
"message": "Visibility reset to public",
}
httpserver.expect_request(
"/api/v1/result/dummy/visibility/", method="DELETE"
).respond_with_json(expected)

got = pro.visibility.reset("dummy")
assert got == expected