Skip to content

Commit aebf442

Browse files
Merge pull request #54 from macserv/feature/add-pro-packages
Add Packages to Pro API, and a few Typing Enhancements
2 parents 8705919 + 33e0945 commit aebf442

File tree

16 files changed

+406
-112
lines changed

16 files changed

+406
-112
lines changed

docs/contributors/index.rst

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ Any Jamf Pro API added to the clients must have the following elements code comp
9696

9797
* The API method has been added to the appropriate client, has a complete docstring, and has an interface in-line with other methods of that client.
9898
* The API must have matching and complete Pydantic models.
99+
* Provide ``@overload`` interfaces for API methods with dynamic return types (e.g. ``Union[list[Computer], Iterator[Page]]``) which be determined from the value of an argument.
100+
* If the value of a variable, method argument, or return parameter can be ``None``, inform the type checker by wrapping its type with ``Optional[]``, e.g. ``description: Optional[str] = None``
99101
* Unless your code is covered by another automated test you will need to add tests to ensure coverage.
100102

101103
The SDK references in the documentation automatically include all public method on the clients and no documentation changes may be required as a part of the contribution.

docs/reference/models_pro.rst

+10
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ Computers
4545
ComputerContentCaching
4646
ComputerGroupMembership
4747

48+
Packages
49+
--------
50+
51+
.. currentmodule:: jamf_pro_sdk.models.pro.packages
52+
53+
.. autosummary::
54+
:toctree: _autosummary
55+
56+
Package
57+
4858
JCDS2
4959
-----
5060

pyproject.toml

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ package-dir = {"" = "src"}
7272
where = ["src"]
7373

7474

75+
[tool.setuptools.package-data]
76+
"jamf_pro_sdk" = ["py.typed"]
77+
78+
7579
[tool.setuptools.dynamic]
7680
version = {attr = "jamf_pro_sdk.__about__.__version__"}
7781
readme = {file = ["README.md"], content-type = "text/markdown"}

src/jamf_pro_sdk/__init__.py

+12
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,15 @@
88
)
99
from .helpers import logger_quick_setup
1010
from .models.client import SessionConfig
11+
12+
__all__ = [
13+
"__title__",
14+
"__version__",
15+
"JamfProClient",
16+
"BasicAuthProvider",
17+
"LoadFromAwsSecretsManager",
18+
"LoadFromKeychain",
19+
"PromptForCredentials",
20+
"logger_quick_setup",
21+
"SessionConfig",
22+
]

src/jamf_pro_sdk/clients/__init__.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import tempfile
44
from pathlib import Path
5-
from typing import Any, Callable, Dict, Iterable, Iterator, Optional, Type, Union
5+
from typing import Any, BinaryIO, Callable, Dict, Iterable, Iterator, Optional, Type, Union
66
from urllib.parse import urlunparse
77

