Skip to content

Commit 607c69f

Browse files
authored
Merge pull request #79 from cr3ation/main
Replace deprecated Jamf Pro API endpoints with supported alternatives
2 parents f49e30a + 6f08dc6 commit 607c69f

9 files changed

Lines changed: 1325 additions & 43 deletions

File tree

.gitignore

Lines changed: 159 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,165 @@
1+
# Byte-compiled / optimized / DLL files
12
*__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
224
*.egg-info/
3-
.idea/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
*.py,cover
50+
.hypothesis/
451
.pytest_cache/
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Scrapy stuff:
59+
.scrapy
60+
61+
# Sphinx documentation
62+
docs/_build/
63+
64+
# PyBuilder
65+
.pybuilder/
66+
target/
67+
68+
# Jupyter Notebook
69+
.ipynb_checkpoints
70+
71+
# IPython
72+
profile_default/
73+
ipython_config.py
74+
75+
# pyenv
76+
# For a library or package, you might want to ignore these files since the code is
77+
# intended to run in multiple environments; otherwise, check them in:
78+
# .python-version
79+
80+
# pipenv
81+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
82+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
83+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
84+
# install all needed dependencies.
85+
#Pipfile.lock
86+
87+
# poetry
88+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
89+
# This is especially recommended for binary packages to ensure reproducibility, and is more
90+
# commonly ignored for libraries.
91+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
92+
#poetry.lock
93+
94+
# pdm
95+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
96+
#pdm.lock
97+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
98+
# in version control.
99+
# https://pdm.fming.dev/#use-with-ide
100+
.pdm.toml
101+
102+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
103+
__pypackages__/
104+
105+
# Celery stuff
106+
celerybeat-schedule
107+
celerybeat.pid
108+
109+
# SageMath parsed files
110+
*.sage.py
111+
112+
# Environments
113+
.env
114+
.venv
115+
env/
116+
venv/
117+
ENV/
118+
env.bak/
119+
venv.bak/
120+
121+
# Spyder project settings
122+
.spyderproject
123+
.spyproject
124+
125+
# Rope project settings
126+
.ropeproject
127+
128+
# ruff
5129
.ruff_cache/
6-
.venv/
7-
build/
8-
coverage/
9-
dist/
130+
131+
# mkdocs documentation
132+
/site
133+
134+
# mypy
135+
.mypy_cache/
136+
.dmypy.json
137+
dmypy.json
138+
139+
# Pyre type checker
140+
.pyre/
141+
142+
# pytype static type analyzer
143+
.pytype/
144+
145+
# Cython debug symbols
146+
cython_debug/
147+
148+
# PyCharm
149+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
150+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
151+
# and can be added to the global gitignore or merged into this file. For a more nuclear
152+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
153+
.idea/
154+
155+
# macOS
156+
*.DS_Store
157+
158+
# VSCode
159+
.vscode/
160+
161+
# Project-specific
162+
/coverage/
163+
pytest.ini
10164
docs/contributors/_autosummary/
11165
docs/reference/_autosummary/
12-
htmlcov/
13-
.coverage
14-
.DS_Store
15-
.vscode

src/jamf_pro_sdk/clients/jcds2.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
import math
5+
import warnings
56
from pathlib import Path
67
from tempfile import TemporaryDirectory
78
from typing import TYPE_CHECKING, Callable, Iterator, Union
@@ -134,9 +135,55 @@ def _upload_part(s3_client, multipart_upload: dict, part_number: int, file_uploa
134135
logger.debug(part_resp)
135136
return {"PartNumber": part_number, "ETag": part_resp["ETag"]}
136137

138+
def upload_package(self, file_path: Union[str, Path]) -> None:
139+
"""Upload a file and create the package object using the new package upload API.
140+
141+
This method replaces :meth:`upload_file` and does not require the ``aws`` extra dependency.
142+
It uses the ``POST /v1/packages/{id}/upload`` endpoint instead of the deprecated JCDS v1
143+
S3-based workflow.
144+
145+
A ``JCDS2FileExistsError`` is raised if any file of the same name exists and is associated
146+
to a package.
147+
148+
:param file_path: The path to the file to upload. Will raise ``FileNotFoundError`` if the
149+
path to the file's location does not exist.
150+
:type file_path: Union[str, Path]
151+
"""
152+
if not isinstance(file_path, Path):
153+
file_path = Path(file_path)
154+
155+
if not file_path.exists():
156+
raise FileNotFoundError(f"File not found: {file_path}")
157+
158+
packages = [
159+
self.classic_api_client.get_package_by_id(p)
160+
for p in self.classic_api_client.list_all_packages()
161+
]
162+
163+
for p in packages:
164+
if file_path.name == p.filename:
165+
raise JCDS2FileExistsError(
166+
f"The file '{file_path.name}' exists and is associated to package "
167+
f"({p.id}) '{p.name}'"
168+
)
169+
170+
new_package = ClassicPackage(name=file_path.name, filename=file_path.name)
171+
new_pkg_id = self.classic_api_client.create_package(data=new_package)
172+
logger.debug("Created package %s", new_pkg_id)
173+
174+
try:
175+
self.pro_api_client.upload_package_v1(package_id=new_pkg_id, file_path=file_path)
176+
except Exception as err:
177+
logger.exception(err)
178+
raise
179+
137180
def upload_file(self, file_path: Union[str, Path]) -> None:
138181
"""Upload a file to the JCDS and create the package object.
139182
183+
.. deprecated::
184+
The JCDS v1 API is deprecated by Jamf (2025-08-28). Use :meth:`upload_package` instead,
185+
which does not require the ``aws`` extra dependency.
186+
140187
If the file is less than 1 GiB in size the upload will be performed in a single request. If
141188
the file is greater than 1 GiB in size a multipart upload operation will be performed.
142189
@@ -151,6 +198,12 @@ def upload_file(self, file_path: Union[str, Path]) -> None:
151198
to the file's location does not exist.
152199
:type file_path: Union[str, Path]
153200
"""
201+
warnings.warn(
202+
"upload_file() is deprecated. The JCDS v1 API was deprecated by Jamf on 2025-08-28. "
203+
"Use upload_package() instead, which does not require the 'aws' extra dependency.",
204+
DeprecationWarning,
205+
stacklevel=2,
206+
)
154207
if not BOTO3_IS_INSTALLED:
155208
raise ImportError("The 'aws' extra dependency is required.")
156209

0 commit comments

Comments
 (0)