Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Packages to Pro API, and a few Typing Enhancements #54

Merged
merged 14 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
10 changes: 10 additions & 0 deletions docs/reference/models_pro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ Computers
ComputerContentCaching
ComputerGroupMembership

Packages
--------

.. currentmodule:: jamf_pro_sdk.models.pro.packages

.. autosummary::
:toctree: _autosummary

Package

JCDS2
-----

Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ package-dir = {"" = "src"}
where = ["src"]


[tool.setuptools.package-data]
"jamf_pro_sdk" = ["py.typed"]


[tool.setuptools.dynamic]
version = {attr = "jamf_pro_sdk.__about__.__version__"}
readme = {file = ["README.md"], content-type = "text/markdown"}
Expand Down
12 changes: 12 additions & 0 deletions src/jamf_pro_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,15 @@
)
from .helpers import logger_quick_setup
from .models.client import SessionConfig

__all__ = [
"__title__",
"__version__",
"JamfProClient",
"BasicAuthProvider",
"LoadFromAwsSecretsManager",
"LoadFromKeychain",
"PromptForCredentials",
"logger_quick_setup",
"SessionConfig",
]
18 changes: 13 additions & 5 deletions src/jamf_pro_sdk/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import tempfile
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, Iterator, Optional, Type, Union
from typing import Any, BinaryIO, Callable, Dict, Iterable, Iterator, Optional, Type, Union
from urllib.parse import urlunparse

import certifi
Expand All @@ -27,7 +27,7 @@ def __init__(
server: str,
credentials: CredentialsProvider,
port: int = 443,
session_config: SessionConfig = None,
session_config: Optional[SessionConfig] = None,
):
"""The base client class for interacting with the Jamf Pro APIs.

Expand Down Expand Up @@ -138,7 +138,7 @@ def classic_api_request(
method: str,
resource_path: str,
data: Optional[Union[str, ClassicApiModel]] = None,
override_headers: dict = None,
override_headers: Optional[dict] = None,
) -> requests.Response:
"""Perform a request to the Classic API.

Expand Down Expand Up @@ -195,7 +195,8 @@ def pro_api_request(
resource_path: str,
query_params: Optional[Dict[str, str]] = None,
data: Optional[Union[dict, BaseModel]] = None,
override_headers: Dict[str, str] = None,
files: Optional[dict[str, tuple[str, BinaryIO, str]]] = None,
override_headers: Optional[Dict[str, str]] = None,
) -> requests.Response:
"""Perform a request to the Pro API.

Expand All @@ -214,6 +215,10 @@ def pro_api_request(
or ``BaseModel`` that is being sent.
:type data: dict | BaseModel

:param files: If the request is a ``POST``, a dictionary with a single ``files`` key,
and a tuple containing the filename, file-like object to upload, and mime type.
:type files: Optional[dict[str, tuple[str, BinaryIO, str]]]

macserv marked this conversation as resolved.
Show resolved Hide resolved
:param override_headers: A dictionary of key-value pairs that will be set as
headers for the request. You cannot override the ``Authorization`` or
``Content-Type`` headers.
Expand Down Expand Up @@ -246,6 +251,9 @@ def pro_api_request(
else:
raise ValueError("'data' must be one of 'dict' or 'BaseModel'")

if files and (method.lower() == "post"):
pro_req["files"] = files

with self.session.request(**pro_req) as pro_resp:
logger.info("ProAPIRequest %s %s", method.upper(), resource_path)
try:
Expand All @@ -261,7 +269,7 @@ def concurrent_api_requests(
handler: Callable,
arguments: Iterable[Any],
return_model: Optional[Type[BaseModel]] = None,
max_concurrency: int = None,
max_concurrency: Optional[int] = None,
return_exceptions: Optional[bool] = None,
) -> Iterator[Union[Any, Exception]]:
"""An interface for performing concurrent API operations.
Expand Down
151 changes: 139 additions & 12 deletions src/jamf_pro_sdk/clients/pro_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Callable, Iterator, List, Union
from typing import TYPE_CHECKING, Callable, Iterator, List, Literal, Optional, Union, overload
from uuid import UUID