88
import certifi
@@ -27,7 +27,7 @@ def __init__(
2727
server: str,
2828
credentials: CredentialsProvider,
2929
port: int = 443,
30-
session_config: SessionConfig = None,
30+
session_config: Optional[SessionConfig] = None,
3131
):
3232
"""The base client class for interacting with the Jamf Pro APIs.
3333
@@ -138,7 +138,7 @@ def classic_api_request(
138138
method: str,
139139
resource_path: str,
140140
data: Optional[Union[str, ClassicApiModel]] = None,
141-
override_headers: dict = None,
141+
override_headers: Optional[dict] = None,
142142
) -> requests.Response:
143143
"""Perform a request to the Classic API.
144144
@@ -195,7 +195,8 @@ def pro_api_request(
195195
resource_path: str,
196196
query_params: Optional[Dict[str, str]] = None,
197197
data: Optional[Union[dict, BaseModel]] = None,
198-
override_headers: Dict[str, str] = None,
198+
files: Optional[dict[str, tuple[str, BinaryIO, str]]] = None,
199+
override_headers: Optional[Dict[str, str]] = None,
199200
) -> requests.Response:
200201
"""Perform a request to the Pro API.
201202
@@ -214,6 +215,10 @@ def pro_api_request(
214215
or ``BaseModel`` that is being sent.
215216
:type data: dict | BaseModel
216217
218+
:param files: If the request is a ``POST``, a dictionary with a single ``files`` key,
219+
and a tuple containing the filename, file-like object to upload, and mime type.
220+
:type files: Optional[dict[str, tuple[str, BinaryIO, str]]]
221+
217222
:param override_headers: A dictionary of key-value pairs that will be set as
218223
headers for the request. You cannot override the ``Authorization`` or
219224
``Content-Type`` headers.
@@ -246,6 +251,9 @@ def pro_api_request(
246251
else:
247252
raise ValueError("'data' must be one of 'dict' or 'BaseModel'")
248253

254+
if files and (method.lower() == "post"):
255+
pro_req["files"] = files
256+
249257
with self.session.request(**pro_req) as pro_resp:
250258
logger.info("ProAPIRequest %s %s", method.upper(), resource_path)
251259
try:
@@ -261,7 +269,7 @@ def concurrent_api_requests(
261269
handler: Callable,
262270
arguments: Iterable[Any],
263271
return_model: Optional[Type[BaseModel]] = None,
264-
max_concurrency: int = None,
272+
max_concurrency: Optional[int] = None,
265273
return_exceptions: Optional[bool] = None,
266274
) -> Iterator[Union[Any, Exception]]:
267275
"""An interface for performing concurrent API operations.

src/jamf_pro_sdk/clients/pro_api/__init__.py

+190-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Callable, Iterator, List, Union
3+
from typing import TYPE_CHECKING, Callable, Iterator, List, Literal, Optional, Union, overload
44
from uuid import UUID
55

6-
from ...models.pro.api_options import * # noqa: F403
6+
from ...models.pro.api_options import (
7+
get_computer_inventory_v1_allowed_filter_fields,
8+
get_computer_inventory_v1_allowed_sections,
9+
get_computer_inventory_v1_allowed_sort_fields,
10+
get_mdm_commands_v2_allowed_command_types,
11+
get_mdm_commands_v2_allowed_filter_fields,
12+
get_mdm_commands_v2_allowed_sort_fields,
13+
get_mobile_device_inventory_v2_allowed_filter_fields,
14+
get_mobile_device_inventory_v2_allowed_sections,
15+
get_mobile_device_inventory_v2_allowed_sort_fields,
16+
get_packages_v1_allowed_filter_fields,
17+
get_packages_v1_allowed_sort_fields,
18+
)
719
from ...models.pro.computers import Computer
820
from ...models.pro.jcds2 import DownloadUrl, File, NewFile
921
from ...models.pro.mdm import (
@@ -21,6 +33,7 @@
2133
ShutDownDeviceCommand,
2234
)
2335
from ...models.pro.mobile_devices import MobileDevice
36+
from ...models.pro.packages import Package
2437
from .pagination import Paginator
2538

