pax_global_header 0000666 0000000 0000000 00000000064 14327745301 0014520 g ustar 00root root 0000000 0000000 52 comment=9e5e13e29c741929d85e4714350f00900cf145a8
aptly-api-client-0.2.4/ 0000775 0000000 0000000 00000000000 14327745301 0014677 5 ustar 00root root 0000000 0000000 aptly-api-client-0.2.4/.coveragerc 0000664 0000000 0000000 00000000110 14327745301 0017010 0 ustar 00root root 0000000 0000000 [run]
source = aptly_api
[report]
fail_under = 100
show_missing = true
aptly-api-client-0.2.4/.github/ 0000775 0000000 0000000 00000000000 14327745301 0016237 5 ustar 00root root 0000000 0000000 aptly-api-client-0.2.4/.github/dependabot.yml 0000664 0000000 0000000 00000000164 14327745301 0021070 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
aptly-api-client-0.2.4/.github/workflows/ 0000775 0000000 0000000 00000000000 14327745301 0020274 5 ustar 00root root 0000000 0000000 aptly-api-client-0.2.4/.github/workflows/test.yml 0000664 0000000 0000000 00000002653 14327745301 0022004 0 ustar 00root root 0000000 0000000 name: Linters
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip wheel
pip install -r requirements-test.txt -e .
- name: Run Tests
run: |
pytest --cov=aptly_api
coverage report -m
coverage lcov
- name: Run Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: coverage.lcov
- name: Run Flake8
run: |
flake8 --max-line-length=120 aptly_api setup.py
- name: Run mypy
run: |
mypy --install-types --non-interactive \
--ignore-missing-imports --follow-imports=skip --disallow-untyped-calls \
--disallow-untyped-defs -p aptly_api aptly-api-client-0.2.4/.gitignore 0000664 0000000 0000000 00000000252 14327745301 0016666 0 ustar 00root root 0000000 0000000 syntax: glob
Lib
lib
*.db
*.pyc
*.pyd
.project
.pydevproject
.settings
.idea
dist
*.egg-info
*.iml
static
build
.idea
.env
__pycache__
.coverage
.mypy_cache
.tox
.toxenv
aptly-api-client-0.2.4/LICENSE 0000664 0000000 0000000 00000002740 14327745301 0015707 0 ustar 00root root 0000000 0000000 Copyright (c) 2016-2017, Jonas Maurus.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
aptly-api-client-0.2.4/README.rst 0000664 0000000 0000000 00000007003 14327745301 0016366 0 ustar 00root root 0000000 0000000 Python 3 Aptly API client
=========================
.. image:: https://coveralls.io/repos/github/gopythongo/aptly-api-client/badge.svg?branch=master
:target: https://coveralls.io/github/gopythongo/aptly-api-client?branch=master
.. image:: https://github.com/gopythongo/aptly-api-client/actions/workflows/test.yml/badge.svg
:target: https://github.com/gopythongo/aptly-api-client/actions/workflows/test.yml
This is a thin abstraction layer for interfacing with
`Aptly's HTTP API `__. It's used by
`GoPythonGo `__, but can be used as
a standalone library from Pypi.
.. code-block:: shell
pip install aptly-api-client
Usage
-----
The library provides a direct abstraction of the published Aptly API, mostly
using the same naming, only replacing it with pythonic naming where necessary.
All code has full `PEP 484 `__
annotations, so if you're using a modern IDE, using this library should be
especially straight-forward.
Where appropriate, the library exposes the interface of the underlying
``requests`` library. This allows you to configure CA pinning, SSL client
certificates, HTTP Basic authentication etc.
.. code-block:: python
# initialize a client
from aptly_api import Client
aptly = Client("http://aptly-endpoint.test/")
# create a repository
aptly.repos.create("myrepo", comment="a test repo",
default_distribution="mydist",
default_component="main")
# upload a package
aptly.files.upload("test_folder", "/tmp/mypkg_1.0_amd64.deb")
# add the package to the repo
aptly.repos.add_uploaded_file("myrepo", "test_folder")
Contributors
============
* @findmyname666
* Filip Křesťan
* @mgusek
* Samuel Bachmann
* @agustinhenze
License
=======
Copyright (c) 2016-2019, Jonas Maurus and Contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
aptly-api-client-0.2.4/aptly_api/ 0000775 0000000 0000000 00000000000 14327745301 0016661 5 ustar 00root root 0000000 0000000 aptly-api-client-0.2.4/aptly_api/__init__.py 0000664 0000000 0000000 00000001406 14327745301 0020773 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# explicit exports for mypy
from aptly_api.client import Client as Client
from aptly_api.base import AptlyAPIException as AptlyAPIException
from aptly_api.parts.packages import Package as Package
from aptly_api.parts.publish import PublishEndpoint as PublishEndpoint
from aptly_api.parts.repos import Repo as Repo, FileReport as FileReport
from aptly_api.parts.snapshots import Snapshot as Snapshot
version = "0.2.4"
__all__ = ['Client', 'AptlyAPIException', 'version', 'Package', 'PublishEndpoint', 'Repo', 'FileReport',
'Snapshot']
aptly-api-client-0.2.4/aptly_api/base.py 0000664 0000000 0000000 00000012314 14327745301 0020146 0 ustar 00root root 0000000 0000000 # This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import IO, TextIO, BinaryIO, Sequence, Dict, Tuple, Optional, Union, List, Any, MutableMapping, Iterable, \
Mapping
from urllib.parse import urljoin
import requests
from requests.auth import AuthBase
_filetype = Optional[
Union[
Dict[
str, Union[
Union[TextIO, BinaryIO, str, bytes],
Tuple[Optional[str], Union[TextIO, BinaryIO, str, bytes]],
Tuple[Optional[str], Union[TextIO, BinaryIO, str, bytes], str],
Tuple[Optional[str], Union[TextIO, BinaryIO, str, bytes], str, Dict[str, str]]
]
],
Sequence[
Tuple[
str, Union[
Union[TextIO, BinaryIO, str, bytes],
Tuple[Optional[str], Union[TextIO, BinaryIO, str, bytes]],
Tuple[Optional[str], Union[TextIO, BinaryIO, str, bytes], str],
Tuple[Optional[str], Union[TextIO, BinaryIO, str, bytes], str, Dict[str, str]]
]
]
],
]
]
_datatype = Optional[
Union[
Iterable[bytes],
str,
bytes,
Union[TextIO, BinaryIO],
List[Tuple[Any, Any]],
Tuple[Tuple[Any, Any], ...],
Mapping[Any, Any]
]
]
class AptlyAPIException(Exception):
def __init__(self, *args: Any, status_code: int = 0) -> None:
super().__init__(*args)
self.status_code = status_code
class BaseAPIClient:
def __init__(self, base_url: str, ssl_verify: Union[str, bool, None] = None,
ssl_cert: Optional[Tuple[str, str]] = None, http_auth: Optional[AuthBase] = None,
timeout: int = 60) -> None:
self.base_url = base_url
self.ssl_verify = ssl_verify
self.ssl_cert = ssl_cert
self.http_auth = http_auth
self.exc_class = AptlyAPIException
self.timeout = timeout
def _error_from_response(self, resp: requests.Response) -> str:
if resp.status_code == 200:
return "no error (status 200)"
try:
rcnt = resp.json()
except ValueError:
return "%s %s %s" % (resp.status_code, resp.reason, resp.text,)
if isinstance(rcnt, dict):
content = rcnt
else:
content = rcnt[0]
ret = "%s - %s -" % (resp.status_code, resp.reason)
if "error" in content:
ret = "%s %s" % (ret, content["error"],)
if "meta" in content:
ret = "%s (%s)" % (ret, content["meta"],)
return ret
def _make_url(self, path: str) -> str:
return urljoin(self.base_url, path)
def do_get(self, urlpath: str, params: Optional[Dict[str, str]] = None) -> requests.Response:
resp = requests.get(self._make_url(urlpath), params=params, verify=self.ssl_verify,
cert=self.ssl_cert, auth=self.http_auth, timeout=self.timeout)
if resp.status_code < 200 or resp.status_code >= 300:
raise AptlyAPIException(self._error_from_response(resp), status_code=resp.status_code)
return resp
def do_post(self, urlpath: str, data: Union[bytes, MutableMapping[str, str], IO[Any], None] = None,
params: Optional[Dict[str, str]] = None,
files: _filetype = None,
json: Optional[MutableMapping[Any, Any]] = None) -> requests.Response:
resp = requests.post(self._make_url(urlpath), data=data, params=params, files=files, json=json,
verify=self.ssl_verify, cert=self.ssl_cert, auth=self.http_auth,
timeout=self.timeout)
if resp.status_code < 200 or resp.status_code >= 300:
raise AptlyAPIException(self._error_from_response(resp), status_code=resp.status_code)
return resp
def do_put(self, urlpath: str, data: Union[bytes, MutableMapping[str, str], IO[Any]] = None,
files: _filetype = None,
json: Optional[MutableMapping[Any, Any]] = None) -> requests.Response:
resp = requests.put(self._make_url(urlpath), data=data, files=files, json=json,
verify=self.ssl_verify, cert=self.ssl_cert, auth=self.http_auth,
timeout=self.timeout)
if resp.status_code < 200 or resp.status_code >= 300:
raise AptlyAPIException(self._error_from_response(resp), status_code=resp.status_code)
return resp
def do_delete(self, urlpath: str, params: Optional[Dict[str, str]] = None,
data: _datatype = None,
json: Union[List[Dict[str, Any]], Dict[str, Any], None] = None) -> requests.Response:
resp = requests.delete(self._make_url(urlpath), params=params, data=data, json=json,
verify=self.ssl_verify, cert=self.ssl_cert, auth=self.http_auth,
timeout=self.timeout)
if resp.status_code < 200 or resp.status_code >= 300:
raise AptlyAPIException(self._error_from_response(resp), status_code=resp.status_code)
return resp
aptly-api-client-0.2.4/aptly_api/client.py 0000664 0000000 0000000 00000004335 14327745301 0020516 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from requests.auth import AuthBase
from typing import Union, Optional, Tuple
from aptly_api.parts.misc import MiscAPISection
from aptly_api.parts.packages import PackageAPISection
from aptly_api.parts.publish import PublishAPISection
from aptly_api.parts.repos import ReposAPISection
from aptly_api.parts.files import FilesAPISection
from aptly_api.parts.snapshots import SnapshotAPISection
class Client:
def __init__(self, aptly_server_url: str, ssl_verify: Union[str, bool, None] = None,
ssl_cert: Optional[Tuple[str, str]] = None, http_auth: Optional[AuthBase] = None,
timeout: int = 60) -> None:
self.__aptly_server_url = aptly_server_url
self.files = FilesAPISection(base_url=self.__aptly_server_url, ssl_verify=ssl_verify,
ssl_cert=ssl_cert, http_auth=http_auth, timeout=timeout)
self.misc = MiscAPISection(base_url=self.__aptly_server_url, ssl_verify=ssl_verify,
ssl_cert=ssl_cert, http_auth=http_auth, timeout=timeout)
self.packages = PackageAPISection(base_url=self.__aptly_server_url, ssl_verify=ssl_verify,
ssl_cert=ssl_cert, http_auth=http_auth, timeout=timeout)
self.publish = PublishAPISection(base_url=self.__aptly_server_url, ssl_verify=ssl_verify,
ssl_cert=ssl_cert, http_auth=http_auth, timeout=timeout)
self.repos = ReposAPISection(base_url=self.__aptly_server_url, ssl_verify=ssl_verify,
ssl_cert=ssl_cert, http_auth=http_auth, timeout=timeout)
self.snapshots = SnapshotAPISection(base_url=self.__aptly_server_url, ssl_verify=ssl_verify,
ssl_cert=ssl_cert, http_auth=http_auth, timeout=timeout)
@property
def aptly_server_url(self) -> str:
return self.__aptly_server_url
def __repr__(self) -> str:
return "Client (Aptly API Client) <%s>" % self.aptly_server_url
aptly-api-client-0.2.4/aptly_api/parts/ 0000775 0000000 0000000 00000000000 14327745301 0020012 5 ustar 00root root 0000000 0000000 aptly-api-client-0.2.4/aptly_api/parts/__init__.py 0000664 0000000 0000000 00000000340 14327745301 0022120 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
aptly-api-client-0.2.4/aptly_api/parts/files.py 0000664 0000000 0000000 00000002767 14327745301 0021502 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
from typing import Sequence, List, Tuple, BinaryIO, cast, Optional # noqa: F401
from aptly_api.base import BaseAPIClient, AptlyAPIException
class FilesAPISection(BaseAPIClient):
def list(self, directory: Optional[str] = None) -> Sequence[str]:
if directory is None:
resp = self.do_get("api/files")
else:
resp = self.do_get("api/files/%s" % directory)
return cast(List[str], resp.json())
def upload(self, destination: str, *files: str) -> Sequence[str]:
to_upload = [] # type: List[Tuple[str, BinaryIO]]
for f in files:
if not os.path.exists(f) or not os.access(f, os.R_OK):
raise AptlyAPIException("File to upload %s can't be opened or read" % f)
fh = open(f, mode="rb")
to_upload.append((f, fh),)
try:
resp = self.do_post("api/files/%s" % destination,
files=to_upload)
except AptlyAPIException:
raise
finally:
for fn, to_close in to_upload:
if not to_close.closed:
to_close.close()
return cast(List[str], resp.json())
def delete(self, path: Optional[str] = None) -> None:
self.do_delete("api/files/%s" % path)
aptly-api-client-0.2.4/aptly_api/parts/misc.py 0000664 0000000 0000000 00000001403 14327745301 0021315 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import cast
from aptly_api.base import AptlyAPIException, BaseAPIClient
class MiscAPISection(BaseAPIClient):
def graph(self, ext: str, layout: str = "horizontal") -> None:
raise NotImplementedError("The Graph API is not yet supported")
def version(self) -> str:
resp = self.do_get("api/version")
if "Version" in resp.json():
return cast(str, resp.json()["Version"])
else:
raise AptlyAPIException("Aptly server didn't return a valid response object:\n%s" % resp.text)
aptly-api-client-0.2.4/aptly_api/parts/packages.py 0000664 0000000 0000000 00000002510 14327745301 0022140 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import NamedTuple, Dict, Union, Optional
from urllib.parse import quote
from aptly_api.base import BaseAPIClient
Package = NamedTuple('Package', [
('key', str),
('short_key', Optional[str]),
('files_hash', Optional[str]),
('fields', Optional[Dict[str, str]]),
])
class PackageAPISection(BaseAPIClient):
@staticmethod
def package_from_response(api_response: Union[str, Dict[str, str]]) -> Package:
if isinstance(api_response, str):
return Package(
key=api_response,
short_key=None,
files_hash=None,
fields=None,
)
else:
return Package(
key=api_response["Key"],
short_key=api_response["ShortKey"] if "ShortKey" in api_response else None,
files_hash=api_response["FilesHash"] if "FilesHash" in api_response else None,
fields=api_response,
)
def show(self, key: str) -> Package:
resp = self.do_get("api/packages/%s" % quote(key))
return self.package_from_response(resp.json())
aptly-api-client-0.2.4/aptly_api/parts/publish.py 0000664 0000000 0000000 00000017623 14327745301 0022043 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import NamedTuple, Sequence, Dict, Union, List, cast, Optional
from urllib.parse import quote
from aptly_api.base import BaseAPIClient, AptlyAPIException
PublishEndpoint = NamedTuple('PublishEndpoint', [
('storage', str),
('prefix', str),
('distribution', str),
('source_kind', str),
('sources', Sequence[Dict[str, str]]),
('architectures', Sequence[str]),
('label', str),
('origin', str),
('acquire_by_hash', bool),
])
T_BodyDict = Dict[str, Union[str, bool, Sequence[Dict[str, str]], Sequence[str], Dict[str, Union[bool, str]]]]
class PublishAPISection(BaseAPIClient):
@staticmethod
def endpoint_from_response(api_response: Union[Dict[str, str], Dict[str, List[str]],
Dict[str, List[Dict[str, str]]]]) -> PublishEndpoint:
return PublishEndpoint(
storage=cast(str, api_response["Storage"]),
prefix=cast(str, api_response["Prefix"]),
distribution=cast(str, api_response["Distribution"]),
source_kind=cast(str, api_response["SourceKind"]),
sources=cast(List[Dict[str, str]], api_response["Sources"]),
architectures=cast(List[str], api_response["Architectures"]),
label=cast(str, api_response["Label"]),
origin=cast(str, api_response["Origin"]),
acquire_by_hash=cast(bool, api_response["AcquireByHash"]),
)
@staticmethod
def escape_prefix(prefix: str) -> str:
if prefix == ".":
return ":."
if "/" in prefix:
# prefix has not yet been quoted as described at
# https://www.aptly.info/doc/api/publish/
if "_" in prefix:
prefix = prefix.replace("_", "__")
prefix = prefix.replace("/", "_")
return prefix
def list(self) -> Sequence[PublishEndpoint]:
resp = self.do_get("api/publish")
ret = []
for rpe in resp.json():
ret.append(self.endpoint_from_response(rpe))
return ret
def publish(self, *, source_kind: str = "local",
sources: Sequence[Dict[str, str]],
architectures: Sequence[str],
prefix: Optional[str] = None, distribution: Optional[str] = None, label: Optional[str] = None,
origin: Optional[str] = None, force_overwrite: bool = False,
sign_skip: bool = False, sign_batch: bool = True, sign_gpgkey: Optional[str] = None,
sign_keyring: Optional[str] = None, sign_secret_keyring: Optional[str] = None,
sign_passphrase: Optional[str] = None, sign_passphrase_file: Optional[str] = None,
acquire_by_hash: Optional[bool] = None) -> PublishEndpoint:
"""
Example:
.. code-block:: python
p.publish(
sources=[{'Name': 'aptly-repo'}], architectures=['amd64'],
prefix='s3:myendpoint:test/a_1', distribution='test', sign_batch=True,
sign_gpgkey='A16BE921', sign_passphrase='*********'
)
"""
if sign_passphrase is not None and sign_passphrase_file is not None:
raise AptlyAPIException("Can't use sign_passphrase and sign_passphrase_file at the same time")
for source in sources:
if "name" not in source and "Name" not in source:
raise AptlyAPIException("Each source in publish() must contain the 'name' attribute")
url = "api/publish"
if prefix is not None and prefix != "":
url = "api/publish/%s" % quote(self.escape_prefix(prefix))
body = {
"SourceKind": source_kind,
"Sources": sources,
} # type: T_BodyDict
if architectures is not None:
body["Architectures"] = architectures
if distribution is not None:
body["Distribution"] = distribution
if label is not None:
body["Label"] = label
if origin is not None:
body["Origin"] = origin
if force_overwrite:
body["ForceOverwrite"] = True
if acquire_by_hash is not None:
body["AcquireByHash"] = acquire_by_hash
sign_dict = {} # type: Dict[str, Union[bool,str]]
if sign_skip:
sign_dict["Skip"] = True
else:
sign_dict["Batch"] = sign_batch
if sign_gpgkey is not None:
sign_dict["GpgKey"] = sign_gpgkey
if sign_keyring is not None:
sign_dict["Keyring"] = sign_keyring
if sign_secret_keyring is not None:
sign_dict["SecretKeyring"] = sign_secret_keyring
if sign_passphrase is not None:
sign_dict["Passphrase"] = sign_passphrase
if sign_passphrase_file is not None:
sign_dict["PassphraseFile"] = sign_passphrase_file
body["Signing"] = sign_dict
resp = self.do_post(url, json=body)
return self.endpoint_from_response(resp.json())
def update(self, *, prefix: str, distribution: str,
snapshots: Optional[Sequence[Dict[str, str]]] = None, force_overwrite: bool = False,
sign_skip: bool = False, sign_batch: bool = True, sign_gpgkey: Optional[str] = None,
sign_keyring: Optional[str] = None, sign_secret_keyring: Optional[str] = None,
sign_passphrase: Optional[str] = None, sign_passphrase_file: Optional[str] = None,
skip_contents: Optional[bool] = False,
skip_cleanup: Optional[bool] = False) -> PublishEndpoint:
"""
Example:
.. code-block:: python
p.update(
prefix="s3:maurusnet:nightly/stretch", distribution="mn-nightly",
sign_batch=True, sign_gpgkey='A16BE921', sign_passphrase='***********'
)
"""
if sign_passphrase is not None and sign_passphrase_file is not None:
raise AptlyAPIException("Can't use sign_passphrase and sign_passphrase_file at the same time")
body = {} # type: T_BodyDict
if snapshots is not None:
for source in snapshots:
if "name" not in source and "Name" not in source:
raise AptlyAPIException("Each source in update() must contain the 'name' attribute")
body["Snapshots"] = snapshots
if force_overwrite:
body["ForceOverwrite"] = True
if skip_cleanup:
body["SkipCleanup"] = True
if skip_contents:
body["SkipContents"] = True
sign_dict = {} # type: Dict[str, Union[bool,str]]
if sign_skip:
sign_dict["Skip"] = True
else:
sign_dict["Batch"] = sign_batch
if sign_gpgkey is not None:
sign_dict["GpgKey"] = sign_gpgkey
if sign_keyring is not None:
sign_dict["Keyring"] = sign_keyring
if sign_secret_keyring is not None:
sign_dict["SecretKeyring"] = sign_secret_keyring
if sign_passphrase is not None:
sign_dict["Passphrase"] = sign_passphrase
if sign_passphrase_file is not None:
sign_dict["PassphraseFile"] = sign_passphrase_file
body["Signing"] = sign_dict
resp = self.do_put("api/publish/%s/%s" %
(quote(self.escape_prefix(prefix)), quote(distribution),), json=body)
return self.endpoint_from_response(resp.json())
def drop(self, *, prefix: str, distribution: str, force_delete: bool = False) -> None:
params = {}
if force_delete:
params["force"] = "1"
self.do_delete("api/publish/%s/%s" %
(quote(self.escape_prefix(prefix)), quote(distribution),), params=params)
aptly-api-client-0.2.4/aptly_api/parts/repos.py 0000664 0000000 0000000 00000012753 14327745301 0021524 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import NamedTuple, Sequence, Dict, Union, cast, Optional
from urllib.parse import quote
from aptly_api.base import BaseAPIClient, AptlyAPIException
from aptly_api.parts.packages import PackageAPISection, Package
Repo = NamedTuple('Repo', [
('name', str),
('comment', Optional[str]),
('default_distribution', Optional[str]),
('default_component', Optional[str])
])
FileReport = NamedTuple('FileReport', [
('failed_files', Sequence[str]),
('report', Dict[str, Sequence[str]])
])
class ReposAPISection(BaseAPIClient):
@staticmethod
def repo_from_response(api_response: Dict[str, str]) -> Repo:
return Repo(
name=api_response["Name"],
default_component=api_response["DefaultComponent"] if "DefaultComponent" in api_response else None,
default_distribution=api_response["DefaultDistribution"] if "DefaultDistribution" in api_response else None,
comment=api_response["Comment"] if "Comment" in api_response else None,
)
@staticmethod
def filereport_from_response(api_response: Dict[str, Union[Sequence[str], Dict[str, Sequence[str]]]]) -> FileReport:
return FileReport(
failed_files=cast(Sequence[str], api_response["FailedFiles"]),
report=cast(Dict[str, Sequence[str]], api_response["Report"]),
)
def create(self, reponame: str, comment: Optional[str] = None, default_distribution: Optional[str] = None,
default_component: Optional[str] = None) -> Repo:
data = {
"Name": reponame,
}
if comment:
data["Comment"] = comment
if default_distribution:
data["DefaultDistribution"] = default_distribution
if default_component:
data["DefaultComponent"] = default_component
resp = self.do_post("api/repos", json=data)
return self.repo_from_response(resp.json())
def show(self, reponame: str) -> Repo:
resp = self.do_get("api/repos/%s" % quote(reponame))
return self.repo_from_response(resp.json())
def search_packages(self, reponame: str, query: Optional[str] = None, with_deps: bool = False,
detailed: bool = False) -> Sequence[Package]:
if query is None and with_deps:
raise AptlyAPIException("search_packages can't include dependencies (with_deps==True) without"
"a query")
params = {}
if query:
params["q"] = query
if with_deps:
params["withDeps"] = "1"
if detailed:
params["format"] = "details"
resp = self.do_get("api/repos/%s/packages" % quote(reponame), params=params)
ret = []
for rpkg in resp.json():
ret.append(PackageAPISection.package_from_response(rpkg))
return ret
def edit(self, reponame: str, comment: Optional[str] = None, default_distribution: Optional[str] = None,
default_component: Optional[str] = None) -> Repo:
if comment is None and default_component is None and default_distribution is None:
raise AptlyAPIException("edit requires at least one of 'comment', 'default_distribution' or "
"'default_component'.")
body = {}
if comment is not None:
body["Comment"] = comment
if default_distribution is not None:
body["DefaultDistribution"] = default_distribution
if default_component is not None:
body["DefaultComponent"] = default_component
resp = self.do_put("api/repos/%s" % quote(reponame), json=body)
return self.repo_from_response(resp.json())
def list(self) -> Sequence[Repo]:
resp = self.do_get("api/repos")
repos = []
for rdesc in resp.json():
repos.append(
self.repo_from_response(rdesc)
)
return repos
def delete(self, reponame: str, force: bool = False) -> None:
self.do_delete("api/repos/%s" % quote(reponame), params={"force": "1" if force else "0"})
def add_uploaded_file(self, reponame: str, dir: str, filename: Optional[str] = None,
remove_processed_files: bool = True, force_replace: bool = False) -> FileReport:
params = {
"noRemove": "0" if remove_processed_files else "1",
}
if force_replace:
params["forceReplace"] = "1"
if filename is None:
resp = self.do_post("api/repos/%s/file/%s" % (quote(reponame), quote(dir),), params=params)
else:
resp = self.do_post("api/repos/%s/file/%s/%s" % (quote(reponame), quote(dir), quote(filename),),
params=params)
return self.filereport_from_response(resp.json())
def add_packages_by_key(self, reponame: str, *package_keys: str) -> Repo:
resp = self.do_post("api/repos/%s/packages" % quote(reponame), json={
"PackageRefs": package_keys,
})
return self.repo_from_response(resp.json())
def delete_packages_by_key(self, reponame: str, *package_keys: str) -> Repo:
resp = self.do_delete("api/repos/%s/packages" % quote(reponame), json={
"PackageRefs": package_keys,
})
return self.repo_from_response(resp.json())
aptly-api-client-0.2.4/aptly_api/parts/snapshots.py 0000664 0000000 0000000 00000011336 14327745301 0022412 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from datetime import datetime
from typing import NamedTuple, Sequence, Optional, Dict, Union, cast, List
from urllib.parse import quote
import iso8601
from aptly_api.base import BaseAPIClient, AptlyAPIException
from aptly_api.parts.packages import Package, PackageAPISection
Snapshot = NamedTuple('Snapshot', [
('name', str),
('description', Optional[str]),
('created_at', Optional[datetime])
])
class SnapshotAPISection(BaseAPIClient):
@staticmethod
def snapshot_from_response(api_response: Dict[str, Union[str, None]]) -> Snapshot:
return Snapshot(
# use a cast() here as `name` can never be None, but the `api_response` declaration can't handle that
name=cast(str, api_response["Name"]),
description=api_response["Description"] if "Description" in api_response else None,
created_at=iso8601.parse_date(
cast(str, api_response["CreatedAt"])
) if "CreatedAt" in api_response else None,
)
def list(self, sort: str = 'name') -> Sequence[Snapshot]:
if sort not in ['name', 'time']:
raise AptlyAPIException("Snapshot LIST only supports two sort modes: 'name' and 'time'. %s is not "
"supported." % sort)
resp = self.do_get("api/snapshots", params={"sort": sort})
ret = []
for rsnap in resp.json():
ret.append(self.snapshot_from_response(rsnap))
return ret
def create_from_repo(self, reponame: str, snapshotname: str, description: Optional[str] = None) -> Snapshot:
body = {
"Name": snapshotname,
}
if description is not None:
body["Description"] = description
resp = self.do_post("api/repos/%s/snapshots" % quote(reponame), json=body)
return self.snapshot_from_response(resp.json())
def create_from_packages(self, snapshotname: str, description: Optional[str] = None,
source_snapshots: Optional[Sequence[str]] = None,
package_refs: Optional[Sequence[str]] = None) -> Snapshot:
body = {
"Name": snapshotname,
} # type: Dict[str, Union[str, Sequence[str]]]
if description is not None:
body["Description"] = description
if source_snapshots is not None:
body["SourceSnapshots"] = source_snapshots
if package_refs is not None:
body["PackageRefs"] = package_refs
resp = self.do_post("api/snapshots", json=body)
return self.snapshot_from_response(resp.json())
def update(self, snapshotname: str, newname: Optional[str] = None,
newdescription: Optional[str] = None) -> Snapshot:
if newname is None and newdescription is None:
raise AptlyAPIException("When updating a Snapshot you must at lease provide either a new name or a "
"new description.")
body = {} # type: Dict[str, Union[str, Sequence[str]]]
if newname is not None:
body["Name"] = newname
if newdescription is not None:
body["Description"] = newdescription
resp = self.do_put("api/snapshots/%s" % quote(snapshotname), json=body)
return self.snapshot_from_response(resp.json())
def show(self, snapshotname: str) -> Snapshot:
resp = self.do_get("api/snapshots/%s" % quote(snapshotname))
return self.snapshot_from_response(resp.json())
def list_packages(self, snapshotname: str, query: Optional[str] = None, with_deps: bool = False,
detailed: bool = False) -> Sequence[Package]:
params = {}
if query is not None:
params["q"] = query
if with_deps:
params["withDeps"] = "1"
if detailed:
params["format"] = "details"
resp = self.do_get("api/snapshots/%s/packages" % quote(snapshotname), params=params)
ret = []
for rpkg in resp.json():
ret.append(PackageAPISection.package_from_response(rpkg))
return ret
def delete(self, snapshotname: str, force: bool = False) -> None:
params = None
if force:
params = {
"force": "1",
}
self.do_delete("api/snapshots/%s" % quote(snapshotname), params=params)
def diff(self, snapshot1: str, snapshot2: str) -> Sequence[Dict[str, str]]:
resp = self.do_get("api/snapshots/%s/diff/%s" % (quote(snapshot1), quote(snapshot2),))
return cast(List[Dict[str, str]], resp.json())
aptly-api-client-0.2.4/aptly_api/tests/ 0000775 0000000 0000000 00000000000 14327745301 0020023 5 ustar 00root root 0000000 0000000 aptly-api-client-0.2.4/aptly_api/tests/__init__.py 0000664 0000000 0000000 00000000771 14327745301 0022141 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from .test_base import * # noqa
from .test_client import * # noqa
from .test_files import * # noqa
from .test_misc import * # noqa
from .test_packages import * # noqa
from .test_publish import * # noqa
from .test_repos import * # noqa
from .test_snapshots import * # noqa
aptly-api-client-0.2.4/aptly_api/tests/test_base.py 0000664 0000000 0000000 00000000340 14327745301 0022343 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
aptly-api-client-0.2.4/aptly_api/tests/test_client.py 0000664 0000000 0000000 00000011151 14327745301 0022711 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Any, cast
from unittest.case import TestCase
import requests
import requests_mock
from aptly_api import Client as AptlyClient
# as we're testing the individual parts, this is rather simple
from aptly_api.base import AptlyAPIException
class ClientTests(TestCase):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self.client = AptlyClient("http://test/")
def test_instantiate(self) -> None:
cl = AptlyClient("http://test/")
self.assertEqual(
str(cl),
"Client (Aptly API Client) "
)
@requests_mock.Mocker(kw='rmock')
def test_api_subdir_get(self, *, rmock: requests_mock.Mocker) -> None:
# register mock:// scheme with urllib.parse
import urllib.parse
urllib.parse.uses_netloc += ['mock']
urllib.parse.uses_relative += ['mock']
urllib.parse.uses_fragment += ['mock']
urllib.parse.uses_params += ['mock']
cl = AptlyClient("mock://test/basedir/")
rmock.get("mock://test/basedir/api/test", status_code=200, text='')
cl.files.do_get("api/test")
self.assertTrue(rmock.called)
def test_error_no_error(self) -> None:
class MockResponse:
def __init__(self, status_code: int = 200) -> None:
self.status_code = status_code
self.assertEqual(
self.client.files._error_from_response(cast(requests.Response, MockResponse())),
"no error (status 200)"
)
def test_error_no_json(self) -> None:
adapter = requests_mock.Adapter()
adapter.register_uri("GET", "mock://test/api", status_code=400, text="this is not json", reason="test")
session = requests.session()
session.mount("mock", adapter)
resp = session.get("mock://test/api")
self.assertEqual(
self.client.files._error_from_response(resp),
"400 test this is not json"
)
def test_error_dict(self) -> None:
adapter = requests_mock.Adapter()
adapter.register_uri("GET", "mock://test/api", status_code=400, text='{"error": "error", "meta": "meta"}',
reason="test")
session = requests.session()
session.mount("mock", adapter)
resp = session.get("mock://test/api")
self.assertEqual(
self.client.files._error_from_response(resp),
"400 - test - error (meta)"
)
def test_error_list(self) -> None:
adapter = requests_mock.Adapter()
adapter.register_uri("GET", "mock://test/api", status_code=400, text='[{"error": "error", "meta": "meta"}]',
reason="test")
session = requests.session()
session.mount("mock", adapter)
resp = session.get("mock://test/api")
self.assertEqual(
self.client.files._error_from_response(resp),
"400 - test - error (meta)"
)
@requests_mock.Mocker(kw='rmock')
def test_error_get(self, *, rmock: requests_mock.Mocker) -> None:
rmock.register_uri("GET", "mock://test/api", status_code=400, text='[{"error": "error", "meta": "meta"}]',
reason="test")
with self.assertRaises(AptlyAPIException):
self.client.files.do_get("mock://test/api")
@requests_mock.Mocker(kw='rmock')
def test_error_post(self, *, rmock: requests_mock.Mocker) -> None:
rmock.register_uri("POST", "mock://test/api", status_code=400, text='[{"error": "error", "meta": "meta"}]',
reason="test")
with self.assertRaises(AptlyAPIException):
self.client.files.do_post("mock://test/api")
@requests_mock.Mocker(kw='rmock')
def test_error_put(self, *, rmock: requests_mock.Mocker) -> None:
rmock.register_uri("PUT", "mock://test/api", status_code=400, text='[{"error": "error", "meta": "meta"}]',
reason="test")
with self.assertRaises(AptlyAPIException):
self.client.files.do_put("mock://test/api")
@requests_mock.Mocker(kw='rmock')
def test_error_delete(self, *, rmock: requests_mock.Mocker) -> None:
rmock.register_uri("DELETE", "mock://test/api", status_code=400, text='[{"error": "error", "meta": "meta"}]',
reason="test")
with self.assertRaises(AptlyAPIException):
self.client.files.do_delete("mock://test/api")
aptly-api-client-0.2.4/aptly_api/tests/test_files.py 0000664 0000000 0000000 00000004062 14327745301 0022540 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Any
import os
from unittest.case import TestCase
import requests_mock
from aptly_api.base import AptlyAPIException
from aptly_api.parts.files import FilesAPISection
@requests_mock.Mocker(kw='rmock')
class FilesAPISectionTests(TestCase):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self.fapi = FilesAPISection("http://test/")
def test_list(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/files", text='["aptly-0.9"]')
self.assertSequenceEqual(self.fapi.list(), ["aptly-0.9"])
def test_list_dir(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/files/dir", text='["dir/aptly_0.9~dev+217+ge5d646c_i386.deb"]')
self.assertSequenceEqual(self.fapi.list("dir"), ["dir/aptly_0.9~dev+217+ge5d646c_i386.deb"])
def test_upload_file(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/files/test", text='["test/testpkg.deb"]')
self.assertSequenceEqual(
self.fapi.upload("test", os.path.join(os.path.dirname(__file__), "testpkg.deb")),
['test/testpkg.deb'],
)
def test_upload_invalid(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(AptlyAPIException):
self.fapi.upload("test", "noexistant")
def test_upload_failed(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/files/test", text='["test/testpkg.deb"]',
status_code=500)
with self.assertRaises(AptlyAPIException):
self.fapi.upload("test", os.path.join(os.path.dirname(__file__), "testpkg.deb"))
def test_delete(self, *, rmock: requests_mock.Mocker) -> None:
rmock.delete("http://test/api/files/test",
text='{}')
self.fapi.delete("test")
aptly-api-client-0.2.4/aptly_api/tests/test_misc.py 0000664 0000000 0000000 00000002234 14327745301 0022370 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Any
from unittest.case import TestCase
import requests_mock
from aptly_api.base import AptlyAPIException
from aptly_api.parts.misc import MiscAPISection
@requests_mock.Mocker(kw='rmock')
class MiscAPISectionTests(TestCase):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self.mapi = MiscAPISection("http://test/")
def test_version(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/version", text='{"Version":"1.0.1"}')
self.assertEqual(self.mapi.version(), "1.0.1")
def test_graph(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(NotImplementedError):
self.mapi.graph("png")
def test_version_error(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/version", text='{"droenk": "blah"}')
with self.assertRaises(AptlyAPIException):
self.mapi.version()
aptly-api-client-0.2.4/aptly_api/tests/test_packages.py 0000664 0000000 0000000 00000007501 14327745301 0023215 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Any
from unittest.case import TestCase
import requests_mock
from aptly_api.parts.packages import PackageAPISection, Package
@requests_mock.Mocker(kw='rmock')
class PackageAPISectionTests(TestCase):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self.papi = PackageAPISection("http://test/")
def test_show(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get(
"http://test/api/packages/Pamd64%20authserver%200.1.14~dev0-1%201cc572a93625a9c9",
text="""{"Architecture":"amd64",
"Depends":"python3, python3-pip, python3-virtualenv, adduser, cron-daemon",
"Description":" no description given\\n",
"Filename":"authserver_0.1.14~dev0-1.deb",
"FilesHash":"1cc572a93625a9c9",
"Homepage":"http://example.com/no-uri-given",
"Installed-Size":"74927",
"Key":"Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9",
"License":"unknown",
"MD5sum":"03cca0794e63cf147b879e0a3695f523",
"Maintainer":"Jonas Maurus",
"Package":"authserver",
"Priority":"extra",
"Provides":"maurusnet-authserver",
"SHA1":"9a77a31dba51f612ee08ee096381f0c7e8f97a42",
"SHA256":"63555a135bf0aa1762d09fc622881aaf352cdb3b244da5d78278c7efa2dba8b7",
"SHA512":"01f9ca888014599374bf7a2c8c46f895d7ef0dfea99dfd092007f9fc5d5fe57a2755b843eda296b65"""
"""cb6ac0f64b9bd88b507221a71825f5329fdda0e58728cd7",
"Section":"default",
"ShortKey":"Pamd64 authserver 0.1.14~dev0-1",
"Size":"26623042",
"Vendor":"root@test",
"Version":"0.1.14~dev0-1"}"""
)
pkg = self.papi.show("Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9")
self.assertEqual(
pkg,
Package(
key='Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9',
short_key='Pamd64 authserver 0.1.14~dev0-1',
files_hash='1cc572a93625a9c9',
fields={
'Architecture': 'amd64',
'Depends': 'python3, python3-pip, python3-virtualenv, adduser, cron-daemon',
'Description': ' no description given\n',
'Filename': 'authserver_0.1.14~dev0-1.deb',
'FilesHash': '1cc572a93625a9c9',
'Homepage': 'http://example.com/no-uri-given',
'Installed-Size': '74927',
'Key': 'Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9',
'License': 'unknown',
'MD5sum': '03cca0794e63cf147b879e0a3695f523',
'Maintainer': 'Jonas Maurus',
'Package': 'authserver',
'Priority': 'extra',
'Provides': 'maurusnet-authserver',
'SHA1': '9a77a31dba51f612ee08ee096381f0c7e8f97a42',
'SHA256': '63555a135bf0aa1762d09fc622881aaf352cdb3b244da5d78278c7efa2dba8b7',
'SHA512': '01f9ca888014599374bf7a2c8c46f895d7ef0dfea99dfd092007f9fc5d5fe57a2755b843eda296b65cb6ac'
'0f64b9bd88b507221a71825f5329fdda0e58728cd7',
'Section': 'default',
'ShortKey': 'Pamd64 authserver 0.1.14~dev0-1',
'Size': '26623042',
'Vendor': 'root@test',
'Version': '0.1.14~dev0-1'
}
)
)
aptly-api-client-0.2.4/aptly_api/tests/test_publish.py 0000664 0000000 0000000 00000037343 14327745301 0023114 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from unittest.case import TestCase
from typing import Any
import requests_mock
from aptly_api.base import AptlyAPIException
from aptly_api.parts.publish import PublishEndpoint, PublishAPISection
@requests_mock.Mocker(kw='rmock')
class PublishAPISectionTests(TestCase):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self.papi = PublishAPISection("http://test/")
self.maxDiff = None
def test_list(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/publish",
text='[{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"mn-nightly","Label":"",'
'"Origin":"","Prefix":"nightly/stretch","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"maurusnet"}],'
'"Storage":"s3:maurusnet"}]')
self.assertSequenceEqual(
self.papi.list(),
[
PublishEndpoint(
storage='s3:maurusnet',
prefix='nightly/stretch',
distribution='mn-nightly',
source_kind='local',
sources=[{
'Name': 'maurusnet',
'Component': 'main'
}],
architectures=['amd64'],
label='',
origin='',
acquire_by_hash=False
)
]
)
def test_update(self, *, rmock: requests_mock.Mocker) -> None:
rmock.put("http://test/api/publish/s3%3Aaptly-repo%3Atest_xyz__1/test",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"",'
'"Origin":"","Prefix":"test/xyz_1","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],'
'"Storage":"s3:aptly-repo"}')
self.assertEqual(
self.papi.update(
prefix="s3:aptly-repo:test/xyz_1",
distribution="test",
sign_batch=True,
sign_gpgkey="A16BE921",
sign_passphrase="123456",
),
PublishEndpoint(
storage='s3:aptly-repo',
prefix='test/xyz_1',
distribution='test',
source_kind='local',
sources=[{
'Name': 'aptly-repo',
'Component': 'main'
}],
architectures=['amd64'],
label='',
origin='',
acquire_by_hash=False
)
)
def test_update_passphrase_file(self, *, rmock: requests_mock.Mocker) -> None:
rmock.put("http://test/api/publish/s3%3Aaptly-repo%3Atest_xyz__1/test",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"",'
'"Origin":"","Prefix":"test/xyz_1","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],'
'"Storage":"s3:aptly-repo"}')
self.assertEqual(
self.papi.update(
prefix="s3:aptly-repo:test/xyz_1",
distribution="test",
sign_batch=True,
sign_gpgkey="A16BE921",
sign_passphrase_file="/root/passphrase.txt",
),
PublishEndpoint(
storage='s3:aptly-repo',
prefix='test/xyz_1',
distribution='test',
source_kind='local',
sources=[{
'Name': 'aptly-repo',
'Component': 'main'
}],
architectures=['amd64'],
label='',
origin='',
acquire_by_hash=False
)
)
def test_update_no_sign(self, *, rmock: requests_mock.Mocker) -> None:
rmock.put("http://test/api/publish/s3%3Aaptly-repo%3Atest_xyz__1/test",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"",'
'"Origin":"","Prefix":"test/xyz_1","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],'
'"Storage":"s3:aptly-repo"}')
self.assertEqual(
self.papi.update(
prefix="s3:aptly-repo:test/xyz_1",
distribution="test",
sign_skip=True,
),
PublishEndpoint(
storage='s3:aptly-repo',
prefix='test/xyz_1',
distribution='test',
source_kind='local',
sources=[{
'Name': 'aptly-repo',
'Component': 'main'
}],
architectures=['amd64'],
label='',
origin='',
acquire_by_hash=False
)
)
def test_update_snapshots(self, *, rmock: requests_mock.Mocker) -> None:
rmock.put("http://test/api/publish/s3%3Aaptly-repo%3Atest_xyz__1/test",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"",'
'"Origin":"","Prefix":"test/xyz_1","SkipContents":false,'
'"SourceKind":"snapshot","Sources":[{"Component":"main","Name":"aptly-repo-1"}],'
'"Storage":"s3:aptly-repo"}')
self.assertEqual(
self.papi.update(
prefix="s3:aptly-repo:test/xyz_1",
distribution="test",
snapshots=[{"Name": "aptly-repo-1"}],
force_overwrite=True,
sign_batch=True,
sign_gpgkey="A16BE921",
sign_passphrase="123456",
sign_keyring="/etc/gpg-managed-keyring/pubring.pub",
sign_secret_keyring="/etc/gpg-managed-keyring/secring.gpg"
),
PublishEndpoint(
storage='s3:aptly-repo',
prefix='test/xyz_1',
distribution='test',
source_kind='snapshot',
sources=[{
'Name': 'aptly-repo-1',
'Component': 'main',
}],
architectures=['amd64'],
label='',
origin='',
acquire_by_hash=False
)
)
def test_publish(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/publish/s3%3Amyendpoint%3Atest_a__1",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"test",'
'"Origin":"origin","Prefix":"test/a_1","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],'
'"Storage":"s3:myendpoint"}')
self.assertEqual(
self.papi.publish(
sources=[{'Name': 'aptly-repo'}], architectures=['amd64'],
prefix='s3:myendpoint:test/a_1', distribution='test', label='test', origin='origin',
sign_batch=True, sign_gpgkey='A16BE921', sign_passphrase='*********',
force_overwrite=True, sign_keyring="/etc/gpg-managed-keyring/pubring.pub",
sign_secret_keyring="/etc/gpg-managed-keyring/secring.gpg",
acquire_by_hash=False
),
PublishEndpoint(
storage='s3:myendpoint',
prefix='test/a_1',
distribution='test',
source_kind='local',
sources=[{'Component': 'main', 'Name': 'aptly-repo'}],
architectures=['amd64'],
label='test',
origin='origin',
acquire_by_hash=False
)
)
def test_publish_passphrase_file(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/publish/s3%3Amyendpoint%3Atest_a__1",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"test",'
'"Origin":"origin","Prefix":"test/a_1","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],'
'"Storage":"s3:myendpoint"}')
self.assertEqual(
self.papi.publish(
sources=[{'Name': 'aptly-repo'}], architectures=['amd64'],
prefix='s3:myendpoint:test/a_1', distribution='test', label='test', origin='origin',
sign_batch=True, sign_gpgkey='A16BE921', sign_passphrase_file='/root/passphrase.txt',
force_overwrite=True, sign_keyring="/etc/gpg-managed-keyring/pubring.pub",
sign_secret_keyring="/etc/gpg-managed-keyring/secring.gpg",
acquire_by_hash=False
),
PublishEndpoint(
storage='s3:myendpoint',
prefix='test/a_1',
distribution='test',
source_kind='local',
sources=[{'Component': 'main', 'Name': 'aptly-repo'}],
architectures=['amd64'],
label='test',
origin='origin',
acquire_by_hash=False
)
)
def test_publish_no_sign(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/publish/s3%3Amyendpoint%3Atest_a__1",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"test",'
'"Origin":"origin","Prefix":"test/a_1","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],'
'"Storage":"s3:myendpoint"}')
self.assertEqual(
self.papi.publish(
sources=[{'Name': 'aptly-repo'}], architectures=['amd64'],
prefix='s3:myendpoint:test/a_1', distribution='test', label='test', origin='origin',
sign_skip=True,
acquire_by_hash=False
),
PublishEndpoint(
storage='s3:myendpoint',
prefix='test/a_1',
distribution='test',
source_kind='local',
sources=[{'Component': 'main', 'Name': 'aptly-repo'}],
architectures=['amd64'],
label='test',
origin='origin',
acquire_by_hash=False
)
)
def test_publish_default_key(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/publish/s3%3Amyendpoint%3Atest_a__1",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"test",'
'"Origin":"origin","Prefix":"test/a_1","SkipContents":false,'
'"SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],'
'"Storage":"s3:myendpoint"}')
self.assertEqual(
self.papi.publish(
sources=[{'Name': 'aptly-repo'}], architectures=['amd64'],
prefix='s3:myendpoint:test/a_1', distribution='test', label='test', origin='origin',
sign_batch=True, sign_passphrase='*********',
force_overwrite=True, sign_keyring="/etc/gpg-managed-keyring/pubring.pub",
sign_secret_keyring="/etc/gpg-managed-keyring/secring.gpg",
),
PublishEndpoint(
storage='s3:myendpoint',
prefix='test/a_1',
distribution='test',
source_kind='local',
sources=[{'Component': 'main', 'Name': 'aptly-repo'}],
architectures=['amd64'],
label='test',
origin='origin',
acquire_by_hash=False
)
)
def test_update_snapshot_default_key(self, *, rmock: requests_mock.Mocker) -> None:
rmock.put("http://test/api/publish/s3%3Aaptly-repo%3Atest_xyz__1/test",
text='{"AcquireByHash":false,"Architectures":["amd64"],"Distribution":"test","Label":"",'
'"Origin":"","Prefix":"test/xyz_1","SkipContents":false,'
'"SourceKind":"snapshot","Sources":[{"Component":"main","Name":"aptly-repo-1"}],'
'"Storage":"s3:aptly-repo"}')
self.assertEqual(
self.papi.update(
prefix="s3:aptly-repo:test/xyz_1",
distribution="test",
snapshots=[{"Name": "aptly-repo-1"}],
force_overwrite=True,
sign_batch=True,
sign_passphrase="123456",
sign_keyring="/etc/gpg-managed-keyring/pubring.pub",
sign_secret_keyring="/etc/gpg-managed-keyring/secring.gpg",
skip_contents=True,
skip_cleanup=True
),
PublishEndpoint(
storage='s3:aptly-repo',
prefix='test/xyz_1',
distribution='test',
source_kind='snapshot',
sources=[{
'Name': 'aptly-repo-1',
'Component': 'main',
}],
architectures=['amd64'],
label='',
origin='',
acquire_by_hash=False
)
)
def test_double_passphrase(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(AptlyAPIException):
self.papi.publish(sources=[{'Name': 'aptly-repo'}], architectures=['amd64'],
prefix='s3:myendpoint:test/a_1', distribution='test', sign_skip=False,
sign_gpgkey='A16BE921', sign_passphrase="*******", sign_passphrase_file="****")
with self.assertRaises(AptlyAPIException):
self.papi.update(prefix='s3:myendpoint:test/a_1', distribution='test', sign_skip=False,
sign_gpgkey='A16BE921', sign_passphrase="*******", sign_passphrase_file="****")
def test_no_name(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(AptlyAPIException):
self.papi.publish(sources=[{'nope': 'nope'}], architectures=['amd64'],
prefix='s3:myendpoint:test/a_1', distribution='test', sign_skip=False,
sign_gpgkey='A16BE921', sign_passphrase="*******")
with self.assertRaises(AptlyAPIException):
self.papi.update(snapshots=[{'nope': 'nope'}],
prefix='s3:myendpoint:test/a_1', distribution='test', sign_skip=False,
sign_gpgkey='A16BE921', sign_passphrase="*******")
def test_drop(self, *, rmock: requests_mock.Mocker) -> None:
rmock.delete("http://test/api/publish/s3%3Amyendpoint%3Atest_a__1/test?force=1", text='{}')
self.papi.drop(prefix='s3:myendpoint:test/a_1', distribution='test', force_delete=True)
def test_escape_prefix(self, *args: Any, **kwargs: Any) -> None:
self.assertEqual(
self.papi.escape_prefix("test/a_1"),
"test_a__1",
)
self.assertEqual(
self.papi.escape_prefix("test-a-1"),
"test-a-1"
)
self.assertEqual(
self.papi.escape_prefix("test/a"),
"test_a"
)
self.assertEqual(
self.papi.escape_prefix("."),
":."
)
aptly-api-client-0.2.4/aptly_api/tests/test_repos.py 0000664 0000000 0000000 00000023272 14327745301 0022572 0 ustar 00root root 0000000 0000000 # -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Any
from unittest.case import TestCase
import requests_mock
from aptly_api.base import AptlyAPIException
from aptly_api.parts.packages import Package
from aptly_api.parts.repos import ReposAPISection, Repo, FileReport
@requests_mock.Mocker(kw='rmock')
class ReposAPISectionTests(TestCase):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self.rapi = ReposAPISection("http://test/")
def test_create(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/repos",
text='{"Name":"aptly-repo","Comment":"test","DefaultDistribution":"test","DefaultComponent":"test"}')
self.assertEqual(
self.rapi.create("aptly-repo", comment="test", default_component="test", default_distribution="test"),
Repo(
name="aptly-repo",
default_distribution="test",
default_component="test",
comment="test",
)
)
def test_show(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/repos/aptly-repo",
text='{"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}')
self.assertEqual(
self.rapi.show("aptly-repo"),
Repo(
name="aptly-repo",
default_distribution="",
default_component="",
comment="",
)
)
def test_search(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/repos/aptly-repo/packages",
text='["Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9"]')
self.assertSequenceEqual(
self.rapi.search_packages("aptly-repo"),
[
Package(
key="Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9",
short_key=None,
files_hash=None,
fields=None,
)
],
)
def test_search_details(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get(
"http://test/api/repos/aptly-repo/packages?format=details",
text="""[{
"Architecture":"amd64",
"Depends":"python3, python3-pip, python3-virtualenv, adduser, cron-daemon",
"Description":" no description given\\n",
"Filename":"authserver_0.1.14~dev0-1.deb",
"FilesHash":"1cc572a93625a9c9",
"Homepage":"http://example.com/no-uri-given",
"Installed-Size":"74927",
"Key":"Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9",
"License":"unknown",
"MD5sum":"03cca0794e63cf147b879e0a3695f523",
"Maintainer":"Jonas Maurus",
"Package":"authserver",
"Priority":"extra",
"Provides":"maurusnet-authserver",
"SHA1":"9a77a31dba51f612ee08ee096381f0c7e8f97a42",
"SHA256":"63555a135bf0aa1762d09fc622881aaf352cdb3b244da5d78278c7efa2dba8b7",
"SHA512":"01f9ca888014599374bf7a2c8c46f895d7ef0dfea99dfd092007f9fc5d5fe57a2755b843eda296b65cb"""
"""6ac0f64b9bd88b507221a71825f5329fdda0e58728cd7",
"Section":"default",
"ShortKey":"Pamd64 authserver 0.1.14~dev0-1",
"Size":"26623042",
"Vendor":"root@test",
"Version":"0.1.14~dev0-1"
}]"""
)
self.assertSequenceEqual(
self.rapi.search_packages("aptly-repo", detailed=True, with_deps=True, query="Name (authserver)"),
[
Package(
key='Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9',
short_key='Pamd64 authserver 0.1.14~dev0-1',
files_hash='1cc572a93625a9c9',
fields={
'Architecture': 'amd64',
'Depends': 'python3, python3-pip, python3-virtualenv, adduser, cron-daemon',
'Description': ' no description given\n',
'Filename': 'authserver_0.1.14~dev0-1.deb',
'FilesHash': '1cc572a93625a9c9',
'Homepage': 'http://example.com/no-uri-given',
'Installed-Size': '74927',
'Key': 'Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9',
'License': 'unknown',
'MD5sum': '03cca0794e63cf147b879e0a3695f523',
'Maintainer': 'Jonas Maurus',
'Package': 'authserver',
'Priority': 'extra',
'Provides': 'maurusnet-authserver',
'SHA1': '9a77a31dba51f612ee08ee096381f0c7e8f97a42',
'SHA256': '63555a135bf0aa1762d09fc622881aaf352cdb3b244da5d78278c7efa2dba8b7',
'SHA512': '01f9ca888014599374bf7a2c8c46f895d7ef0dfea99dfd092007f9fc5d5fe57a2755b843eda296b65'
'cb6ac0f64b9bd88b507221a71825f5329fdda0e58728cd7',
'Section': 'default',
'ShortKey': 'Pamd64 authserver 0.1.14~dev0-1',
'Size': '26623042',
'Vendor': 'root@test',
'Version': '0.1.14~dev0-1'
}
)
]
)
def test_repo_edit_validation(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(AptlyAPIException):
self.rapi.edit("aptly-repo")
def test_repo_edit(self, *, rmock: requests_mock.Mocker) -> None:
rmock.put("http://test/api/repos/aptly-repo",
text='{"Name":"aptly-repo","Comment":"comment",'
'"DefaultDistribution":"stretch","DefaultComponent":"main"}')
self.assertEqual(
self.rapi.edit("aptly-repo", comment="comment", default_distribution="stretch", default_component="main"),
Repo(name='aptly-repo', comment='comment', default_distribution='stretch', default_component='main')
)
def test_list(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/repos",
text='[{"Name":"maurusnet","Comment":"","DefaultDistribution":"",'
'"DefaultComponent":"main"},{"Name":"aptly-repo","Comment":"comment",'
'"DefaultDistribution":"stretch","DefaultComponent":"main"}]')
self.assertSequenceEqual(
self.rapi.list(),
[
Repo(name='maurusnet', comment='', default_distribution='', default_component='main'),
Repo(name='aptly-repo', comment='comment', default_distribution='stretch', default_component='main'),
]
)
def test_delete(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(requests_mock.NoMockAddress):
self.rapi.delete("aptly-repo", force=True)
def test_add_file(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/repos/aptly-repo/file/test/dirmngr_2.1.18-6_amd64.deb",
text='{"FailedFiles":[],"Report":{"Warnings":[],'
'"Added":["dirmngr_2.1.18-6_amd64 added"],"Removed":[]}}')
self.assertEqual(
self.rapi.add_uploaded_file("aptly-repo", "test", "dirmngr_2.1.18-6_amd64.deb", force_replace=True),
FileReport(failed_files=[],
report={'Added': ['dirmngr_2.1.18-6_amd64 added'],
'Removed': [], 'Warnings': []})
)
def test_add_dir(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/repos/aptly-repo/file/test",
text='{"FailedFiles":[],"Report":{"Warnings":[],'
'"Added":["dirmngr_2.1.18-6_amd64 added"],"Removed":[]}}')
self.assertEqual(
self.rapi.add_uploaded_file("aptly-repo", "test", force_replace=True),
FileReport(failed_files=[],
report={'Added': ['dirmngr_2.1.18-6_amd64 added'],
'Removed': [], 'Warnings': []})
)
def test_add_package(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/repos/aptly-repo/packages",
text='{"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}')
self.assertEqual(
self.rapi.add_packages_by_key("aptly-repo", "Pamd64 dirmngr 2.1.18-6 4c7412c5f0d7b30a"),
Repo(name='aptly-repo', comment='', default_distribution='', default_component='')
)
def test_delete_package(self, *, rmock: requests_mock.Mocker) -> None:
rmock.delete(
"http://test/api/repos/aptly-repo/packages",
text='{"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}'
)
self.assertEqual(
self.rapi.delete_packages_by_key("aptly-repo", "Pamd64 dirmngr 2.1.18-6 4c7412c5f0d7b30a"),
Repo(name='aptly-repo', comment='', default_distribution='', default_component=''),
)
def test_search_invalid_params(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(AptlyAPIException):
self.rapi.search_packages("aptly-repo", with_deps=True)
aptly-api-client-0.2.4/aptly_api/tests/test_snapshots.py 0000664 0000000 0000000 00000024162 14327745301 0023463 0 ustar 00root root 0000000 0000000 # This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Any
from unittest.case import TestCase
import iso8601
import requests_mock
from aptly_api.base import AptlyAPIException
from aptly_api.parts.packages import Package
from aptly_api.parts.snapshots import SnapshotAPISection, Snapshot
@requests_mock.Mocker(kw='rmock')
class SnapshotAPISectionTests(TestCase):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self.sapi = SnapshotAPISection("http://test/")
self.maxDiff = None
def test_list(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/snapshots",
text='[{"Name":"stretch-security-1","CreatedAt":"2017-06-03T21:36:22.2692213Z",'
'"Description":"Snapshot from mirror [stretch-security]: '
'http://security.debian.org/debian-security/ stretch/updates"},'
'{"Name":"stretch-updates-1","CreatedAt":"2017-06-03T21:36:22.431767659Z",'
'"Description":"Snapshot from mirror [stretch-updates]: '
'http://ftp-stud.hs-esslingen.de/debian/ stretch-updates"}]')
self.assertSequenceEqual(
self.sapi.list(),
[
Snapshot(
name='stretch-security-1',
description='Snapshot from mirror [stretch-security]: http://security.debian.org/debian-security/ '
'stretch/updates',
created_at=iso8601.parse_date('2017-06-03T21:36:22.2692213Z')
),
Snapshot(
name='stretch-updates-1',
description='Snapshot from mirror [stretch-updates]: http://ftp-stud.hs-esslingen.de/debian/ '
'stretch-updates',
created_at=iso8601.parse_date('2017-06-03T21:36:22.431767659Z')
)
]
)
def test_list_invalid(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(AptlyAPIException):
self.sapi.list("snoepsort")
def test_update_noparams(self, *, rmock: requests_mock.Mocker) -> None:
with self.assertRaises(AptlyAPIException):
self.sapi.update("test")
def test_create(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/repos/aptly-repo/snapshots",
text='{"Name":"aptly-repo-1","CreatedAt":"2017-06-03T23:43:40.275605639Z",'
'"Description":"Snapshot from local repo [aptly-repo]"}')
self.assertEqual(
self.sapi.create_from_repo("aptly-repo", "aptly-repo-1",
description='Snapshot from local repo [aptly-repo]'),
Snapshot(
name='aptly-repo-1',
description='Snapshot from local repo [aptly-repo]',
created_at=iso8601.parse_date('2017-06-03T23:43:40.275605639Z')
)
)
def test_list_packages(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/snapshots/aptly-repo-1/packages",
text='["Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1 5f70af798690300d"]')
self.assertEqual(
self.sapi.list_packages("aptly-repo-1"),
[
Package(
key='Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1 5f70af798690300d',
short_key=None,
files_hash=None,
fields=None
),
]
)
def test_list_packages_details(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/snapshots/aptly-repo-1/packages",
text='[{"Architecture":"all","Depends":"postgresql-9.6-postgis-2.3-scripts",'
'"Description":" transitional dummy package\\n This is a transitional dummy package. '
'It can safely be removed.\\n",'
'"Filename":"postgresql-9.6-postgis-scripts_2.3.2+dfsg-1~exp2.pgdg90+1_all.deb",'
'"FilesHash":"5f70af798690300d",'
'"Homepage":"http://postgis.net/",'
'"Installed-Size":"491",'
'"Key":"Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1 5f70af798690300d",'
'"MD5sum":"56de7bac497e4ac34017f4d11e75fffb",'
'"Maintainer":"Debian GIS Project \u003cpkg-grass-devel@lists.alioth.debian.org\u003e",'
'"Package":"postgresql-9.6-postgis-scripts",'
'"Priority":"extra",'
'"SHA1":"61bb9250e7a35be9b78808944e8cfbae1e70f67d",'
'"SHA256":"01c0c4645e9100f7ddb6d05a9d36ad3866ac8d2e412b7c04163a9e65397ce05e",'
'"Section":"oldlibs",'
'"ShortKey":"Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1",'
'"Size":"468824","Source":"postgis","Version":"2.3.2+dfsg-1~exp2.pgdg90+1"}]')
parsed = self.sapi.list_packages("aptly-repo-1", query="Name (% postgresql-9.6.-postgis-sc*)", detailed=True,
with_deps=True)[0]
expected = Package(
key='Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1 5f70af798690300d',
short_key='Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1',
files_hash='5f70af798690300d',
fields={
'Maintainer': 'Debian GIS Project ',
'Size': '468824',
'MD5sum': '56de7bac497e4ac34017f4d11e75fffb',
'ShortKey': 'Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1',
'FilesHash': '5f70af798690300d',
'Filename': 'postgresql-9.6-postgis-scripts_2.3.2+dfsg-1~exp2.pgdg90+1_all.deb',
'Section': 'oldlibs',
'Homepage': 'http://postgis.net/',
'Description': ' transitional dummy package\n This is a transitional dummy package. '
'It can safely be removed.\n',
'Architecture': 'all',
'Priority': 'extra',
'Source': 'postgis',
'SHA1': '61bb9250e7a35be9b78808944e8cfbae1e70f67d',
'Installed-Size': '491',
'Version': '2.3.2+dfsg-1~exp2.pgdg90+1',
'Depends': 'postgresql-9.6-postgis-2.3-scripts',
'Key': 'Pall postgresql-9.6-postgis-scripts 2.3.2+dfsg-1~exp2.pgdg90+1 5f70af798690300d',
'SHA256': '01c0c4645e9100f7ddb6d05a9d36ad3866ac8d2e412b7c04163a9e65397ce05e',
'Package': 'postgresql-9.6-postgis-scripts'
}
)
# mypy should detect this as ensuring that parsed.fields is not None, but it doesn't
self.assertIsNotNone(parsed.fields)
self.assertIsNotNone(expected.fields)
self.assertDictEqual(
parsed.fields if parsed.fields else {}, # make sure that mypy doesn't error on this being potentially None
expected.fields if expected.fields else {}, # this can't happen unless Package.__init__ is fubared
)
def test_show(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/snapshots/aptly-repo-1",
text='{"Name":"aptly-repo-1",'
'"CreatedAt":"2017-06-03T23:43:40.275605639Z",'
'"Description":"Snapshot from local repo [aptly-repo]"}')
self.assertEqual(
self.sapi.show("aptly-repo-1"),
Snapshot(
name='aptly-repo-1',
description='Snapshot from local repo [aptly-repo]',
created_at=iso8601.parse_date('2017-06-03T23:43:40.275605639Z')
)
)
def test_update(self, *, rmock: requests_mock.Mocker) -> None:
rmock.put("http://test/api/snapshots/aptly-repo-1",
text='{"Name":"aptly-repo-2","CreatedAt":"2017-06-03T23:43:40.275605639Z",'
'"Description":"test"}')
self.assertEqual(
self.sapi.update("aptly-repo-1", newname="aptly-repo-2", newdescription="test"),
Snapshot(
name='aptly-repo-2',
description='test',
created_at=iso8601.parse_date('2017-06-03T23:43:40.275605639Z')
)
)
def test_delete(self, *, rmock: requests_mock.Mocker) -> None:
rmock.delete("http://test/api/snapshots/aptly-repo-1",
text='{}')
self.sapi.delete("aptly-repo-1", force=True)
def test_diff(self, *, rmock: requests_mock.Mocker) -> None:
rmock.get("http://test/api/snapshots/aptly-repo-1/diff/aptly-repo-2",
text='[{"Left":null,"Right":"Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9"},'
'{"Left":"Pamd64 radicale 1.1.1 fbc974fa526f14e9","Right":null}]')
self.assertSequenceEqual(
self.sapi.diff("aptly-repo-1", "aptly-repo-2"),
[
{'Left': None, 'Right': 'Pamd64 authserver 0.1.14~dev0-1 1cc572a93625a9c9'},
{'Left': 'Pamd64 radicale 1.1.1 fbc974fa526f14e9', 'Right': None}
]
)
def test_create_from_packages(self, *, rmock: requests_mock.Mocker) -> None:
rmock.post("http://test/api/snapshots",
text='{"Name":"aptly-repo-2","CreatedAt":"2017-06-07T14:19:07.706408213Z","Description":"test"}')
self.assertEqual(
self.sapi.create_from_packages(
"aptly-repo-2",
description="test",
package_refs=["Pamd64 dirmngr 2.1.18-6 4c7412c5f0d7b30a"],
source_snapshots=["aptly-repo-1"]
),
Snapshot(
name='aptly-repo-2',
description='test',
created_at=iso8601.parse_date('2017-06-07T14:19:07.706408213Z')
)
)
aptly-api-client-0.2.4/aptly_api/tests/testpkg.deb 0000664 0000000 0000000 00000000000 14327745301 0022146 0 ustar 00root root 0000000 0000000 aptly-api-client-0.2.4/requirements-test.txt 0000664 0000000 0000000 00000000234 14327745301 0021137 0 ustar 00root root 0000000 0000000 requests-mock==1.10.0
coverage==6.5.0
coveralls==3.3.1
flake8==5.0.4
pep257==0.7.0
doc8==1.0.0
Pygments==2.13.0
mypy==0.982
pytest==7.2.0
pytest-cov==4.0.0
aptly-api-client-0.2.4/setup.py 0000664 0000000 0000000 00000003474 14327745301 0016421 0 ustar 00root root 0000000 0000000 #!/usr/bin/python
# -* encoding: utf-8 *-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import re
from setuptools import setup, find_packages
_package_root = "."
_root_package = 'aptly_api'
_HERE = os.path.abspath(os.path.dirname(__file__))
with open("aptly_api/__init__.py", "rt", encoding="utf-8") as vf:
lines = vf.readlines()
_version = "0.0.0+local"
for line in lines:
m = re.match("version = \"(.*?)\"", line)
if m:
_version = m.group(1)
_packages = find_packages(_package_root, exclude=["*.tests", "*.tests.*", "tests.*", "tests"])
_requirements = [
# intentionally unpinned. We're a library, so we don't need to conflict with others by pinning versions
# and we don't depend on a specific minimum version.
'requests',
'iso8601',
]
try:
long_description = open(os.path.join(_HERE, 'README.rst')).read()
except IOError:
long_description = None
setup(
name='aptly-api-client',
version=_version,
packages=_packages,
package_dir={
'': _package_root,
},
install_requires=_requirements,
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"Environment :: Console",
"Programming Language :: Python :: 3 :: Only",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Operating System :: POSIX",
],
author="Jonas Maurus (@jdelic)",
author_email="jonas@gopythongo.com",
maintainer="GoPythonGo.com",
maintainer_email="info@gopythongo.com",
description="A Python 3 client for the Aptly API",
long_description=long_description,
)