from ...models.pro.api_options import * # noqa: F403
from ...models.pro.api_options import (
get_computer_inventory_v1_allowed_filter_fields,
get_computer_inventory_v1_allowed_sections,
get_computer_inventory_v1_allowed_sort_fields,
get_mdm_commands_v2_allowed_command_types,
get_mdm_commands_v2_allowed_filter_fields,
get_mdm_commands_v2_allowed_sort_fields,
get_mobile_device_inventory_v2_allowed_filter_fields,
get_mobile_device_inventory_v2_allowed_sections,
get_mobile_device_inventory_v2_allowed_sort_fields,
get_packages_v1_allowed_filter_fields,
get_packages_v1_allowed_sort_fields,
)
from ...models.pro.computers import Computer
from ...models.pro.jcds2 import DownloadUrl, File, NewFile
from ...models.pro.mdm import (
Expand All @@ -21,6 +33,7 @@
ShutDownDeviceCommand,
)
from ...models.pro.mobile_devices import MobileDevice
from ...models.pro.packages import Package
from .pagination import Paginator

if TYPE_CHECKING:
Expand All @@ -42,14 +55,38 @@ def __init__(

# Computer Inventory APIs

@overload
def get_computer_inventory_v1(
self,
sections: Optional[List[str]] = ...,
start_page: int = ...,
end_page: Optional[int] = ...,
page_size: int = ...,
sort_expression: Optional[SortExpression] = ...,
filter_expression: Optional[FilterExpression] = ...,
return_generator: Literal[False] = False,
) -> List[Computer]: ...

@overload
def get_computer_inventory_v1(
self,
sections: List[str] = None,
sections: Optional[List[str]] = ...,
start_page: int = ...,
end_page: Optional[int] = ...,
page_size: int = ...,
sort_expression: Optional[SortExpression] = ...,
filter_expression: Optional[FilterExpression] = ...,
return_generator: Literal[True] = True,
) -> Iterator[Page]: ...

def get_computer_inventory_v1(
self,
sections: Optional[List[str]] = None,
start_page: int = 0,
end_page: int = None,
end_page: Optional[int] = None,
page_size: int = 100,
sort_expression: SortExpression = None,
filter_expression: FilterExpression = None,
sort_expression: Optional[SortExpression] = None,
filter_expression: Optional[FilterExpression] = None,
return_generator: bool = False,
) -> Union[List[Computer], Iterator[Page]]:
"""Returns a list of computer inventory records.
Expand Down Expand Up @@ -132,6 +169,96 @@ def get_computer_inventory_v1(

return paginator(return_generator=return_generator)

@overload
def get_packages_v1(
self,
start_page: int = ...,
end_page: Optional[int] = ...,
page_size: int = ...,
sort_expression: Optional[SortExpression] = ...,
filter_expression: Optional[FilterExpression] = ...,
return_generator: Literal[False] = False,
) -> List[Package]: ...

@overload
def get_packages_v1(
self,
start_page: int = ...,
end_page: Optional[int] = ...,
page_size: int = ...,
sort_expression: Optional[SortExpression] = ...,
filter_expression: Optional[FilterExpression] = ...,
return_generator: Literal[True] = True,
) -> Iterator[Page]: ...

def get_packages_v1(
self,
start_page: int = 0,
end_page: Optional[int] = None,
page_size: int = 100,
sort_expression: Optional[SortExpression] = None,
filter_expression: Optional[FilterExpression] = None,
return_generator: bool = False,
) -> Union[List[Package], Iterator[Page]]:
"""Returns a list of package records.

:param start_page: (optional) The page to begin returning results from. See
:class:`Paginator` for more information.
:type start_page: int

:param end_page: (optional) The page to end returning results at. See :class:`Paginator` for
more information.
:type start_page: int

:param page_size: (optional) The number of results to include in each requested page. See
:class:`Paginator` for more information.
:type page_size: int

:param sort_expression: (optional) The sort fields to apply to the request. See the
documentation for :ref:`Pro API Sorting` for more information.

Allowed sort fields:

.. autoapioptions:: jamf_pro_sdk.models.pro.api_options.get_computer_inventory_v1_allowed_sort_fields

:type sort_expression: SortExpression

:param filter_expression: (optional) The filter expression to apply to the request. See the
documentation for :ref:`Pro API Filtering` for more information.

Allowed filter fields:

.. autoapioptions:: jamf_pro_sdk.models.pro.api_options.get_computer_inventory_v1_allowed_filter_fields
macserv marked this conversation as resolved.
Show resolved Hide resolved

:type filter_expression: FilterExpression

:param return_generator: If ``True`` a generator is returned to iterate over pages. By
default, the results for all pages will be returned in a single response.
:type return_generator: bool

:return: List of computers OR a paginator generator.
:rtype: List[~jamf_pro_sdk.models.pro.computer.Computer] | Iterator[Page]
macserv marked this conversation as resolved.
Show resolved Hide resolved

"""
if sort_expression:
sort_expression.validate(get_packages_v1_allowed_sort_fields)

if filter_expression:
filter_expression.validate(get_packages_v1_allowed_filter_fields)

paginator = Paginator(
api_client=self,
resource_path="v1/packages",
return_model=Package,
start_page=start_page,
end_page=end_page,
page_size=page_size,
sort_expression=sort_expression,
filter_expression=filter_expression,
)

return paginator(return_generator=return_generator)

# JCDS APIs

def get_jcds_files_v1(self) -> List[File]:
Expand Down Expand Up @@ -260,9 +387,9 @@ def get_mdm_commands_v2(
self,
filter_expression: FilterExpression,
start_page: int = 0,
end_page: int = None,
end_page: Optional[int] = None,
page_size: int = 100,
sort_expression: SortExpression = None,
sort_expression: Optional[SortExpression] = None,
return_generator: bool = False,
) -> Union[List[MdmCommandStatus], Iterator[Page]]:
"""Returns a list of MDM commands.
Expand Down Expand Up @@ -335,12 +462,12 @@ def get_mdm_commands_v2(

def get_mobile_device_inventory_v2(
self,
sections: List[str] = None,
sections: Optional[List[str]] = None,
start_page: int = 0,
end_page: int = None,
end_page: Optional[int] = None,
page_size: int = 100,
sort_expression: SortExpression = None,
filter_expression: FilterExpression = None,
sort_expression: Optional[SortExpression] = None,
filter_expression: Optional[FilterExpression] = None,
return_generator: bool = False,
) -> Union[List[MobileDevice], Iterator[Page]]:
"""Returns a list of mobile device (iOS and tvOS) inventory records.
Expand Down
16 changes: 8 additions & 8 deletions src/jamf_pro_sdk/clients/pro_api/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, Type, Union
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, Optional, Type, Union

from pydantic import BaseModel

Expand Down Expand Up @@ -54,7 +54,7 @@ def __init__(self, name: str):
def _return_expression(self, operator: str, value: Union[bool, int, str]) -> FilterExpression:
return FilterExpression(
filter_expression=f"{self.name}{operator}{value}",
fields=[FilterEntry(name=self.name, op=operator, value=value)],
fields=[FilterEntry(name=self.name, op=operator, value=str(value))],
)

def eq(self, value: Union[bool, int, str]) -> FilterExpression:
Expand Down Expand Up @@ -146,13 +146,13 @@ def __init__(
self,
api_client: ProApi,
resource_path: str,
return_model: Type[BaseModel] = None,
return_model: Type[BaseModel],
start_page: int = 0,
end_page: int = None,
end_page: Optional[int] = None,
page_size: int = 100,
sort_expression: SortExpression = None,
filter_expression: FilterExpression = None,
extra_params: Dict[str, str] = None,
sort_expression: Optional[SortExpression] = None,
filter_expression: Optional[FilterExpression] = None,
extra_params: Optional[Dict[str, str]] = None,
):
"""A paginator for the Jamf Pro API. A paginator automatically iterates over an API if
multiple unreturned pages are detected in the response. Paginated requests are performed
Expand Down Expand Up @@ -212,7 +212,7 @@ def __init__(
self.extra_params = extra_params

def _paginated_request(self, page: int) -> Page:
query_params = {"page": page, "page-size": self.page_size}
query_params: dict = {"page": page, "page-size": self.page_size}
if self.sort_expression:
query_params["sort"] = str(self.sort_expression)
if self.filter_expression:
Expand Down
2 changes: 1 addition & 1 deletion src/jamf_pro_sdk/models/classic/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .. import BaseModel
from . import ClassicApiModel

_XML_ARRAY_ITEM_NAMES = {}
_XML_ARRAY_ITEM_NAMES: dict = {}


class ClassicPackageItem(BaseModel):
Expand Down
12 changes: 6 additions & 6 deletions src/jamf_pro_sdk/models/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@


class Schemes(str, Enum):
http: str = "http"
https: str = "https"
http = "http"
https = "https"


class SessionConfig(BaseModel):
Expand Down Expand Up @@ -51,15 +51,15 @@ class SessionConfig(BaseModel):
:type scheme: str
"""

timeout: Union[int, None] = None
timeout: Optional[int] = None
max_retries: int = 0
max_concurrency: int = 5
return_exceptions: bool = True
user_agent: str = DEFAULT_USER_AGENT
verify: bool = True
cookie: Union[str, Path] = None
ca_cert_bundle: Union[str, Path] = None
scheme: Schemes = "https"
cookie: Optional[Union[str, Path]] = None
ca_cert_bundle: Optional[Union[str, Path]] = None
scheme: Schemes = Schemes.https


class AccessToken(BaseModel):
Expand Down
Loading