2639
if TYPE_CHECKING:
@@ -42,14 +55,38 @@ def __init__(
4255

4356
# Computer Inventory APIs
4457

58+
@overload
59+
def get_computer_inventory_v1(
60+
self,
61+
sections: Optional[List[str]] = ...,
62+
start_page: int = ...,
63+
end_page: Optional[int] = ...,
64+
page_size: int = ...,
65+
sort_expression: Optional[SortExpression] = ...,
66+
filter_expression: Optional[FilterExpression] = ...,
67+
return_generator: Literal[False] = False,
68+
) -> List[Computer]: ...
69+
70+
@overload
71+
def get_computer_inventory_v1(
72+
self,
73+
sections: Optional[List[str]] = ...,
74+
start_page: int = ...,
75+
end_page: Optional[int] = ...,
76+
page_size: int = ...,
77+
sort_expression: Optional[SortExpression] = ...,
78+
filter_expression: Optional[FilterExpression] = ...,
79+
return_generator: Literal[True] = True,
80+
) -> Iterator[Page]: ...
81+
4582
def get_computer_inventory_v1(
4683
self,
47-
sections: List[str] = None,
84+
sections: Optional[List[str]] = None,
4885
start_page: int = 0,
49-
end_page: int = None,
86+
end_page: Optional[int] = None,
5087
page_size: int = 100,
51-
sort_expression: SortExpression = None,
52-
filter_expression: FilterExpression = None,
88+
sort_expression: Optional[SortExpression] = None,
89+
filter_expression: Optional[FilterExpression] = None,
5390
return_generator: bool = False,
5491
) -> Union[List[Computer], Iterator[Page]]:
5592
"""Returns a list of computer inventory records.
@@ -132,6 +169,98 @@ def get_computer_inventory_v1(
132169

133170
return paginator(return_generator=return_generator)
134171

172+
# Package APIs
173+
174+
@overload
175+
def get_packages_v1(
176+
self,
177+
start_page: int = ...,
178+
end_page: Optional[int] = ...,
179+
page_size: int = ...,
180+
sort_expression: Optional[SortExpression] = ...,
181+
filter_expression: Optional[FilterExpression] = ...,
182+
return_generator: Literal[False] = False,
183+
) -> List[Package]: ...
184+
185+
@overload
186+
def get_packages_v1(
187+
self,
188+
start_page: int = ...,
189+
end_page: Optional[int] = ...,
190+
page_size: int = ...,
191+
sort_expression: Optional[SortExpression] = ...,
192+
filter_expression: Optional[FilterExpression] = ...,
193+
return_generator: Literal[True] = True,
194+
) -> Iterator[Page]: ...
195+
196+
def get_packages_v1(
197+
self,
198+
start_page: int = 0,
199+
end_page: Optional[int] = None,
200+
page_size: int = 100,
201+
sort_expression: Optional[SortExpression] = None,
202+
filter_expression: Optional[FilterExpression] = None,
203+
return_generator: bool = False,
204+
) -> Union[List[Package], Iterator[Page]]:
205+
"""Returns a list of package records.
206+
207+
:param start_page: (optional) The page to begin returning results from. See
208+
:class:`Paginator` for more information.
209+
:type start_page: int
210+
211+
:param end_page: (optional) The page to end returning results at. See :class:`Paginator` for
212+
more information.
213+
:type start_page: int
214+
215+
:param page_size: (optional) The number of results to include in each requested page. See
216+
:class:`Paginator` for more information.
217+
:type page_size: int
218+
219+
:param sort_expression: (optional) The sort fields to apply to the request. See the
220+
documentation for :ref:`Pro API Sorting` for more information.
221+
222+
Allowed sort fields:
223+
224+
.. autoapioptions:: jamf_pro_sdk.models.pro.api_options.get_packages_v1_allowed_sort_fields
225+
226+
:type sort_expression: SortExpression
227+
228+
:param filter_expression: (optional) The filter expression to apply to the request. See the
229+
documentation for :ref:`Pro API Filtering` for more information.
230+
231+
Allowed filter fields:
232+
233+
.. autoapioptions:: jamf_pro_sdk.models.pro.api_options.get_packages_v1_allowed_filter_fields
234+
235+
:type filter_expression: FilterExpression
236+
237+
:param return_generator: If ``True`` a generator is returned to iterate over pages. By
238+
default, the results for all pages will be returned in a single response.
239+
:type return_generator: bool
240+
241+
:return: List of packages OR a paginator generator.
242+
:rtype: List[~jamf_pro_sdk.models.pro.packages.package] | Iterator[Page]
243+
244+
"""
245+
if sort_expression:
246+
sort_expression.validate(get_packages_v1_allowed_sort_fields)
247+
248+
if filter_expression:
249+
filter_expression.validate(get_packages_v1_allowed_filter_fields)
250+
251+
paginator = Paginator(
252+
api_client=self,
253+
resource_path="v1/packages",
254+
return_model=Package,
255+
start_page=start_page,
256+
end_page=end_page,
257+
page_size=page_size,
258+
sort_expression=sort_expression,
259+
filter_expression=filter_expression,
260+
)
261+
262+
return paginator(return_generator=return_generator)
263+
135264
# JCDS APIs
136265

137266
def get_jcds_files_v1(self) -> List[File]:
@@ -256,13 +385,35 @@ def send_mdm_command_preview(
256385
resp = self.api_request(method="post", resource_path="preview/mdm/commands", data=data)
257386
return [SendMdmCommandResponse(**i) for i in resp.json()]
258387

388+
@overload
389+
def get_mdm_commands_v2(
390+
self,
391+
filter_expression: FilterExpression,
392+
start_page: int = ...,
393+
end_page: Optional[int] = ...,
394+
page_size: int = ...,
395+
sort_expression: Optional[SortExpression] = ...,
396+
return_generator: Literal[False] = False,
397+
) -> List[MdmCommandStatus]: ...
398+
399+
@overload
400+
def get_mdm_commands_v2(
401+
self,
402+
filter_expression: FilterExpression,
403+
start_page: int = ...,
404+
end_page: Optional[int] = ...,
405+
page_size: int = ...,
406+
sort_expression: Optional[SortExpression] = ...,
407+
return_generator: Literal[True] = True,
408+
) -> Iterator[Page]: ...
409+
259410
def get_mdm_commands_v2(
260411
self,
261412
filter_expression: FilterExpression,
262413
start_page: int = 0,
263-
end_page: int = None,
414+
end_page: Optional[int] = None,
264415
page_size: int = 100,
265-
sort_expression: SortExpression = None,
416+
sort_expression: Optional[SortExpression] = None,
266417
return_generator: bool = False,
267418
) -> Union[List[MdmCommandStatus], Iterator[Page]]:
268419
"""Returns a list of MDM commands.
@@ -302,7 +453,7 @@ def get_mdm_commands_v2(
302453
the results for all pages will be returned in a single response.
303454
:type return_generator: bool
304455
305-
:return: List of computers OR a paginator generator.
456+
:return: List of MDM commands OR a paginator generator.
306457
:rtype: List[~jamf_pro_sdk.models.pro.mdm.MdmCommand] | Iterator[Page]
307458
"""
308459

@@ -333,14 +484,40 @@ def get_mdm_commands_v2(
333484

334485
return paginator(return_generator=return_generator)
335486

487+
# Mobile Device Inventory APIs
488+
489+
@overload
490+
def get_mobile_device_inventory_v2(
491+
self,
492+
sections: Optional[List[str]] = ...,
493+
start_page: int = ...,
494+
end_page: Optional[int] = ...,
495+
page_size: int = ...,
496+
sort_expression: Optional[SortExpression] = ...,
497+
filter_expression: Optional[FilterExpression] = ...,
498+
return_generator: Literal[False] = False,
499+
) -> List[MobileDevice]: ...
500+
501+
@overload
502+
def get_mobile_device_inventory_v2(
503+
self,
504+
sections: Optional[List[str]] = ...,
505+
start_page: int = ...,
506+
end_page: Optional[int] = ...,
507+
page_size: int = ...,
508+
sort_expression: Optional[SortExpression] = ...,
509+
filter_expression: Optional[FilterExpression] = ...,
510+
return_generator: Literal[True] = True,
511+
) -> Iterator[Page]: ...
512+
336513
def get_mobile_device_inventory_v2(
337514
self,
338-
sections: List[str] = None,
515+
sections: Optional[List[str]] = None,
339516
start_page: int = 0,
340-
end_page: int = None,
517+
end_page: Optional[int] = None,
341518
page_size: int = 100,
342-
sort_expression: SortExpression = None,
343-
filter_expression: FilterExpression = None,
519+
sort_expression: Optional[SortExpression] = None,
520+
filter_expression: Optional[FilterExpression] = None,
344521
return_generator: bool = False,
345522
) -> Union[List[MobileDevice], Iterator[Page]]:
346523
"""Returns a list of mobile device (iOS and tvOS) inventory records.

0 commit comments

Comments
 (0)