{"id":30237671,"url":"https://github.com/devopshq/artifactory","last_synced_at":"2025-08-15T02:07:28.430Z","repository":{"id":39422971,"uuid":"65551466","full_name":"devopshq/artifactory","owner":"devopshq","description":"dohq-artifactory: a Python client for Artifactory","archived":false,"fork":false,"pushed_at":"2025-04-08T08:42:18.000Z","size":903,"stargazers_count":292,"open_issues_count":27,"forks_count":158,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-08-08T03:02:56.454Z","etag":null,"topics":["aql","artifactory","artifactory-query-language","artifactory-saas","hacktoberfest","jfrog","jfrog-artifactory","python","python-library"],"latest_commit_sha":null,"homepage":"https://devopshq.github.io/artifactory/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devopshq.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-08-12T12:25:43.000Z","updated_at":"2025-07-24T14:35:15.000Z","dependencies_parsed_at":"2024-02-10T05:27:49.180Z","dependency_job_id":"06f29783-d1b8-4b3f-93a9-c6cd9dc445af","html_url":"https://github.com/devopshq/artifactory","commit_stats":{"total_commits":523,"total_committers":73,"mean_commits":7.164383561643835,"dds":0.8030592734225621,"last_synced_commit":"047377c2b95cf2b70a930ff63b711b973fe1529d"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/devopshq/artifactory","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devopshq%2Fartifactory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devopshq%2Fartifactory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devopshq%2Fartifactory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devopshq%2Fartifactory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devopshq","download_url":"https://codeload.github.com/devopshq/artifactory/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devopshq%2Fartifactory/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269673533,"owners_count":24457146,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aql","artifactory","artifactory-query-language","artifactory-saas","hacktoberfest","jfrog","jfrog-artifactory","python","python-library"],"created_at":"2025-08-15T02:07:24.634Z","updated_at":"2025-08-15T02:07:28.413Z","avatar_url":"https://github.com/devopshq.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Python interface library for JFrog Artifactory ![](https://img.shields.io/badge/status-supported-green.svg)\n\n\n[![docs](https://img.shields.io/readthedocs/pip.svg)][1]\n[![dohq-artifactory build Status](https://github.com/devopshq/artifactory/workflows/CI/badge.svg?branch=master)][2]\n[![dohq-artifactory on PyPI](https://img.shields.io/pypi/v/dohq-artifactory.svg)][3] \n[![dohq-artifactory license](https://img.shields.io/pypi/l/dohq-artifactory.svg)][4]\n\n`dohq-artifactory` is a live python package for [JFrog Artifactory][5]. This module is intended to serve as a logical \ndescendant of [pathlib][6], and it implements everything as closely as \npossible to the origin with few exceptions. Current module was forked from outdated \n[parallels/artifactory][7] and supports all functionality from the original \npackage.\n\n[1]: https://devopshq.github.io/artifactory/\n[2]: https://github.com/devopshq/artifactory/actions/workflows/ci.yml\n[3]: https://pypi.python.org/pypi/dohq-artifactory\n[4]: https://github.com/devopshq/artifactory/blob/master/LICENSE\n[5]: https://www.jfrog.com/confluence/display/JFROG/JFrog+Artifactory\n[6]: https://docs.python.org/3/library/pathlib.html\n[7]: https://github.com/parallels/artifactory\n\n# Tables of Contents\n\n\u003c!-- toc --\u003e\n\n- [Install](#install)\n- [Usage](#usage)\n  * [Authentication](#authentication)\n  * [Artifactory SaaS](#artifactory-saas)\n  * [Walking Directory Tree](#walking-directory-tree)\n  * [Downloading Artifacts](#downloading-artifacts)\n  * [Downloading Artifacts in chunks](#downloading-artifacts-in-chunks)\n  * [Downloading Artifacts folder as archive](#downloading-artifacts-folder-as-archive)\n  * [Uploading Artifacts](#uploading-artifacts)\n  * [Copy Artifacts](#copy-artifacts)\n  * [Move Artifacts](#move-artifacts)\n  * [Remove Artifacts](#remove-artifacts)\n  * [Artifact properties](#artifact-properties)\n  * [Repository Scheduled Replication Status](#repository-scheduled-replication-status)\n  * [Artifactory Query Language](#artifactory-query-language)\n  * [Artifact Stat](#artifact-stat)\n    + [File/Folder Statistics](#filefolder-statistics)\n    + [Get Download Statistics](#get-download-statistics)\n  * [Promote Docker image](#promote-docker-image)\n  * [Builds](#builds)\n  * [Exception handling](#exception-handling)\n- [Admin area](#admin-area)\n  * [User](#user)\n    + [API Keys](#api-keys)\n  * [Group](#group)\n    + [Internal](#internal)\n    + [GroupLDAP](#groupldap)\n  * [RepositoryLocal](#repositorylocal)\n  * [RepositoryVirtual](#repositoryvirtual)\n  * [RepositoryRemote](#repositoryremote)\n  * [Project](#project)\n  * [Get repository of any type](#get-repository-of-any-type)\n  * [Iterate over repository artifacts](#iterate-over-repository-artifacts)\n  * [Access repository child item](#access-repository-child-item)\n  * [Search for certain package artifacts](#search-for-certain-package-artifacts)\n  * [PermissionTarget](#permissiontarget)\n  * [Token](#token)\n  * [Common](#common)\n- [Advanced](#advanced)\n  * [Session](#session)\n  * [SSL Cert Verification Options](#ssl-cert-verification-options)\n  * [Timeout on requests](#timeout-on-requests)\n  * [Logging](#logging)\n  * [Global Configuration File](#global-configuration-file)\n- [Contribute](#contribute)\n- [Advertising](#advertising)\n\n\u003c!-- tocstop --\u003e\n\n# Install #\nUpgrade/install to the newest available version:\n```bash\npip install dohq-artifactory --upgrade\n```\nInstall latest development version (Warning! It may contains some errors!):\n```bash\npip install dohq-artifactory --upgrade --pre\n```\nOr specify version, e.g.:\n```bash\npip install dohq-artifactory==0.5.dev243\n```\n\n# Usage\n## Authentication ##\n\n`dohq-artifactory` supports these ways of authentication:\n\n- Username and password (or [API KEY](https://www.jfrog.com/confluence/display/RTF/Updating+Your+Profile#UpdatingYourProfile-APIKey)) to access restricted resources, you can pass ```auth``` parameter to ArtifactoryPath.\n- [API KEY](https://www.jfrog.com/confluence/display/RTF/Updating+Your+Profile#UpdatingYourProfile-APIKey) can pass with `apikey` parameter.\n- [Access Token](https://www.jfrog.com/confluence/display/JFROG/Access+Tokens#AccessTokens-UsingTokens) can pass with `token` parameter.\n\n```python\nfrom artifactory import ArtifactoryPath\n\n# API_KEY\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\", apikey=\"MY_API_KEY\"\n)\n\n# Access Token\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\", token=\"MY_ACCESS_TOKEN\"\n)\n\n# User and password OR API_KEY\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\",\n    auth=(\"USERNAME\", \"PASSWORD or API_KEY\"),\n)\n\n# Other authentication types\nfrom requests.auth import HTTPDigestAuth\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\",\n    auth=(\"USERNAME\", \"PASSWORD\"),\n    auth_type=HTTPDigestAuth,\n)\n\nfrom requests.auth import HTTPBasicAuth\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\",\n    auth=(\"USERNAME\", \"PASSWORD\"),\n    auth_type=HTTPBasicAuth,\n)\n\n# Load username, password from global config if exist:\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\",\n    auth_type=HTTPBasicAuth,\n)\n\npath.touch()\n```\n\n## Artifactory SaaS\nIf you use Artifactory SaaS solution - use `ArtifactorySaaSPath` class.  \nSaaS supports all methods and authentication types as `ArtifactoryPath`. We have to use other class, because as a SaaS \nservice, the URL is different from an on-prem installation and the REST API endpoints.\n```python\nfrom artifactory import ArtifactorySaaSPath\n\npath = ArtifactorySaaSPath(\n    \"https://myartifactorysaas.jfrog.io/myartifactorysaas/folder/path.xml\",\n    apikey=\"MY_API_KEY\",\n)\n```\n\n\n## Walking Directory Tree ##\n\nGet directory listing:\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\"http://repo.jfrog.org/artifactory/gradle-ivy-local\")\nfor p in path:\n    print(p)\n```\n\nFind all `.gz` files in current dir, recursively:\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\"http://repo.jfrog.org/artifactory/distributions/org/\")\n\nfor p in path.glob(\"**/*.gz\"):\n    print(p)\n```\n\n## Downloading Artifacts ##\n\nDownload artifact to a local filesystem:\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz\"\n)\n\nwith path.open() as fd, open(\"tomcat.tar.gz\", \"wb\") as out:\n    out.write(fd.read())\n```\n\n## Downloading Artifacts in chunks ##\n\nDownload artifact to the local filesystem using chunks (in bytes) to prevent loading the entire response into memory at once.\nThis can help with getting big files or resolve [known issue](https://github.com/devopshq/artifactory/issues/135)\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz\"\n)\n\n# download by providing path to out file and use default chunk 1024\npath.writeto(out=\"tomcat.tar.gz\")\n\n# download and suppress progress messages\npath.writeto(out=\"tomcat2.tar.gz\", progress_func=None)\n\n# download by providing out as file object and specify chunk size\nwith open(\"tomcat3.tar.gz\", \"wb\") as out:\n    path.writeto(out, chunk_size=256)\n\n\n# download and use custom print function\ndef custom_print(bytes_now, total, custom):\n    \"\"\"\n    Custom function that accepts first two arguments as [int, int] in its signature\n    \"\"\"\n    print(bytes_now, total, custom)\n\n\n# since writeto requires [int, int] in its signature, all custom arguments you have to provide via lambda function or\n# similar methods\npath.writeto(\n    out=\"tomcat5.tar.gz\",\n    progress_func=lambda x, y: custom_print(x, y, custom=\"test\"),\n)\n```\n\n\n## Downloading Artifacts folder as archive ##\nDownload artifact folder to a local filesystem as archive (supports zip/tar/tar.gz/tgz)\nAllows to specify archive type and request checksum for the folder\n\nNote: Archiving should be enabled on the server!\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my_url:8080/artifactory/my_repo/winx64/aas\", auth=(\"user\", \"password\")\n)\n\nwith path.archive(archive_type=\"zip\", check_sum=False).open() as archive:\n    with open(r\"D:\\target.zip\", \"wb\") as out:\n        out.write(archive.read())\n\n# download folder archive in chunks\npath.archive().writeto(out=\"my.zip\", chunk_size=100 * 1024)\n```\n\n## Uploading Artifacts ##\n\nDeploy a regular file ```myapp-1.0.tar.gz```. This method by default will calculate all available checksums and attach\nthem to the file\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\"\n)\npath.mkdir()\n\npath.deploy_file(\"./myapp-1.0.tar.gz\")\n```\n\nDeploy artifacts from archive: this will automatically extract the contents of the archive on the server preserving \nthe archive's paths\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\"\n)\npath.mkdir()\n\npath.deploy_file(\"./myapp-1.0.tar.gz\", explode_archive=True)\n```\n\nAtomically deploy artifacts from archive: this will automatically extract the contents of the archive on the server \npreserving the archive's paths. This is primarily useful when you want Artifactory to see all the artifacts at once, \ne.g., for indexing purposes.\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\"\n)\npath.mkdir()\n\npath.deploy_file(\n    \"./myapp-1.0.tar.gz\", explode_archive=True, explode_archive_atomic=True\n)\n```\n\n[Deploy artifact by checksum](https://www.jfrog.com/confluence/display/RTF6X/Artifactory+REST+API#ArtifactoryRESTAPI-DeployArtifactbyChecksum): deploy an artifact to the specified destination by checking if the artifact\ncontent already exists in Artifactory. If Artifactory already contains a user\nreadable artifact with the same checksum the artifact content is copied over to\nthe new location without requiring content transfer.\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\"http://my-artifactory/artifactory/my_repo/foo\")\nsha1 = \"1be5d2dbe52ddee96ef2d17d354e2be0a155a951\"\nsha256 = \"00bbf80ccca376893d60183e1a714e707fd929aea3e458f9ffda60f7ae75cc51\"\n\n# If you don't know sha value, you can calculate it via\n# sha1 = artifactory.sha1sum(\"local_path_of_your_file\")\n# or\n# sha256 = artifactory.sha256sum(\"local_path_of_your_file\")\n\n# Each of the following 4 methods works fine if the artifact content already\n# exists in Artifactory.\npath.deploy_by_checksum(sha1=sha1)\n\n# deploy by sha1 via checksum parameter\npath.deploy_by_checksum(checksum=sha1)\n\n# deploy by sha256 via sha256 parameter\npath.deploy_by_checksum(sha256=sha256)\n\n# deploy by sha256 via checksum parameter\npath.deploy_by_checksum(checksum=sha256)\n```\n\nDeploy a debian package ```myapp-1.0.deb``` to an ```existent``` folder\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\"http://my-artifactory/artifactory/ubuntu-local/pool/\")\npath.deploy_deb(\n    \"./myapp-1.0.deb\", distribution=\"trusty\", component=\"main\", architecture=\"amd64\"\n)\n```\n\nDeploy a debian package ```myapp-1.0.deb``` to a ```non-existent``` folder\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/ubuntu-local/pool/myapp-1.0.deb\"\n)\npath.deploy_deb(\n    \"./myapp-1.0.deb\", distribution=\"trusty\", component=\"main\", architecture=\"amd64\"\n)\n\n# if you want to set multiple values you can use list to set them\npath.deploy_deb(\n    \"./myapp-1.0.deb\",\n    distribution=[\"dist1\", \"dist2\"],\n    component=\"main\",\n    architecture=[\"amd64\", \"i386\"],\n)\n```\n\n## Copy Artifacts\nCopy artifact from this path to destination.\nIf files are on the same instance of artifactory, lightweight (local)\ncopying will be attempted.\n\nThe suppress_layouts parameter, when set to `True`, will allow artifacts\nfrom one path to be copied directly into another path without enforcing\nrepository layouts. The default behaviour is to copy to the repository\nroot, but remap the [org], [module], [baseVer], etc. structure to the\ntarget repository.\n\nFor example, we have a builds repository using the default maven2\nrepository where we publish our builds, and we also have a published\nrepository where a directory for production and a directory for\nstaging environments should hold the current promoted builds. How do\nwe copy the contents of a build over to the production folder?\n\n```python\nfrom artifactory import ArtifactoryPath\n\nsource = ArtifactoryPath(\"http://example.com/artifactory/builds/product/product/1.0.0/\")\ndest = ArtifactoryPath(\"http://example.com/artifactory/published/production/\")\n\n\"\"\"\nUsing copy with the default, suppress_layouts=False, the artifacts inside\nbuilds/product/product/1.0.0/ will not end up in the published/production\npath as we intended, but rather the entire structure product/product/1.0.0\nis placed in the destination repo.\n\"\"\"\n\nsource.copy(dest)\nfor p in dest:\n    print(p)\n# http://example.com/artifactory/published/production/foo-0.0.1.gz\n# http://example.com/artifactory/published/production/foo-0.0.1.pom\n\nfor p in ArtifactoryPath(\n    \"http://example.com/artifactory/published/product/product/1.0.0.tar\"\n):\n    print(p)\n# http://example.com/artifactory/published/product/product/1.0.0/product-1.0.0.tar.gz\n# http://example.com/artifactory/published/product/product/1.0.0/product-1.0.0.tar.pom\n\n\"\"\"\nUsing copy with suppress_layouts=True, the contents inside our source are copied\ndirectly inside our dest as we intended.\n\"\"\"\n\nsource.copy(dest, suppress_layouts=True)\nfor p in dest:\n    print(p)\n\"\"\"\nhttp://example.com/artifactory/published/production/foo-0.0.1.gz\nhttp://example.com/artifactory/published/production/foo-0.0.1.pom\nhttp://example.com/artifactory/published/production/product-1.0.0.tar.gz\nhttp://example.com/artifactory/published/production/product-1.0.0.tar.pom\n\"\"\"\n\n# you can use dry run just to check if command will succeed without real change, adds debug message\nsource.copy(dest, dry_run=True)\n```\n\n## Move Artifacts\nMove artifact from this path to destination.\n\nThe suppress_layouts parameter, when set to `True`, will allow artifacts\nfrom one path to be copied directly into another path without enforcing\nrepository layouts. The default behaviour is to copy to the repository\nroot, but remap the [org], [module], [baseVer], etc. structure to the\ntarget repository.\n\n```python\nfrom artifactory import ArtifactoryPath\n\nsource = ArtifactoryPath(\"http://example.com/artifactory/builds/product/product/1.0.0/\")\ndest = ArtifactoryPath(\"http://example.com/artifactory/published/production/\")\n\nsource.move(dest)\n\n# you can use dry run just to check if command will succeed without real change, adds debug message\nsource.move(dest, dry_run=True)\n```\n\n## Remove Artifacts\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz\"\n)\n\nif path.exists():\n    path.unlink()\n```\n\n## Artifact properties ##\nYou can get and set (or remove) properties from artifact.\nFollowing example shows how to manage properties and property sets\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz\"\n)\n\n# Get properties\nproperties = path.properties\nprint(properties)\n\n# Update a property or add if does not exist\nproperties[\"qa\"] = \"tested\"\npath.properties = properties\n\n# add/replace set of properties\nnew_props = {\n    \"test\": [\"test_property\"],\n    \"time\": [\"2018-01-16 12:17:44.135143\"],\n    \"addthis\": [\"addthis\"],\n}\npath.properties = new_props\n\n# Remove properties\nproperties.pop(\"release\")\npath.properties = properties\n```\n\n## Repository Scheduled Replication Status ##\nReturns the status of scheduled  cron-based replication jobs define via the Artifactory UI on repositories.\nSupported by local, local-cached and remote repositories.\n\nNotes: Requires Artifactory Pro\n\nSecurity: Requires a user with 'read' permission (can be anonymous)\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"https://repo.jfrog.org/artifactory/repo1-cache/archetype-catalog.xml\"\n)\n\nrep_status = path.replication_status\nprint(\"status: \", rep_status[\"status\"])\n```\n\n## Artifactory Query Language\nYou can use [Artifactory Query Language](https://www.jfrog.com/confluence/display/RTF/Artifactory+Query+Language) in python.\n\n```python\nfrom artifactory import ArtifactoryPath\n\narti_path = ArtifactoryPath(\n    \"http://my-artifactory/artifactory\"\n)  # path to artifactory, NO repo\n\n# dict support\n# Send query:\n# items.find({\"repo\": \"myrepo\"})\nartifacts = arti_path.aql(\"items.find\", {\"repo\": \"myrepo\"})\n\n# list support.\n# Send query:\n# items.find().include(\"name\", \"repo\")\nartifacts = arti_path.aql(\"items.find()\", \".include\", [\"name\", \"repo\"])\n\n#  support complex query\n# Example 1\n# items.find(\n#     {\n#         \"$and\": [\n#             {\"repo\": {\"$eq\": \"repo\"}},\n#             {\"$or\": [{\"path\": {\"$match\": \"*path1\"}}, {\"path\": {\"$match\": \"*path2\"}}]},\n#         ]\n#     }\n# )\naqlargs = [\n    \"items.find\",\n    {\n        \"$and\": [\n            {\"repo\": {\"$eq\": \"repo\"}},\n            {\n                \"$or\": [\n                    {\"path\": {\"$match\": \"*path1\"}},\n                    {\"path\": {\"$match\": \"*path2\"}},\n                ]\n            },\n        ]\n    },\n]\n\n# artifacts_list contains raw data (list of dict)\n# Send query\nartifacts_list = arti_path.aql(*aqlargs)\n\n# Example 2\n# The query will find all items in repo docker-prod that are of type file and were created after timecode. The\n# query will only display the fields \"repo\", \"path\" and \"name\" and will sort the result ascendingly by those fields.\n# items.find(\n#     {\n#         \"$or\": [{\"repo\": \"docker-prod\"}],\n#         \"type\": \"file\",\n#         \"created\": {\"$gt\": \"2019-07-10T19:20:30.45+01:00\"},\n#     }\n# ).include(\"repo\", \"path\", \"name\",).sort({\"$asc\": [\"repo\", \"path\", \"name\"]})\naqlargs = [\n    \"items.find\",\n    {\n        \"$and\": [\n            {\"repo\": \"docker-prod\"},\n            {\"type\": \"file\"},\n            {\"created\": {\"$gt\": \"2019-07-10T19:20:30.45+01:00\"}},\n        ]\n    },\n    \".include\",\n    [\"repo\", \"path\", \"name\", \"type\"],\n    \".sort\",\n    {\"$asc\": [\"repo\", \"path\", \"name\"]},\n]\nartifacts_list = arti_path.aql(*aqlargs)\n\n# You can convert to pathlib object:\nartifact_pathlib = map(arti_path.from_aql, artifacts_list)\nartifact_pathlib_list = list(map(arti_path.from_aql, artifacts_list))\n```\n\n\n## Artifact Stat\n### File/Folder Statistics\nYou can get hash (`md5`, `sha1`, `sha256`), creator, create date, and change date:\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz\"\n)\n\n# Get FileStat\nstat = path.stat()\nprint(stat)\nprint(stat.ctime)\nprint(stat.mtime)\nprint(stat.created_by)\nprint(stat.modified_by)\nprint(stat.mime_type)\nprint(stat.size)\nprint(stat.sha1)\nprint(stat.sha256)\nprint(stat.md5)\nprint(stat.is_dir)\nprint(stat.children)\nprint(stat.repo)\n```\n\n### Get Download Statistics\nInformation about number of downloads, user that last downloaded and date of last download\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://repo.jfrog.org/artifactory/distributions/org/apache/tomcat/apache-tomcat-7.0.11.tar.gz\"\n)\n\n# Get FileStat\ndownload_stat = path.download_stats()\nprint(download_stat)\nprint(download_stat.last_downloaded)\nprint(download_stat.last_downloaded_by)\nprint(download_stat.download_count)\nprint(download_stat.remote_download_count)\nprint(download_stat.remote_last_downloaded)\nprint(download_stat.uri)\n```\n\n## Promote Docker image\nPromotes a Docker image in a registry to another registry.\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\"http://example.com/artifactory\")\n\npath.promote_docker_image(\"docker-staging\", \"docker-prod\", \"my-application\", \"0.5.1\")\n```\n\n## Builds\n```python\nfrom artifactory import ArtifactoryBuildManager\n\narti_build = ArtifactoryBuildManager(\n    \"https://repo.jfrog.org/artifactory\", project=\"proj_name\", auth=(\"admin\", \"admin\")\n)\n\n# Get all builds\nall_builds = arti_build.builds\nprint(all_builds)\n\n# Build Runs\nbuild1 = all_builds[0]\nall_runs = build1.runs\nprint(all_runs)\n\n# Build Info\nbuild_number1 = all_runs[0]\nprint(build_number1.info)\n\n# Builds Diff\n\"\"\"\n  Compare a build artifacts/dependencies/environment with an older build to see what \n  has changed (new artifacts added, old dependencies deleted etc).  \n\"\"\"\nprint(build_number1.diff(3))\n\n\n# Build Promotion\n\"\"\"\n  Change the status of a build, optionally moving or copying the build's artifacts and its dependencies \n  to a target repository and setting properties on promoted artifacts.  \n  All artifacts from all scopes are included by default while dependencies are not. Scopes are additive (or)\n\"\"\"\n\nbuild_number1.promote(\n    ci_user=\"admin\",\n    properties={\"components\": [\"c1\", \"c3\", \"c14\"], \"release-name\": [\"fb3-ga\"]},\n)\n```\n\n## Exception handling\nExceptions in this library are represented by `dohq_artifactory.exception.ArtifactoryException` or by `OSError`\nIf exception was caused by HTTPError you can always drill down the root cause by using following example:\n```python\nfrom artifactory import ArtifactoryPath\nfrom dohq_artifactory.exception import ArtifactoryException\n\npath = ArtifactoryPath(\n    \"http://my_arti:8080/artifactory/installer/\", auth=(\"wrong_user\", \"wrong_pass\")\n)\n\ntry:\n    path.stat()\nexcept ArtifactoryException as exc:\n    print(exc)  # clean artifactory error message\n    # \u003e\u003e\u003e Bad credentials\n    print(\n        exc.__cause__\n    )  # HTTP error that triggered exception, you can use this object for more info\n    # \u003e\u003e\u003e 401 Client Error: Unauthorized for url: http://my_arti:8080/artifactory/installer/\n```\n\n# Admin area\nYou can manipulate with user\\group\\repository and permission. First, create `ArtifactoryPath` object without a repository\n```python\nfrom artifactory import ArtifactoryPath\n\nartifactory_ = ArtifactoryPath(\n    \"https://artifactory.example.com/artifactory\", auth=(\"user\", \"password\")\n)\n```\n\nYou can see detailed use of `AdminObject` in file `.\\tests\\integration\\test_admin.py`\n## User\n```python\n# Find or create first way\nfrom dohq_artifactory import generate_password, User\n\nuser = artifactory_.find_user(\"username\")\nif user is None:\n    # User does not exist\n    user = User(\n        artifactory_, \"username\", \"username@example.com\", password=generate_password()\n    )\n    user.create()\n\n# Find or create - second way\nuser = User(artifactory_, \"username\")\nif not user.read():  # Return True if user exist\n    # User does not exist\n    user = User(\n        artifactory_, \"username\", \"username@example.com\", password=generate_password()\n    )\n    user.create()\n\n\n# Add to group\nuser.add_to_group(\"byname\")\n\ngroup = artifactory_.find_group(\"groupname\")\nuser.add_to_group(group)\nuser.update()  # Don't forget update :)\n\nenc_pwd = user.encrypted_password\n\n# You can re-read from Artifactory\nuser.read()\n\nuser.delete()\n```\n\n### API Keys\n```python\nfrom dohq_artifactory import User\n\nuser = User(artifactory_, \"username\")\n\n# create an API key\nuser.api_key.create()\n\n# get API key\nuser.api_key.get()\n# or using str() method\nmy_key = str(user.api_key)\n# or using repr method\nprint(user.api_key)\n\n# regenerate API key if one already exists\nuser.api_key.regenerate()\n\n# remove API key for current user\nuser.api_key.revoke()\n\n# remove all API keys in system, only if user has admin rights\nuser.api_key.revoke_for_all_users()\n```\n\n## Group\n### Internal\n\n```python\n# Find\nfrom dohq_artifactory import generate_password, Group\n\ngroup = artifactory_.find_group(\"groupname\")\n\n# Create\nif group is None:\n    group = Group(artifactory_, \"groupname\")\n    group.create()\n\n# You can re-read from Artifactory\ngroup.read()\n\n# You can add multiple users at once to Group\ngroup.users = [\"admin\", \"anonymous\"]\ngroup.create()\n\n# You can remove all users from a Group\ngroup.users = []\ngroup.create()\n\ngroup.delete()\n```\n\n### GroupLDAP\nhttps://www.jfrog.com/confluence/display/RTF/LDAP+Groups#LDAPGroups-UsingtheRESTAPI\n\n```python\n# Full DN path in artifactory\ndn = \"cn=R.DevOps.TestArtifactory,ou=Groups,dc=example,dc=com\"\nattr = \"ldapGroupName=r.devops.testartifactory;groupsStrategy=STATIC;groupDn={}\".format(\n    dn\n)\ntest_group = GroupLDAP(\n    artifactory=artifactory_, name=\"r.devops.testartifactory\", realm_attributes=attr\n)\ntest_group.create()\n```\n\n## RepositoryLocal\n```python\n# Find\nfrom dohq_artifactory import generate_password, RepositoryLocal\n\nrepo = artifactory_.find_repository_local(\"reponame\")\n\n# Create\nif repo is None:\n    # or RepositoryLocal.PYPI, RepositoryLocal.NUGET, etc\n    repo = RepositoryLocal(artifactory_, \"reponame\", packageType=RepositoryLocal.DEBIAN)\n    repo.create()\n\n# You can re-read from Artifactory\nrepo.read()\n\nrepo.delete()\n```\n\n## RepositoryVirtual\n```python\n# Find\nfrom dohq_artifactory import RepositoryVirtual\n\nrepo = artifactory_.find_repository_virtual(\"pypi.all\")\n\n# Create\nif repo is None:\n    # or RepositoryVirtual.PYPI, RepositoryLocal.NUGET, etc\n    repo = RepositoryVirtual(\n        artifactory_,\n        \"pypi.all\",\n        repositories=[\"pypi.snapshot\", \"pypi.release\"],\n        packageType=RepositoryVirtual.PYPI,\n    )\n    repo.create()\n\n# You can re-read from Artifactory\nrepo.read()\n\nlocal_repos = repo.repositories  # return List\u003cRepositoryLocal\u003e\n\nrepo.delete()\n```\n\n## RepositoryRemote\n```python\n# Find\nfrom dohq_artifactory import RepositoryRemote\n\nrepo = artifactory_.find_repository_virtual(\"pypi.all\")\n\n# Create\nif repo is None:\n    # or RepositoryRemote.PYPI, RepositoryRemote.NUGET, etc\n    repo = RepositoryRemote(\n        artifactory_,\n        \"pypi.all\",\n        url=\"https://files.pythonhosted.org\",\n        packageType=RepositoryVirtual.PYPI,\n    )\n    repo.create()\n\n# You can re-read from Artifactory\nrepo.read()\n\nrepo.delete()\n```\n\n## Project\n```python\n# Find\nfrom artifactory import ArtifactoryPath\nfrom dohq_artifactory import Project\n\nartifactory_ = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\", token=\"MY_TOKEN\"\n)\nproject = artifactory_.find_project(\"t1k1\")\n\n# Create\nif project is None:\n    project = Project(artifactory_, \"t1k1\", \"t1k1_display_name\")\n    project.create()\n\n# You can re-read from Artifactory\nproject.read()\n\nproject.delete()\n```\n\n## Get repository of any type\n```python\n# Find any repo by name\nrepo = artifactory_.find_repository(\"pypi.all\")\n```\n\n## Iterate over repository artifacts\n```python\n# Get repo\nrepo = artifactory_.find_repository(\"pypi.all\")\n\n# Iterate over repo\nfor artifact in repo:\n    print(artifact)\n    print(artifact.properties)\n\n# Result:\n# http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.0-py3.whl\n# {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.0\"]}\n# http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.1.dev5-py3.whl\n# {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.1.dev5\"]}\n# http://my.artifactory.com/artifactory/pypi.all/other_package/other_package-0.0.1-py3.whl\n# {\"pypi.name\": [\"other_package\"], \"pypy_version\": [\"0.0.1\"]}\n# ...\n```\n\n## Access repository child item\nRepo can bee accessed just like any other `ArtifactPath`:\n```python\n# Get repo\nrepo = artifactory_.find_repository(\"pypi.all\")\n\n# Access a folder within the repo\npackage = repo / \"my_package\"\n\n# Result:\n# ArtifactPath('http://my.artifactory.com/artifactory/pypi.all/my_package')\n\n# Access a file within the repo\npackage = repo / \"my_package\" / \"my_artifact.tar.gz\"\n\n# Result:\n# ArtifactPath('http://my.artifactory.com/artifactory/pypi.all/my_package/my_artifact.tar.gz')\n```\n\n## Search for certain package artifacts\n```python\n# Get repo\nrepo = artifactory_.find_repository(\"pypi.all\")\n\n# Will generate and perform AQL query for getting artifacts by path or name\nfor artifacts in repo[\"my_package\"]:\n    print(artifact)\n    print(artifact.properties)\n\n# Result:\n# http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.0-py3.whl\n# {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.0\"], ...}\n# http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.1.dev5-py3.whl\n# {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.1.dev5\"], ...}\n# http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.1.0-py3.whl\n# {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.1.0\"], ...}\n# ...\n\n# Using partial match\nfor artifacts in repo[\"my_pack*\"]:\n    print(artifact)\n    print(artifact.properties)\n\n# Result:\n# http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.0-py3.whl\n# {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.0\"], ...}\n# http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.1.dev5-py3.whl\n# {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.1.dev5\"], ...}\n# http://my.artifactory.com/artifactory/pypi.all/my_package_new_/my_package_new-0.0.1-py3.whl\n# {\"pypi.name\": [\"my_package_new\"], \"pypy_version\": [\"0.0.1\"], ...}\n# ...\n```\n\nSome types of repositories support specific ways of searching artifacts.\n  * PyPi\n\n    ```python\n    # Get repo\n    repo = artifactory_.find_repository(\"pypi.all\")\n\n    # Get artifacts by package name\n    for artifacts in repo[\"my_package\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.0-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.0\"], ...}\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.1.dev5-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.1.dev5\"], ...}\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.1.0-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.1.0\"], ...}\n    # ...\n\n    # Get artifacts by specific version\n    for artifacts in repo[\"my_package==1.0.0\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.0-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.0\"], ...}\n\n    # Using other pip operators (result should be additionaly checked!)\n    for artifacts in repo[\"my_package!=1.0.0\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.1.dev5-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.1.dev5\"], ...}\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.1.0-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.1.0\"], ...}\n    # ...\n\n\n    # In case of using \u003e or \u003c operators, the result should be additionaly checked\n    # because Artifactory compares strings, not versions\n    for artifacts in repo[\"my_package\u003e=1.0.0\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.0.1.dev5-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.1.dev5\"], ...}\n    # http://my.artifactory.com/artifactory/pypi.all/my_package/my_package-1.1.0-py3.whl\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.1.0\"], ...}\n    # ...\n    ```\n\n  * Docker\n\n    ```python\n    # Get repo\n    repo = artifactory_.find_repository(\"docker.all\")\n\n    # Get artifacts by image name\n    for artifacts in repo[\"my_image\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/docker.all/my_image/latest/manifest.json\n    # {\"docker.repoName\": [\"my_image\"], \"docker.manifest\": [\"latest\"], ...}\n    # http://my.artifactory.com/artifactory/docker.all/my_image/1.0.0/manifest.json\n    # {\"docker.repoName\": [\"my_image\"], \"docker.manifest\": [\"1.0.0\"], ...}\n    # http://my.artifactory.com/artifactory/docker.all/my_image/1.1.0/manifest.json\n    # {\"docker.repoName\": [\"my_image\"], \"docker.manifest\": [\"1.1.0\"], ...}\n    # ...\n\n    # Get artifacts by specific version\n    for artifacts in repo[\"my_image:1.0.0\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/docker.all/my_image/1.0.0/manifest.json\n    # {\"docker.repoName\": [\"my_image\"], \"docker.manifest\": [\"1.0.0\"], ...}\n    # ...\n\n    for artifacts in repo[\"my_image:latest\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/docker.all/my_image/latest/manifest.json\n    # {\"docker.repoName\": [\"my_image\"], \"docker.manifest\": [\"latest\"], ...}\n    # ...\n\n    # Partial search\n    for artifacts in repo[\"my_package:*dev*\"]:\n        print(artifact)\n        print(artifact.properties)\n    # http://my.artifactory.com/artifactory/docker.all/my_image/dev/manifest.json\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"dev\"]}\n    # http://my.artifactory.com/artifactory/docker.all/my_image/1.0.1-dev5/manifest.json\n    # {\"pypi.name\": [\"my_package\"], \"pypy_version\": [\"1.0.1-dev5\"]}\n    # ...\n    ```\n\n  * Maven\n\n    ```python\n    # Get repo\n    repo = artifactory_.find_repository(\"maven.all\")\n\n    # Get artifacts by group name\n    for artifacts in repo[\"my.group\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/maven-metadata.xml\n    # ...\n    # http://my.artifactory.com/artifactory/maven.all/my/group/another_package/1.2.3/maven-metadata.xml\n    # ...\n\n    # Get artifacts by group and package name\n    for artifacts in repo[\"my.group:package\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/maven-metadata.xml\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0-source.jar\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0-javadoc.jar\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0.pom\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0.jar\n    # {\"build.number\": [\"123\"], \"build.name\": [\"1.0.0\"], ...}\n\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.1/maven-metadata.xml\n    # ...\n\n    # Get artifacts by group, package name and version\n    for artifacts in repo[\"my.group:package:1.0.0\"]:\n        print(artifact)\n        print(artifact.properties)\n\n    # Result:\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/maven-metadata.xml\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0-source.jar\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0-javadoc.jar\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0.pom\n    # http://my.artifactory.com/artifactory/maven.all/my/group/package/1.0.0/package-1.0.0.jar\n    # {\"build.number\": [\"123\"], \"build.name\": [\"1.0.0\"], ...}\n    ```\n\n## PermissionTarget\nDocs: https://www.jfrog.com/confluence/display/RTF/Managing+Permissions\n\nSupports these roles:\n- `PermissionTarget.ROLE_ADMIN` = `ADMIN + DELETE + DEPLOY + ANNOTATE + READ`\n- `PermissionTarget.ROLE_DELETE` = `DELETE + DEPLOY + ANNOTATE + READ`\n- `PermissionTarget.ROLE_DEPLOY` = `DEPLOY + ANNOTATE + READ`\n- `PermissionTarget.ROLE_ANNOTATE` = `ANNOTATE + READ`\n- `PermissionTarget.ROLE_READ` = `READ`\n\nAnd for more modular control:\n- `PermissionTarget.ADMIN` - Allows changing the permission settings for other users on this permission target\n- `PermissionTarget.DELETE` - Allows deletion or overwriting of artifacts\n- `PermissionTarget.DEPLOY` - Allows deploying artifacts and deploying to caches (i.e. populating caches with remote artifacts)\n- `PermissionTarget.ANNOTATE` - Allows annotating artifacts and folders with metadata and properties\n- `PermissionTarget.READ` - Allows reading and downloading of artifacts\n\n```python\nfrom dohq_artifactory import PermissionTarget\n\npermission = artifactory_.find_permission_target(\"rule\")\nif permission is None:\n    # Permission target does not exist\n    permission = PermissionTarget(\n        artifactory_,\n        \"rule\",\n        repositories=Object, # \u003cRepositiryLocal repo1\u003e, \u003cRepositiryLocal repo2\u003e\n        users=Object, # \u003cUser user1\u003e, \u003cUser user2\u003e\n        groups=Object, # \u003cGroup group1\u003e, \u003cGroup group2\u003e\n        includes_pattern=\"com.mycompany.myproject1.**,com.othercompany.**\"\n        excludes_pattern=\"com.othercompany.projectX.**,com.othercompany.projectY.**\"\n    )\n    permission.create()\n\n# See repositories, users or groups\npermission.repositories\n# Result:\n# \u003cRepositiryLocal repo1\u003e\n# \u003cRepositiryLocal repo2\u003e\n\npermission.users\n# Result:\n# \u003cUser user1\u003e\n# \u003cUser user2\u003e\n\npermission.groups\n# Result:\n# \u003cGroup group1\u003e\n# \u003cGroup group2\u003e\n\n# Add repo (string or Repository) object\npermission.add_repository(\"repo3\", \"repo4\")\npermission.add_repository(repo5_object)\n# Or remove\npermission.remove_repository(\"repo1\", \"repo2\")\n\n# Add user (string or User object) with specific permission\npermission.add_user(\"user3\", PermissionTarget.ROLE_ADMIN)\npermission.add_user(\n    user4_object, PermissionTarget.ROLE_READ + PermissionTarget.ROLE_WRITE\n)  # You can add sum of permissions\n\n# Or remove\npermission.remove_user(\"user1\", \"user2\")\n\n# Add group (string or Group object) with permission\npermission.add_group(\"group3\", PermissionTarget.ROLE_ADMIN)\npermission.add_group(\n    group4_object, PermissionTarget.ROLE_READ + PermissionTarget.ROLE_WRITE\n)  # You can add sum of permissions\n\n# Or remove\npermission.remove_group(\"group1\", \"group2\")\n\npermission.update()  # Update!!\n\npermission.repositories\n# Result:\n# \u003cRepositiryLocal repo3\u003e\n# \u003cRepositiryLocal repo4\u003e\n# \u003cRepositiryLocal repo5\u003e\n\npermission.users\n# Result:\n# \u003cUser user3\u003e\n# \u003cUser user4\u003e\n\npermission.groups\n# Result:\n# \u003cGroup group3\u003e\n# \u003cGroup group4\u003e\n```\n\n## Token\nhttps://www.jfrog.com/confluence/display/RTF5X/Access+Tokens#AccessTokens-RESTAPI\n```python\nfrom requests.auth import HTTPBasicAuth\nfrom artifactory import ArtifactoryPath\nfrom dohq_artifactory import Token\n\nsession = ArtifactoryPath(\n    \"https://artifactory_dns/artifactory\",\n    auth=(\"admin\", \"admin_password\"),\n    auth_type=HTTPBasicAuth,\n    verify=False,\n)\n\n# Read token for readers group\ngroup_name = \"readers\"\nscope = \"api:* member-of-groups:\" + group_name\ntoken = Token(session, scope=scope)\ntoken.read()\n\n# Create token for member of the readers\ngroup_name = \"readers\"\nscope = \"api:* member-of-groups:\" + group_name\nsubject = group_name\ntoken = Token(\n    session, scope=scope, username=subject, expires_in=31557600, refreshable=True\n)\nresponse = token.create()\n\nprint(\"Readonly token:\")\nprint(\"Username: \" + token.username)\nprint(\"Token: \" + token.token[\"access_token\"])\n```\n\n## Common\nAll `AdminObject`  support:\n```python\nuser = artifactory_.find_user(\"username\")\nprint(user.raw)  # JSON response from Artifactory\n\nnew_repo = RepositoryLocal(artifactory, \"reponame\")\n# If some key you can't find in object, you can use this:\nnew_repo.additional_params[\"property_sets\"] = [\"my\", \"properties_sets\"]\nnew_repo.create()\n\n# All object support CRUD operations:\nobj.read()  # Return True if user exist (and read from Artifactory), else return False\nobj.create()\nobj.update()\nobj.delete()\n\n# ArtifactoryPath have different find_ method:\nartifactory_.find_user(\"name\")\nartifactory_.find_group(\"name\")\nartifactory_.find_repository_local(\"name\")\nartifactory_.find_permission_target(\"name\")\nartifactory_.find_project(\"project_key\")\n```\n\n# Advanced\n\n## Session ##\n\nTo re-use the established connection, you can pass ```session``` parameter to ArtifactoryPath:\n\n```python\nfrom artifactory import ArtifactoryPath\nimport requests\n\nses = requests.Session()\nses.auth = (\"username\", \"password\")\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/my-path-1\", sesssion=ses\n)\npath.touch()\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/my-path-2\", sesssion=ses\n)\npath.touch()\n```\n\n\n## SSL Cert Verification Options ##\nSee [Requests - SSL verification](http://docs.python-requests.org/en/latest/user/advanced/#ssl-cert-verification) for more details.\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\"\n)\n```\n... is the same as\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\", verify=True\n)\n```\nSpecify a local cert to use as client side certificate\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\",\n    cert=\"/path_to_file/server.pem\",\n)\n```\nDisable host cert verification\n\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\", verify=False\n)\n```\n\n**Note:** If host cert verification is disabled, `urllib3` will throw a [InsecureRequestWarning](https://urllib3.readthedocs.org/en/latest/security.html#insecurerequestwarning).\nTo disable these warning, one needs to call `urllib3.disable_warnings()`.\n```python\nimport requests.packages.urllib3 as urllib3\n\nurllib3.disable_warnings()\n```\n\n## Timeout on requests ##\n\nThe library supports `timeout` argument in the same meaner as [requests does](https://requests.readthedocs.io/en/master/user/advanced/#timeouts)\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\"\n)\n```\n... is the same as\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\", timeout=None\n)\n```\nSet 5 seconds timeout to your requests after which it will be terminated:\n```python\nfrom artifactory import ArtifactoryPath\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/libs-snapshot-local/myapp/1.0\", timeout=5\n)\n```\n\n## Logging ##\nThe library can be configured to emit logging that will give you better insight into what it's doing.\nJust configure [logging](https://docs.python.org/3/library/logging.html) module in your python script. \nSimplest example to add debug messages to a console:\n```python\nimport logging\nfrom artifactory import ArtifactoryPath\n\nlogging.basicConfig()\n# set level only for artifactory module, if omitted, then global log level is used, eg from basicConfig\nlogging.getLogger(\"artifactory\").setLevel(logging.DEBUG)\n\npath = ArtifactoryPath(\n    \"http://my-artifactory/artifactory/myrepo/restricted-path\", apikey=\"MY_API_KEY\"\n)\n```\n\n\n## Global Configuration File ##\n\nArtifactory Python module also can specify all connection-related settings in a central file, given by environment\nvariable ```$DOHQ_ARTIFACTORY_PYTHON_CFG``` (default if not set: ```~/.artifactory_python.cfg```) that is read upon\nthe creation of first ```ArtifactoryPath``` object and is stored globally. For instance, you can specify per-instance\nsettings of authentication tokens, so that you won't need to explicitly pass ```auth``` parameter to ```ArtifactoryPath```.\n\nExample:\n\n```ini\n[DEFAULT]\nusername = nameforallinstances\n\n[http://artifactory-instance.com/artifactory]\npassword = ilikerandompasswords\nverify = false\n\n[another-artifactory-instance.com/artifactory]\npassword = @dmin\ncert = ~/mycert\n```\n\nWhether or not you specify ```http://``` or ```https://```, the prefix is not essential. The module will first try to \nlocate the best match and then try to match URLs without prefixes. So in the config, if you specify \n```https://my-instance.local``` and call ```ArtifactoryPath``` with ```http://my-instance.local```, it will still do \nthe right thing.\n\n\n# Contribute\n[About contributing and testing](docs/CONTRIBUTE.md)\n\n# Advertising\n- [artifactory-du](https://github.com/devopshq/artifactory-du) - estimate file space usage. Summarize disk usage in \n  JFrog Artifactory of the set of FILEs, recursively for directories.\n- [artifactory-cleanup](https://github.com/devopshq/artifactory-cleanup) - is an extended and flexible cleanup tool for JFrog Artifactory.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevopshq%2Fartifactory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevopshq%2Fartifactory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevopshq%2Fartifactory/lists"}