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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Zscaler Python SDK Changelog


## 1.9.40 (August 4 2026)

### Notes

- Python Versions: **v3.9, v3.10, v3.11, v3.12**

### Bug Fixes

* [PR #557](https://github.com/zscaler/zscaler-sdk-python/issues/557) - Added new ZIA URL Category function `list_categories_lite` to return lightweight key-value list of all or custom URL categories.
* [PR #557](https://github.com/zscaler/zscaler-sdk-python/issues/557) - Fixed ZCELL Model `sim_location_groups` to properly parse the `GeoFence` block attribute.

## 1.9.39 (July 27, 2026)

### Notes
Expand Down
14 changes: 14 additions & 0 deletions docsrc/zs/guides/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ Release Notes
Zscaler Python SDK Changelog
----------------------------

1.9.40 (August 4 2026)
---------------------------

Notes
-----

- Python Versions: **v3.9, v3.10, v3.11, v3.12**

Bug Fixes
---------

(`#557 <https://github.com/zscaler/zscaler-sdk-python/pull/557>`_) - Added new ZIA URL Category function `list_categories_lite` to return lightweight key-value list of all or custom URL categories.
(`#557 <https://github.com/zscaler/zscaler-sdk-python/pull/557>`_) - Fixed ZCELL Model `sim_location_groups` to properly parse the `GeoFence` block attribute.

1.9.39 (July 27, 2026)
---------------------------

Expand Down
682 changes: 341 additions & 341 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "zscaler-sdk-python"
version = "1.9.39"
version = "1.9.40"
description = "Official Python SDK for the Zscaler Products"
authors = ["Zscaler, Inc. <devrel@zscaler.com>"]
license = "MIT"
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
aenum==3.1.17 ; python_version >= "3.10" and python_version < "4.0"
arrow==1.4.0 ; python_version >= "3.10" and python_version < "4.0"
certifi==2026.7.22 ; python_version >= "3.10" and python_version < "4.0"
cffi==2.1.0 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy"
cffi==2.1.1 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy"
charset-normalizer==3.4.9 ; python_version >= "3.10" and python_version < "4.0"
cryptography==49.0.0 ; python_version >= "3.10" and python_version < "4.0"
cryptography==50.0.0 ; python_version >= "3.10" and python_version < "4.0"
idna==3.18 ; python_version >= "3.10" and python_version < "4.0"
jmespath==1.1.0 ; python_version >= "3.10" and python_version < "4.0"
jwcrypto==1.5.8 ; python_version >= "3.10" and python_version < "4.0"
Expand Down
2 changes: 1 addition & 1 deletion zscaler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
__contributors__ = [
"William Guilherme",
]
__version__ = "1.9.39"
__version__ = "1.9.40"


from zscaler.oneapi_client import Client as ZscalerClient # noqa
94 changes: 91 additions & 3 deletions zscaler/zcell/models/sim_location_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,112 @@ def request_format(self):
return parent_req_format


class GeoFence(ZscalerObject):
"""
A class representing the geo fence of a SIM location group.

The API spells this ``geoFenceData`` on read/update and ``geoFenceDetails`` on
create; both carry the same shape. ``lat``/``lng``/``radius`` are fractional --
e.g. ``{"lat": -17.687827, "lng": 52.8125, "radius": 1637864.965089}``.
"""

def __init__(self, config=None):
super().__init__(config)
if config:
self.lat = config["lat"] if "lat" in config else None
self.lng = config["lng"] if "lng" in config else None
self.radius = config["radius"] if "radius" in config else None
else:
self.lat = None
self.lng = None
self.radius = None

def request_format(self):
"""
Return the object as a dictionary in the format expected for API requests.
"""
parent_req_format = super().request_format()
current_obj_format = {
"lat": self.lat,
"lng": self.lng,
"radius": self.radius,
}
parent_req_format.update(current_obj_format)
return parent_req_format


class LinkedPolicyDetails(ZscalerObject):
"""
A class representing a policy linked to a SIM location group.
"""

def __init__(self, config=None):
super().__init__(config)
if config:
self.policy_id = config["policyId"] if "policyId" in config else None
self.policy_name = config["policyName"] if "policyName" in config else None
self.policy_type = config["policyType"] if "policyType" in config else None
self.status = config["status"] if "status" in config else None
else:
self.policy_id = None
self.policy_name = None
self.policy_type = None
self.status = None

def request_format(self):
"""
Return the object as a dictionary in the format expected for API requests.
"""
parent_req_format = super().request_format()
current_obj_format = {
"policyId": self.policy_id,
"policyName": self.policy_name,
"policyType": self.policy_type,
"status": self.status,
}
parent_req_format.update(current_obj_format)
return parent_req_format


class ApiCreateSimLocationGroupRequestBody(ZscalerObject):
"""
A class representing a ApiCreateSimLocationGroupRequestBody object.

Note the create payload uses ``geoFenceDetails``, where read and update use
``geoFenceData``. The endpoint takes a *list* of these bodies per call.
"""

def __init__(self, config=None):
super().__init__(config)
if config:
pass
self.name = config["name"] if "name" in config else None
self.tracked_devices = ZscalerCollection.form_list(
config["trackedDevices"] if "trackedDevices" in config else [], str
)
if "geoFenceDetails" in config:
if isinstance(config["geoFenceDetails"], sim_location_groups.GeoFence):
self.geo_fence_details = config["geoFenceDetails"]
elif config["geoFenceDetails"] is not None:
self.geo_fence_details = sim_location_groups.GeoFence(config["geoFenceDetails"])
else:
self.geo_fence_details = None
else:
self.geo_fence_details = None
else:
pass
self.name = None
self.tracked_devices = []
self.geo_fence_details = None

def request_format(self):
"""
Return the object as a dictionary in the format expected for API requests.
"""
parent_req_format = super().request_format()
current_obj_format = {}
current_obj_format = {
"name": self.name,
"trackedDevices": self.tracked_devices,
"geoFenceDetails": self.geo_fence_details,
}
parent_req_format.update(current_obj_format)
return parent_req_format

Expand Down
2 changes: 1 addition & 1 deletion zscaler/zcell/sim_location_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def get_sim_location_group(self, id: str = None, group_id: str = None) -> APIRes
tuple: (result, Response, error)

Examples:
>>> result, response, error = client.zcell.sim_location_groups.get_sim_location_groups(
>>> result, response, error = client.zcell.sim_location_groups.get_sim_location_group(
... id='...',
... group_id='...',
... )
Expand Down
57 changes: 57 additions & 0 deletions zscaler/zia/url_categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,63 @@ def list_categories(

return (results, response, None)

def list_categories_lite(self, query_params: Optional[dict] = None) -> APIResult[List[URLCategory]]:
"""
Returns lightweight key-value list of all or custom URL categories.

Keyword Args:

Returns:
:obj:`Tuple`: A list of configured categories.

Examples:
List locations with default settings:

>>> categories_list, _, err = client.zia.url_categories.list_categories_lite()
>>> if err:
... print(f"Error listing url categories: {err}")
... return
... print(f"Total url categories found: {len(categories_list)}")
... for category in categories_list:
... print(category.as_dict())

Client-side filtering with JMESPath:

The response object supports client-side filtering and
projection via ``resp.search(expression)``. See the
`JMESPath documentation <https://jmespath.org/>`_ for
expression syntax.

"""
http_method = "get".upper()
api_url = format_url(f"""
{self._zia_base_endpoint}
/urlCategories/lite
""")

query_params = query_params or {}

body = {}
headers = {}

request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params)

if error:
return (None, None, error)

response, error = self._request_executor.execute(request)

if error:
return (None, response, error)

try:
result = []
for item in response.get_results():
result.append(URLCategory(self.form_response_body(item)))
except Exception as error:
return (None, response, error)
return (result, response, None)

def get_category(self, category_id: str) -> APIResult[URLCategory]:
"""
Returns URL category information for the provided category.
Expand Down
Loading