{"id":16993647,"url":"https://github.com/unbekanntes-pferd/dracoon-python-api","last_synced_at":"2025-03-17T09:30:39.171Z","repository":{"id":37604861,"uuid":"298758824","full_name":"unbekanntes-pferd/dracoon-python-api","owner":"unbekanntes-pferd","description":"A Python3 based package to connect with DRACOON API (async) - more infos on DRACOON here: https://dracoon.com","archived":false,"fork":false,"pushed_at":"2024-09-03T23:32:09.000Z","size":728,"stargazers_count":12,"open_issues_count":8,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-27T21:50:17.359Z","etag":null,"topics":["api","api-client","api-wrapper","dracoon","python","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/unbekanntes-pferd.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2020-09-26T07:12:09.000Z","updated_at":"2024-07-06T09:40:12.000Z","dependencies_parsed_at":"2024-01-23T10:16:28.054Z","dependency_job_id":"5f0b37c1-1a7b-4f75-babd-01f169651b9a","html_url":"https://github.com/unbekanntes-pferd/dracoon-python-api","commit_stats":{"total_commits":264,"total_committers":4,"mean_commits":66.0,"dds":0.1477272727272727,"last_synced_commit":"34b101edab3a68cb016609198c8563b711b121e4"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unbekanntes-pferd%2Fdracoon-python-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unbekanntes-pferd%2Fdracoon-python-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unbekanntes-pferd%2Fdracoon-python-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unbekanntes-pferd%2Fdracoon-python-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unbekanntes-pferd","download_url":"https://codeload.github.com/unbekanntes-pferd/dracoon-python-api/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243858307,"owners_count":20359253,"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","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":["api","api-client","api-wrapper","dracoon","python","python3"],"created_at":"2024-10-14T03:43:39.033Z","updated_at":"2025-03-17T09:30:38.573Z","avatar_url":"https://github.com/unbekanntes-pferd.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![dracoon tests](https://github.com/unbekanntes-pferd/dracoon-python-api/actions/workflows/testing.yml/badge.svg)](https://github.com/unbekanntes-pferd/dracoon-python-api/actions/workflows/testing.yml)\n  \n  \n  \u003ch3 align=\"center\"\u003eDRACOON-PYTHON-API\u003c/h3\u003e\n\n  \u003cp align=\"center\"\u003e\n    Python connector to DRACOON API\n    \u003cbr /\u003e\n    \u003ca href=\"https://github.com/unbekanntes-pferd/DRACOON-PYTHON-API\"\u003e\u003cstrong\u003eExplore the docs »\u003c/strong\u003e\u003c/a\u003e\n    \u003cbr /\u003e\n    \u003ca href=\"https://github.com/unbekanntes-pferd/DRACOON-PYTHON-API/issues\"\u003eReport Bug\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n\u003c!-- TABLE OF CONTENTS --\u003e\n## Table of Contents\n\n* [About the Project](#about-the-project)\n  * [Built With](#built-with)\n* [Getting Started](#getting-started)\n  * [Prerequisites](#prerequisites)\n  * [Installation](#installation)\n* [Usage](#usage)\n* [Roadmap](#roadmap)\n* [Contributing](#contributing)\n* [License](#license)\n\n\n\n\u003c!-- ABOUT THE PROJECT --\u003e\n## About The Project\n__Disclaimer: this is an unofficial repo and is not supported by DRACOON__\u003cbr\u003e\nThis package provides a wrapper for the DRACOON API including full crypto support. \nDRACOON is a cloud storage product / service (SaaS) by DRACOON GmbH (http://dracoon.com). \nDRACOON API documentation can be found here (Swagger UI):\n\nhttps://dracoon.team/api/\n\n### Built With\n\n* [Python 3.9.0](https://www.python.org/)\n* [httpx module](https://www.python-httpx.org/)\n* [cryptography](https://cryptography.io/en/latest/)\n* [pydantic](https://pydantic-docs.helpmanual.io/)\n\n[List all dependencies](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/requirements.txt)\n\n\u003c!-- GETTING STARTED --\u003e\n## Getting Started\n\nTo get started, create a virtual environment in Python and install the dracoon package:\n```bash\nvirtualenv \u003cDIR\u003e\nsource \u003cDIR\u003e/bin/activate \npython3 -m pip install dracoon\n```\n\n### Prerequisites\n\nYou will need a working Python 3 installation - check your version:\n* Python\n```bash\npython3 --version\n```\n\n### Installation\n\n1. Install the package from PyPi\n```bash\npython3 -m pip install dracoon\n```\n\n\u003c!-- USAGE EXAMPLES --\u003e\n## Usage\n### Import DRACOON\n```Python\nfrom dracoon import DRACOON\n```\n\nThis is the main class and contains all other adapters to access DRACOON API endpoints. \nThe object contains a client (DRACOONClient) which handles all http connections via httpx (async). \n\n\n### Object creation\n```Python\ndracoon = DRACOON(base_url, client_id, client_secret)\n```\n\n* _client_id_: please register your OAuth app or use dracoon_legacy_scripting (default)\n* _client_secret_: please register your OAuth app or use dracoon_legacy_scripting - secret is an empty string (no secret)\n* _base_url_: Your DRACOON URL instance (e.g. https://dracoon.team)\n\n#### Optional settings\nYou can additionally configure the logs for any script using the following optional parameters:\n* _log_stream_: default is set to False – when set to True, will output the logs to console / terminal (stderr)\n* _log_level_: default is set to logging.INFO – if required, can be changed to e.g. logging.DEBUG (this will contain senstive information e.g. names of created objects!). In order to use the log level, import logging module\n* _log_file_: default is set to './dracoon.log' (based on cwd of the running script!) – you can use any path with write access to log\n* _raise_on_err_: default is set to False – if set to True, any HTTP error (4xx or higher) will raise an error and stop the script / application\n\nFull parameters:\n```Python\ndracoon = DRACOON(base_url, client_id, client_secret, log_level, log_stream, log_file, raise_on_err)\n```\n\nA note to raising on errors: You can set the raise_on_err flag individually for any adapter method (e.g. nodes.get_nodes(raise_on_err=True)) to ensure the app breaks in case an error occurs. \n\n\n### Authentication\n\n#### Password flow\n\n```Python\nconnection = await dracoon.connect(OAuth2ConnectionType.password_flow, username, password)\n```\n\nThe connection result contains the tokens (access and refresh, including validity).\n\nYou need pass one of the supported OAuth2 connection types. \nTo access the enums, import OAuth2ConnectionType:\n\n```Python\nfrom dracoon import DRACOON, OAuth2Connectiontype\n```\n\nPlease note: you can only authenticate if OAuth app is correctly configured. Only local accounts (including Active Directory) can be used via password flow.\nFull example: [Login via password flow](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/login_password_flow.py)\n\n#### Authorization code flow\n```Python\nprint(dracoon.get_code_url())\nauth_code = input('Enter auth code:')\nconnection = await dracoon.connect(auth_code=auth_code)\n```\nIf you do not provide a connection type, the default will be auth code.\nYou should prompt (or fetch) the auth code via the respective url.\nFull example: [Login via auth code](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/login_auth_code_flow.py)\n\nPlease note: you can only authenticate if OAuth app is correctly configured. You will need a custom app with authorization code flow enabled and you will need to set your redirect uri to https://your.domain.com/oauth/callback for CLI usage (default). Otherwise, use a custom redirect uri by providing it as a parameter when creating a DRACOON instance:\n\n```Python\nDRACOON(base_url=base_url, client_id=client_id, client_secret=client_secret, redirect_uri='x-custom-handler://your.handler')\n```\n\n#### Test connection\n```Python\nconnected = dracoon.test_connection()\n```\nThis will provide a true / false result depending on the connection.\nIf no flag is set, this will just check if the access token is valid based on the token validity.\nIn order to test the connection with a request, use the test flag:\n\n```Python\nconnected = dracoon.test_connection(test=True)\n```\n\nAn authenticated ping is used to verify the tokens are valid.\n\n#### Refresh token\n\nAll methods check for access token validity and fetch new tokens, if the access tokens expire.\nTherefore it should not be necessary to manually request it.\n\nYou can manually use the refresh token auth as follows, if you have an authenticated instance:\n\n```Python\nconnection = await dracoon.client.connect(OAuth2ConnectionType.refresh_token)\n```\n\nEvery connect process will update the connection.\n\nIn order to securely store a refresh token, you can access the connection:\n\n```Python\nrefresh_token = dracoon.connection.refresh_token\n```\n\nYou can then create a new authenticated object like this:\n\n```Python\nconnection = await dracoon.connect(connection_type=OAuth2ConnectionType.refresh_token, refresh_token=xxxxx)\n```\n\n\n\n#### Log out\n```Python\nawait dracoon.logout()\n```\nThis will revoke both access and refresh tokens.\n\n\n### Send requests\n\n1. You can access specific API endpoints by accessing the related adapter, e.g. for users, once you have connected:\n\n```Python\nresult = await dracoon.users.get_users()\n```\n\nPlease note: \n* GET requests are limited to returning 500 items. Therefore all such requests contain an offset parameter (default is 0)\n* Providing a filter or sorting is optional - see API documentation and examples on usage – filter, sort or any other query parameter can be passed as parameter in any method\n* Raising on errors: Default is set to False – if needed, you can use the raise_on_err flag to stop for responses with HTTP status code 4xx or higher\n* If you do not connect the client, the adapters are not instantiated and \ncannot be accessed!\n* All (!) calls are async methods and need to be awaited\n\nAvailable adapters:\n\n```Python\ndracoon.config  # config API including webhooks\ndracoon.users  # users management\ndracoon.groups # groups management\ndracoon.user # user account and keypair setup\ndracoon.nodes # nodes (up- and download including S3 direct up)\ndracoon.shares # shares and file requests\ndracoon.uploads # upload API\ndracoon.reports # new reporting API\ndracoon.eventlog # old eventlog API\n```\n\n\n2. This package contains type hints and includes models for all payloads and responses (updates and create payloads).\nTo faciliate compliant object creation, there are several helper methods which can be found via make_, e.g.:\n\n```Python\nroom = dracoon.nodes.make_room(...)\n```\n\nThis helps finding the right parameters and building objects that are compliant with the DRACOON models.\n\n#### Aynchronous requests\n\nWith httpx this package supports full async request handling. This means all methods are coroutines which can be awaited.\nYou can use any runtime supported by httpx, e.g. asyncio (which comes with Python3).\n\nIn order to send requests asynchronously, you can use `asyncio.gather()` – example:\n\n```Python\nuser1_res = dracoon.users.create_user(user1)\nuser2_res = dracoon.users.create_user(user2)\nuser3_res = dracoon.users.create_user(user3)\n...\n\nusers = await asyncio.gather(user1_res, user2_res, user3_res, ...)\n\n```\n\nThe result is completely typed and returns a tuple with the responses in the order you sent the request:\nFor users[0] you receive user_1_res and so on.\n\n**Caution:** It is not recommended to use massive async requests for creating objects (e.g. creating rooms) or permissions based operations, as this might cause unexpected behaviour / errors.\n\nFor these cases, use small batches (e.g. 2 - 3 requests) to process requests faster without compromising the DRACOON API.\n\nExample for batches:\n\n```Python\nroom1_res = dracoon.nodes_create_room(room1)\nroom2_res = dracoon.nodes_create_room(room2)\nroom3_res = dracoon.nodes_create_room(room3)\n\n...\n\nrooms = await asyncio.gather(room1_res, room2_res, room3_res, ...)\n```\n\nYou can additionally use a helper to create an iterator with a given batch size:\n\n```Python\nrooms_reqs = [dracoon.nodes.create_room(room) for room in rooms]\n\n# will process 10 requests concurrently \nfor reqs in dracoon.batch_process(coro_list=room_reqs, batch_size=10):\n  await asyncio.gather(*reqs)\n\n...\n\nrooms = await asyncio.gather(room1_res, room2_res, room3_res, ...)\n```\n\n## Cryptography\n\nDRACOON cryptography is fully supported by the package. In order to use it, import the relevant functions or en- and decryptors:\n\n```Python\nfrom dracoon.crypto import create_plain_userkeypair\nfrom dracoon.crypto import create_file_key\n```\n\n### Create a new keypair\n\nThe account adapter (user) includes a method to set a new keypair:\n\n```Python\ndracoon.user.set_keypair(secret)\n\n```\nA new keypair will be generated (4096bit RSA asymmetric).\nPrior to setting a new keypair you always need to delete the old one!\nPlease note: Deleting a keypair can cause data loss.\n\n### Getting your (plain) keypair\n\nIn order to work with encrypted rooms you will need to access your keypair:\n\n```Python\nawait dracoon.get_keypair(secret=secret)\n\n```\nThis method of the main API wrapper will accept a secret (that you need to pass or prompt) returns the plain keypair and stores in in \nthe client for the current session.\n\n\n### En- and decode on the fly (in memory)\n\nFor smaller payload you can directly use the functions returning either\nplain or encrypted bytes like this:\n\n```Python\nplain_bytes = decrypt_bytes(enc_data, plain_file_key)\nenc_bytes = encrypt_bytes(plain_data, plain_file_key)\n\n```\n\n### Chunking\n\nFor larger files it is recommended to encrypt (and upload) in chunks.\nAn example of encryptor usage:\n\n```Python\ndracoon_cipher = FileEncryptionCipher(plain_file_key=plain_file_key)\nenc_chunk = dracoon_cipher.encode_bytes(chunk)\nlast_data, plain_file_key = dracoon_cipher.finalize()\n\n```\nYou can instantiate an encryptor / decryptor by passing a plain file key.\nWhen finalizing, you need to add the last data to the last chunk.\nThe result of the completed encryption is an updated plain_file_key with a specific tag.\n\nHint: You do not need to implement the upload process and can directly use full methods in the uploads adapter (see next chapter).\n\n## Transfers\n\n### Uploads\n\nThe nodes and uploads adapters include full methods to upload data to DRACOON and includes chunking and encryption support.\nImplementing the upload with respective calls is not recommended - please use the main wrapper (see example below) instead.\n\nHere is an example of uploading a file to an encrypted room:\n\n```Python\n\n    source = '/Example/Path/test.mov'\n    target = '/Example/Target/'\n    \n    await dracoon.upload(file_path=source, target_path=target)\n    \n```\n\nThe default chunk size is 32 MB but can be passed as an option (chunksize, in bytes).\n\nIf you have the node id of the target room / folder, you can also pass this and ommit the target_path like this:\n\n```Python\n\n    await dracoon.upload(file_path=source, target_parent_id=999)\n    \n```\n\nYou can also pass a custom file name, if required:\n\n```Python\n\n    await dracoon.upload(file_path=source, target_parent_id=999, file_name='my_custom.pdf')\n    \n```\n\n\nThe main API wrapper includes a method that includes upload for encrypted and unencrypted files.\nFull example: [File upload](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/upload.py)\n\n### Downloads\n\nThe downloads adapter includes full methods to download data from DRACOON including chunking and encryption support.\n\nAs with uploads, the main wrapper has a method which handles encryption, keypair and file key.\nUsage:\n\n```Python\n\ntarget = '/Example/Target'\nsource = '/DEMO/testfile.bin'\nawait dracoon.download(file_path=source, target_path=target)\n\n```\n\nYou can also pass a custom file name, if required:\n\n```Python\nawait dracoon.download(file_path=source, target_path=target, file_name='custom_file.pdf')\n```\nIf a file already exists, a FileConflictError will be raised (file is not overwritten).\n\nFull example: [Download files](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/download.py)\n\n\n### Callbacks\n\nIn order to keep track of a transfer progress, both up- and download accept a callback function which accepts a value of the changed bytes and the total size of the binary once (when initializing).\n\nA function should therefore adhere to the following signature:\n\n```Python\n\nclass Callback(Protocol):\n    def __call__(self, val: int, total: int = ...) -\u003e Any:\n        ...\n\n```\nThe function should accept the bytes as first value and accept the total as an optional parameter. \n\nA base class to build own jobs is also provided and called TransferJob - usage with inheritance (demo with tqdm as progress bar):\n\n```Python\nclass CustomTransferJob(TransferJob):\n    \"\"\" object representing a single transfer (up- / download) \"\"\"\n    progress_bar = None\n    \n    def __init__(self) -\u003e None:\n        super().__init__()\n    \n    def update_progress(self, val: int, total: int = None) -\u003e None:\n        self.transferred += val\n        if total is not None and self.total == 0:\n            self.total = total\n            self.progress_bar = tqdm(unit='iMB',unit_divisor=1024, total=self.total, unit_scale=True)\n        \n        if self.progress_bar:\n            self.progress_bar.update(val)\n    \n    def __del__(self):\n        if self.progress_bar:\n            self.progress_bar.close()\n            \n        \n    @property\n    def progress(self):\n        if self.total \u003e 0:\n            return self.transferred / self.total\n        else:\n            return 0\n```\n\nA full example can be found here: \n\n[Use transfer callbacks](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/transfer_callbacks.py)\n\n## Error handling \n\nIn order to perform error handling, you can import needed errors from the errors module:\n\n```Python\nfrom dracoon.errors import DRACOONBaseError, DRACOONHttpError, HTTPNotFoundError\n\n```\n\nThe error hirarchy is like this: \n\n* DRACOONBaseError - main error class\n  * DRACOONCryptoError - error related to crypto operations\n    * individual crypto errors\n  * DRACOONHttpError - error with response status code \u003e 3xx (4xx and above)\n    * HTTPNotFoundError - individual error named after response, e.g. 404 Not Found\n    ...\n  * DRACOONClientError - error with the client (not connected etc.)\n     * individual crypto errors\n  * DRACOONValidationError - errors validating input\n    * individual validation errors (e.g. FileConflictError)\n\nIn order to raise exceptions based on HTTP status codes you MUST provide the raise_on_err flag for the method like this:\n\n```Python\n\nawait dracoon.users.get_users(raise_on_err=True)\n\n```\n\nAlternatively you can set raise_on_err globally when creating the DRACOON object:\n\n```Python\n\ndracoon = DRACOON(base_url=base_url, client_id=client_id, client_secret=client_secret, log_level=logging.INFO, raise_on_err=True)\n\n```\n\nExample of catching errors:\n\n```Python\ntry:\n   await dracoon.users.get_user(user_id=999)\nexcept HTTPNotFoundError:\n  print(\"User not found\")\nexcept HTTPForbiddenError:\n  print(\"User is not a user manager - operation not allowed\")\nexcept DRACOONHttpError:\n  print(\"Oops, an unknown error ocurred\")\n\n```\n\n## Examples\n\n_For examples, check out the example files:_\u003cbr\u003e\n\n* [Login via password flow](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/login_password_flow.py)\n* [Login via auth code](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/login_auth_code_flow.py)\n* [Create a user](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/create_user.py)\n* [Set up personal rooms](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/personal_rooms.py)\n* [Process pending group assignments](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/pending_assignments.py)\n* [Set a new keypair](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/create_new_keypair.py)\n* [Upload file](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/upload.py)\n* [Download files](https://github.com/unbekanntes-pferd/dracoon-python-api/blob/master/examples/download.py)\n\n\u003c!-- ROADMAP --\u003e\n## Roadmap\n* Add branding API \n\n\u003c!-- LICENSE --\u003e\n## License\n\nDistributed under the Apache License. See [LICENSE](/LICENSE) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funbekanntes-pferd%2Fdracoon-python-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funbekanntes-pferd%2Fdracoon-python-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funbekanntes-pferd%2Fdracoon-python-api/lists"}