Skip to content

Commit

Permalink
Merge pull request #52 from anancarv/fix-50-properties-method
Browse files Browse the repository at this point in the history
Fix properties method
  • Loading branch information
anancarv authored Jun 19, 2020
2 parents 358bf19 + 0945e00 commit 14399dc
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 52 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This library enables you to manage Artifactory resources such as users, groups,
+ [Repository](#repository)
+ [Permission](#permission)
* [Artifacts](#artifacts)
+ [Get the information about a file or folder](#get-the-information-about-a-file-or-folder)
+ [Deploy an artifact](#deploy-an-artifact)
+ [Download an artifact](#download-an-artifact)
+ [Retrieve artifact properties](#retrieve-artifact-properties)
Expand Down Expand Up @@ -262,6 +263,13 @@ art.permissions.delete("test_permission")

### Artifacts

#### Get the information about a file or folder
```python
artifact_info = art.artifacts.info("<ARTIFACT_PATH_IN_ARTIFACTORY>")
# file_info = art.artifacts.info("my-repository/my/artifact/directory/file.txt")
# folder_info = art.artifacts.info("my-repository/my/artifact/directory")
```

#### Deploy an artifact
```python
artifact = art.artifacts.deploy("<LOCAL_FILE_LOCATION>", "<ARTIFACT_PATH_IN_ARTIFACTORY>")
Expand All @@ -278,16 +286,16 @@ artifact = art.artifacts.download("<ARTIFACT_PATH_IN_ARTIFACTORY>", "<LOCAL_DIRE

#### Retrieve artifact properties
```python
artifact_properties = art.artifacts.properties("<ARTIFACT_PATH_IN_ARTIFACTORY>")
artifact_properties = art.artifacts.properties("<ARTIFACT_PATH_IN_ARTIFACTORY>") # returns all properties
# artifact_properties = art.artifacts.properties("my-repository/my/new/artifact/directory/file.txt")
>>> print(artifact_properties.json)
artifact_properties = art.artifacts.properties("<ARTIFACT_PATH_IN_ARTIFACTORY>", ["prop1", "prop2"]) # returns specific properties
artifact_properties.properties["prop1"] # ["value1", "value1-bis"]
```

#### Retrieve artifact stats
```python
artifact_stats = art.artifacts.stats("<ARTIFACT_PATH_IN_ARTIFACTORY>")
# artifact_stats = art.artifacts.stats("my-repository/my/new/artifact/directory/file.txt")
>>> print(artifact_stats.json)
```

#### Copy artifact to a new location
Expand Down
5 changes: 5 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
[mypy]
files = pyartifactory
plugins = pydantic.mypy
warn_return_any = True
warn_unused_configs = True
pretty = True

[mypy-requests_toolbelt.multipart]
ignore_missing_imports = True

[pydantic-mypy]
init_forbid_extra = True
8 changes: 8 additions & 0 deletions pyartifactory/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,13 @@ class PermissionNotFoundException(ArtifactoryException):
"""A permission object was not found."""


class ArtifactNotFoundException(ArtifactoryException):
"""An artifact was not found"""


class PropertyNotFoundException(ArtifactoryException):
"""All requested properties were not found"""


class InvalidTokenDataException(ArtifactoryException):
"""The token contains invalid data."""
10 changes: 8 additions & 2 deletions pyartifactory/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Import all models here.
"""
from typing import Union
from typing import Union, Type

from .auth import AuthModel, ApiKeyModel, PasswordModel, AccessTokenModel
from .group import Group, SimpleGroup
Expand All @@ -16,7 +16,13 @@
SimpleRepository,
)

from .artifact import ArtifactPropertiesResponse, ArtifactStatsResponse
from .artifact import (
ArtifactPropertiesResponse,
ArtifactStatsResponse,
ArtifactFileInfoResponse,
ArtifactFolderInfoResponse,
ArtifactInfoResponse,
)
from .permission import Permission, SimplePermission


Expand Down
34 changes: 27 additions & 7 deletions pyartifactory/models/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


from datetime import datetime
from typing import Optional, List
from typing import Optional, List, Dict, Union
from pydantic import BaseModel


Expand All @@ -26,27 +26,44 @@ class Child(BaseModel):
"""Models a child folder."""

uri: str
folder: str
folder: bool


class ArtifactPropertiesResponse(BaseModel):
"""Models an artifact properties response."""
""" Models an artifact properties response."""

uri: str
properties: Dict[str, List[str]]


class ArtifactInfoResponseBase(BaseModel):
"""The base information available for both file and folder"""

repo: str
path: str
created: Optional[datetime] = None
createdBy: str
createdBy: Optional[str] = None
lastModified: Optional[datetime] = None
modifiedBy: Optional[str] = None
lastUpdated: Optional[datetime] = None
uri: str


class ArtifactFolderInfoResponse(ArtifactInfoResponseBase):
"""Models an artifact folder info response."""

children: List[Child]


class ArtifactFileInfoResponse(ArtifactInfoResponseBase):
"""Models an artifact file info response."""

downloadUri: Optional[str] = None
remoteUrl: Optional[str] = None
mimeType: Optional[str] = None
size: Optional[str] = None
size: Optional[int] = None
checksums: Optional[Checksums] = None
originalChecksums: Optional[OriginalChecksums] = None
children: Optional[List[Child]] = None
uri: str


class ArtifactStatsResponse(BaseModel):
Expand All @@ -58,3 +75,6 @@ class ArtifactStatsResponse(BaseModel):
lastDownloadedBy: Optional[str]
remoteDownloadCount: int
remoteLastDownloaded: int


ArtifactInfoResponse = Union[ArtifactFileInfoResponse, ArtifactFolderInfoResponse]
77 changes: 60 additions & 17 deletions pyartifactory/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
PermissionAlreadyExistsException,
PermissionNotFoundException,
InvalidTokenDataException,
PropertyNotFoundException,
ArtifactNotFoundException,
)

from pyartifactory.models import (
Expand All @@ -47,6 +49,11 @@
SimplePermission,
ArtifactPropertiesResponse,
ArtifactStatsResponse,
ArtifactInfoResponse,
)
from pyartifactory.models.artifact import (
ArtifactFileInfoResponse,
ArtifactFolderInfoResponse,
)

logger = logging.getLogger("pyartifactory")
Expand All @@ -56,11 +63,7 @@ class Artifactory:
"""Models artifactory."""

def __init__(
self,
url: str,
auth: Tuple[str, str] = None,
verify: bool = True,
cert: str = None,
self, url: str, auth: Tuple[str, str], verify: bool = True, cert: str = None,
):
self.artifactory = AuthModel(url=url, auth=auth, verify=verify, cert=cert)
self.users = ArtifactoryUser(self.artifactory)
Expand Down Expand Up @@ -745,9 +748,34 @@ def delete(self, permission_name: str) -> None:
class ArtifactoryArtifact(ArtifactoryObject):
"""Models an artifactory artifact."""

def info(self, artifact_path: str) -> ArtifactInfoResponse:
"""
Retrieve information about a file or a folder
See https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-FolderInfo
and https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-FileInfo
:param artifact_path: Path to file or folder in Artifactory
"""
artifact_path = artifact_path.lstrip("/")
try:
response = self._get(f"api/storage/{artifact_path}")
artifact_info: ArtifactInfoResponse = parse_obj_as(
Union[ArtifactFolderInfoResponse, ArtifactFileInfoResponse],
response.json(),
)
return artifact_info
except requests.exceptions.HTTPError as error:
if error.response.status_code == 404:
logger.error("Artifact %s does not exist", artifact_path)
raise ArtifactNotFoundException(
f"Artifact {artifact_path} does not exist"
)
raise ArtifactoryException from error

def deploy(
self, local_file_location: str, artifact_path: str
) -> ArtifactPropertiesResponse:
) -> ArtifactInfoResponse:
"""
:param artifact_path: Path to file in Artifactory
:param local_file_location: Location of the file to deploy
Expand All @@ -764,7 +792,7 @@ def deploy(
headers = {"Prefer": "respond-async", "Content-Type": form.content_type}
self._put(f"{artifact_path}", headers=headers, data=form)
logger.debug("Artifact %s successfully deployed", local_filename)
return self.properties(artifact_path)
return self.info(artifact_path)

def download(self, artifact_path: str, local_directory_path: str = None) -> str:
"""
Expand All @@ -789,15 +817,30 @@ def download(self, artifact_path: str, local_directory_path: str = None) -> str:
logger.debug("Artifact %s successfully downloaded", local_filename)
return local_file_full_path

def properties(self, artifact_path: str) -> ArtifactPropertiesResponse:
def properties(
self, artifact_path: str, properties: Optional[List[str]] = None
) -> ArtifactPropertiesResponse:
"""
:param artifact_path: Path to file in Artifactory
:param properties: List of properties to retrieve
:return: Artifact properties
"""
if properties is None:
properties = []
artifact_path = artifact_path.lstrip("/")
response = self._get(f"api/storage/{artifact_path}?properties[=x[,y]]")
logger.debug("Artifact Properties successfully retrieved")
return ArtifactPropertiesResponse(**response.json())
try:
response = self._get(
f"api/storage/{artifact_path}",
params={"properties": ",".join(properties)},
)
logger.debug("Artifact Properties successfully retrieved")
return ArtifactPropertiesResponse(**response.json())
except requests.exceptions.HTTPError as error:
if error.response.status_code == 404:
raise PropertyNotFoundException(
f"Properties {properties} were not found on artifact {artifact_path}"
)
raise ArtifactoryException from error

def stats(self, artifact_path: str) -> ArtifactStatsResponse:
"""
Expand All @@ -811,12 +854,12 @@ def stats(self, artifact_path: str) -> ArtifactStatsResponse:

def copy(
self, artifact_current_path: str, artifact_new_path: str, dryrun: bool = False
) -> ArtifactPropertiesResponse:
) -> ArtifactInfoResponse:
"""
:param artifact_current_path: Current path to file
:param artifact_new_path: New path to file
:param dryrun: Dry run
:return: ArtifactPropertiesResponse: properties of the copied artifact
:return: ArtifactInfoResponse: info of the copied artifact
"""
artifact_current_path = artifact_current_path.lstrip("/")
artifact_new_path = artifact_new_path.lstrip("/")
Expand All @@ -827,16 +870,16 @@ def copy(

self._post(f"api/copy/{artifact_current_path}?to={artifact_new_path}&dry={dry}")
logger.debug("Artifact %s successfully copied", artifact_current_path)
return self.properties(artifact_new_path)
return self.info(artifact_new_path)

def move(
self, artifact_current_path: str, artifact_new_path: str, dryrun: bool = False
) -> ArtifactPropertiesResponse:
) -> ArtifactInfoResponse:
"""
:param artifact_current_path: Current path to file
:param artifact_new_path: New path to file
:param dryrun: Dry run
:return: ArtifactPropertiesResponse: properties of the moved artifact
:return: ArtifactInfoResponse: info of the moved artifact
"""
artifact_current_path = artifact_current_path.lstrip("/")
artifact_new_path = artifact_new_path.lstrip("/")
Expand All @@ -848,7 +891,7 @@ def move(

self._post(f"api/move/{artifact_current_path}?to={artifact_new_path}&dry={dry}")
logger.debug("Artifact %s successfully moved", artifact_current_path)
return self.properties(artifact_new_path)
return self.info(artifact_new_path)

def delete(self, artifact_path: str) -> None:
"""
Expand Down
Loading

0 comments on commit 14399dc

Please sign in to comment.