Classic API

The Classic API only accepts XML for write operations, but allows JSON for read operations. The Classic API interface for the SDK only accepts JSON data in read responses. API responses are returned as data models that can be more easily interacted with using dot notation.

Read Requests

The curated methods return data models of the JSON response. Data models can be interacted with using dot notation.

>>> computers = client.classic_api.list_all_computers()
>>> len(computers)
4
>>> type(computers[0])
<class 'jamf_pro_sdk.models.classic.computers.ComputersItem'>
>>> for c in computers:
...   print(c.name)
...
Oscar's MacBook Air
Chip's MacBook Pro
Zach's MacBook Air
Bryson’s MacBook Pro
>>>

Some Classic API operations support subsets which extend or limit the data that is returned:

>>> computers = client.classic_api.list_all_computers(subsets=["basic"])
>>> computers[0]
ComputersItem(id=1, name="Oscar's MacBook Air", managed=True, username='oscar', model='MacBookPro18,3', department='', building='', mac_address='00:1A:2B:CD:34:FF', udid='2AD4F6B0-3926-4305-B567-C1FB93F36768', serial_number='TGIF772PLY', report_date_utc=datetime.datetime(2022, 12, 16, 22, 38, 51, 347000, tzinfo=datetime.timezone.utc), report_date_epoch=1671230331347)
>>>

ISO 8601 date fields (generally, fields that end with _date_utc) are automatically converted into datetime.datetime objects:

>>> computers[0].report_date_utc
datetime.datetime(2022, 12, 16, 22, 38, 51, 347000, tzinfo=datetime.timezone.utc)
>>>

The data models can also be converted back into a Python dict:

>>> computers[0].dict()
{'id': 1, 'name': "Oscar's MacBook Pro", 'managed': True, 'username': 'oscar', 'model': 'MacBookPro18,3', 'department': '', 'building': '', 'mac_address': '00:1A:2B:CD:34:FF"', 'udid': '2AD4F6B0-3926-4305-B567-C1FB93F36768', 'serial_number': 'TGIF772PLY', 'report_date_utc': datetime.datetime(2022, 12, 16, 22, 38, 51, 347000, tzinfo=datetime.timezone.utc), 'report_date_epoch': 1671230331347}
>>>

Tip

You can browse the available data models at Classic API Models.

Write Requests

The Classic API only accepts XML for POST and PUT operations. The SDK accepts XML strings if you are generating this data, or you can leverage the data models and their built-in XML generation to do the work for you.

Here is an example where an extension attribute value is being updated:

>>> from jamf_pro_sdk.models.classic.computers import ClassicComputer, ClassicComputerExtensionAttribute
>>> computer_update = ClassicComputer()
>>> computer_update.extension_attributes.append(ClassicComputerExtensionAttribute(id=1, value="new"))
>>> computer_update.xml()
'<?xml version="1.0" encoding="UTF-8" ?><computer><extension_attributes><extension_attribute><id>1</id><value>new</value></extension_attribute></extension_attributes></computer>'
>>>

You can also construct the update as a dict and pass that into the model:

>>> data  = {"extension_attributes": [{"id": 1, "value": "new"}]}
>>> computer_update = ClassicComputer(**data)
>>> computer_update.xml()
'<?xml version="1.0" encoding="UTF-8" ?><computer><extension_attributes><extension_attribute><id>1</id><value>new</value></extension_attribute></extension_attributes></computer>'
>>>

The SDK’s data models perform type checking and some validation. By using the data models you can prevent invalid data from being set.

>>> bad_data = {"extension_attributes": {"id": 1, "value": "new"}}
>>> ClassicComputer(**bad_data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/jamf-pro-sdk/jamf_pro_sdk/models/__init__.py", line 10, in __init__
    super(BaseModel, self).__init__(*args, **kwargs)
  File "pydantic/main.py", line 342, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for ClassicComputer
extension_attributes
  value is not a valid list (type=type_error.list)
>>>

The XML string or SDK data model are passed to the data argument for write operations. The SDK handles converting data models to XML.

>>> xml = '<?xml version="1.0" encoding="UTF-8" ?><computer><extension_attributes><extension_attribute><id>1</id><value>new</value></extension_attribute></extension_attributes></computer>'
>>> client.classic_api.update_computer_by_id(computer_id=1, data=xml)

>>> data  = {"extension_attributes": [{"id": 1, "value": "new"}]}
>>> computer_update = ClassicComputer(**data)
>>> client.classic_api.update_computer_by_id(computer_id=1, data=computer_update)

Example Usage

Assume this client has been instantiated for the examples shown below.

>>> from jamf_pro_sdk import JamfProClient, BasicAuthProvider
>>> client = JamfProClient(
...     server="jamf.my.org",
...     credentials=BasicAuthProvider("oscar", "j@mf1234!")
... )
>>>

Update a Computer’s Location

You can selectively update fields on a computer record by creating a ClassicComputer object and setting the desired fields, passing a dictionary with a model, or a raw XML string.

Using the model:

>>> from jamf_pro_sdk.models.classic.computers import ClassicComputer
>>> computer_update = ClassicComputer()
>>> computer_update.location.username = "amy"
>>> computer_update.location.real_name = "Amy"
>>> computer_update.location.email_address = "amy@my.org"
>>> computer_update.xml()
'<?xml version="1.0" encoding="UTF-8" ?><computer><location><username>amy</username><real_name>Amy</real_name><email_address>amy@my.org</email_address></location></computer>'
>>> client.classic_api.update_computer_by_id(5, computer_update)
>>>

Using a dictionary:

>>> dict_update = {'username': 'amy', 'real_name': 'Amy', 'email_address': 'amy@my.org'}
>>> client.classic_api.update_computer_by_id(5, ClassicComputer(**dict_update))
>>>

Using a raw XML string:

>>> xml_update = """<computer>
...     <location>
...         <username>amy</username>
...         <real_name>Amy</real_name>
...         <email_address>amy@my.org</email_address>
...     </location>
... </computer>"""
>>> client.classic_api.update_computer_by_id(5, xml_update)
>>>

Update a Static Computer Group’s Membership

Static group memberships are modified by providing an iterable of either device IDs (integers) or ClassicComputerGroupMember objects. Passing in the objects is a handy shortcut when iterating over membership results and selecting devices to add/remove from the same group or another.

>>> client.classic_api.get_computer_group_by_id(3)
ClassicComputerGroup(id=3, name='Test Group 1', is_smart=False, site=Site(id=-1, name='None'), criteria=[], computers=[])
>>>

Passing an array with an ID:

>>> client.classic_api.update_static_computer_group_membership_by_id(3, computers_to_add=[10])
>>> client.classic_api.get_computer_group_by_id(3)).computers
[ClassicComputerGroupMember(id=10, name='YohnkBook', mac_address='25:3f:d9:ec:d5:b6', alt_mac_address='77:81:eb:54:b2:6a', serial_number='CJYQC70IW2T3')]

Passing a ComputerGroupMember object:

>>> from jamf_pro_sdk.models.classic.computer_groups import ClassicComputerGroupMember
>>> new_member = ClassicComputerGroupMember(id=10)
>>> client.classic_api.update_static_computer_group_membership_by_id(3, computers_to_add=[new_member])
>>> client.classic_api.get_computer_group_by_id(3)).computers
[ClassicComputerGroupMember(id=10, name='YohnkBook', mac_address='25:3f:d9:ec:d5:b6', alt_mac_address='77:81:eb:54:b2:6a', serial_number='CJYQC70IW2T3')]