From ffc2a09441ec712ef96c3c8ee2580b294525869a Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Mon, 2 Aug 2021 18:49:50 -0700 Subject: [PATCH 1/7] Add docs --- .circleci/config.yml | 40 ++-- .gitignore | 2 + Makefile | 5 +- README.md | 20 ++ docs/Makefile | 32 +++ docs/classes/assets.rst | 5 + docs/classes/comments.rst | 5 + docs/classes/index.rst | 12 + docs/classes/logs.rst | 5 + docs/classes/projects.rst | 5 + docs/classes/search.rst | 2 + docs/classes/sharing.rst | 8 + docs/classes/teams.rst | 5 + docs/classes/users.rst | 5 + docs/conf.py | 60 +++++ docs/index.rst | 35 +++ docs/installation.rst | 37 +++ docs/make.bat | 35 +++ docs/modules/downloader.rst | 8 + docs/modules/helpers.rst | 9 + docs/modules/index.rst | 8 + docs/modules/uploader.rst | 8 + docs/modules/utils.rst | 8 + docs/publish.py | 190 +++++++++++++++ docs/requirements.txt | 8 + examples/asset_tree.py | 33 +++ examples/new_tests.py | 20 ++ frameioclient/__init__.py | 2 +- frameioclient/client.py | 78 +++++++ frameioclient/lib/download.py | 9 + frameioclient/lib/upload.py | 9 + frameioclient/lib/utils.py | 20 +- frameioclient/service/assets.py | 355 +++++++++++++++++++++++++++++ frameioclient/service/projects.py | 134 +++++++++++ frameioclient/service/service.py | 5 + frameioclient/services/comments.py | 51 +++-- frameioclient/services/links.py | 84 +++---- frameioclient/services/logs.py | 14 +- frameioclient/services/teams.py | 57 ++--- setup.py | 2 + 40 files changed, 1305 insertions(+), 125 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/classes/assets.rst create mode 100644 docs/classes/comments.rst create mode 100644 docs/classes/index.rst create mode 100644 docs/classes/logs.rst create mode 100644 docs/classes/projects.rst create mode 100644 docs/classes/search.rst create mode 100644 docs/classes/sharing.rst create mode 100644 docs/classes/teams.rst create mode 100644 docs/classes/users.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/make.bat create mode 100644 docs/modules/downloader.rst create mode 100644 docs/modules/helpers.rst create mode 100644 docs/modules/index.rst create mode 100644 docs/modules/uploader.rst create mode 100644 docs/modules/utils.rst create mode 100644 docs/publish.py create mode 100644 docs/requirements.txt create mode 100644 examples/asset_tree.py create mode 100644 examples/new_tests.py create mode 100644 frameioclient/service/assets.py create mode 100644 frameioclient/service/projects.py create mode 100644 frameioclient/service/service.py diff --git a/.circleci/config.yml b/.circleci/config.yml index f975809e..2d510f62 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,6 +33,11 @@ workflows: requires: - hold + - docs: + requires: + # - deploy + - build + # upload_test: # triggers: # - schedule: @@ -41,10 +46,8 @@ workflows: # branches: # only: # - jh/use-xxhash-for-integration-test - # jobs: # - build - # - upload_test_job: # requires: # - build @@ -103,17 +106,12 @@ jobs: name: Attach build artifact - run: - name: Install package - command: | - pip install '/tmp/artifact' - - - run: - name: Run integration test + name: Upload to pypi command: | - python /tmp/artifact/tests/integration.py - + cd /tmp/artifact + twine upload dist/* - deploy: + docs: docker: - image: circleci/python:latest @@ -125,18 +123,18 @@ jobs: - run: name: Install dependencies command: | - pip install setuptools wheel twine - + cd /tmp/artifact/docs + pip install -r requirements.txt + - run: - name: init .pypirc + name: Build autodocs command: | - cd /tmp/artifact - echo -e "[pypi]" >> ~/.pypirc - echo -e "username = $TWINE_USERNAME" >> ~/.pypirc - echo -e "password = $TWINE_PASSWORD" >> ~/.pypirc + cd /tmp/artifact/docs + make jekyll - run: - name: Upload to pypi + name: Publish autodocs command: | - cd /tmp/artifact - twine upload dist/* + cd /tmp/artifact/docs + python publish.py + diff --git a/.gitignore b/.gitignore index f1141588..37bc36ef 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,5 @@ Pipfile Pipfile.lock .vscode/launch.json .vscode/settings.json + +pyproject.toml \ No newline at end of file diff --git a/Makefile b/Makefile index a570a84d..0762ee5c 100644 --- a/Makefile +++ b/Makefile @@ -29,4 +29,7 @@ run-benchmark: docker run -it -e $1 benchmark format: - black frameioclient \ No newline at end of file + black frameioclient + +publish-docs: + cd docs && pip install -r requirements.txt && make jekyll && make publish \ No newline at end of file diff --git a/README.md b/README.md index b4b398fc..d8c4dd94 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,26 @@ fioctl \ --threads 2 ``` +### Links + +**Sphinx Documentation** +- https://pythonhosted.org/sphinxcontrib-restbuilder/ +- https://www.npmjs.com/package/rst-selector-parser +- https://sphinx-themes.org/sample-sites/furo/_sources/index.rst.txt +- https://developer.mantidproject.org/Standards/DocumentationGuideForDevs.html +- https://sublime-and-sphinx-guide.readthedocs.io/en/latest/code_blocks.html +- https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html +- https://stackoverflow.com/questions/64451966/python-sphinx-how-to-embed-code-into-a-docstring +- https://pythonhosted.org/an_example_pypi_project/sphinx.html + +**Decorators** +- https://docs.python.org/3.7/library/functools.html +- https://realpython.com/primer-on-python-decorators/ +- https://www.sphinx-doc.org/en/master/usage/quickstart.html +- https://www.geeksforgeeks.org/decorators-with-parameters-in-python/ +- https://stackoverflow.com/questions/43544954/why-does-sphinx-autodoc-output-a-decorators-docstring-when-there-are-two-decora + + ## Usage _Note: A valid token is required to make requests to Frame.io. Go to our [Developer Portal](https://developer.frame.io/) to get a token!_ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..29ca4852 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,32 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = dist + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +publish: + python publish.py + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +jekyll: + sphinx-build -b jekyll . dist/markdown + +rst: + sphinx-build -b rst . dist/rst + +html: + sphinx-build -b html . dist/html \ No newline at end of file diff --git a/docs/classes/assets.rst b/docs/classes/assets.rst new file mode 100644 index 00000000..ff78e6d6 --- /dev/null +++ b/docs/classes/assets.rst @@ -0,0 +1,5 @@ +Assets +========================= + +.. autoclass:: frameioclient.Asset + :members: diff --git a/docs/classes/comments.rst b/docs/classes/comments.rst new file mode 100644 index 00000000..ea9f45cc --- /dev/null +++ b/docs/classes/comments.rst @@ -0,0 +1,5 @@ +Comments +=================== + +.. autoclass:: frameioclient.Comment + :members: diff --git a/docs/classes/index.rst b/docs/classes/index.rst new file mode 100644 index 00000000..be5258bf --- /dev/null +++ b/docs/classes/index.rst @@ -0,0 +1,12 @@ +Classes +===================== + +.. toctree:: + users + assets + comments + logs + projects + teams + sharing + search \ No newline at end of file diff --git a/docs/classes/logs.rst b/docs/classes/logs.rst new file mode 100644 index 00000000..098f0fc2 --- /dev/null +++ b/docs/classes/logs.rst @@ -0,0 +1,5 @@ +Audit Logs +=================== + +.. autoclass:: frameioclient.AuditLogs + :members: diff --git a/docs/classes/projects.rst b/docs/classes/projects.rst new file mode 100644 index 00000000..0998acd0 --- /dev/null +++ b/docs/classes/projects.rst @@ -0,0 +1,5 @@ +Projects +=================== + +.. autoclass:: frameioclient.Project + :members: diff --git a/docs/classes/search.rst b/docs/classes/search.rst new file mode 100644 index 00000000..53825d7a --- /dev/null +++ b/docs/classes/search.rst @@ -0,0 +1,2 @@ +Search +=================== diff --git a/docs/classes/sharing.rst b/docs/classes/sharing.rst new file mode 100644 index 00000000..3bff7927 --- /dev/null +++ b/docs/classes/sharing.rst @@ -0,0 +1,8 @@ +Sharing +=================== + +.. autoclass:: frameioclient.PresentationLink + :members: + +.. autoclass:: frameioclient.ReviewLink + :members: \ No newline at end of file diff --git a/docs/classes/teams.rst b/docs/classes/teams.rst new file mode 100644 index 00000000..23c34eb9 --- /dev/null +++ b/docs/classes/teams.rst @@ -0,0 +1,5 @@ +Teams +=================== + +.. autoclass:: frameioclient.Team + :members: diff --git a/docs/classes/users.rst b/docs/classes/users.rst new file mode 100644 index 00000000..77ed12d1 --- /dev/null +++ b/docs/classes/users.rst @@ -0,0 +1,5 @@ +Users +=================== + +.. autoclass:: frameioclient.User + :members: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..a8dd5de3 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,60 @@ +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import frameioclient + +PACKAGE_TITLE = 'Frame.io Python SDK' +PACKAGE_NAME = 'frameioclient' +PACKAGE_DIR = '../frameioclient' +AUTHOR_NAME = 'Frame.io' + +try: + RELEASE = frameioclient.ClientVersion.version() +except AttributeError: + RELEASE = 'unknown' + +version = RELEASE.split('.')[0] + +# -- Project information ----------------------------------------------------- + +project = PACKAGE_TITLE +copyright = 'MIT License 2021, Frame.io' +author = AUTHOR_NAME + +# The full version, including alpha/beta/rc tags +release = RELEASE + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinxcontrib.restbuilder', + 'sphinx_jekyll_builder', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'build/*', 'examples/*', 'tests/*', '*.cfg', '.vscode/*', '.github/*', '.circleci/*', '.pytest_cache/*', 'dist/*'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'furo' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..9da0551d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,35 @@ +Welcome to Frame.io's Python SDK documentation! +=============================================== + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + +.. warning:: + This sample documentation was generated on |today|, and is rebuilt weekly. + + +FrameioClient +=================== +.. automodule:: frameioclient.FrameioClient + :inherited-members: + + +Classes +=========== +.. toctree:: + classes/index + + +Modules +=========== +.. toctree:: + modules/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 00000000..7ee3d525 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,37 @@ +=============== +frameioclient +=============== + +.. toctree:: + :hidden: + + installation + +Installation +============ + +Stable releases of frameioclient can be installed with + +.. code-block:: sh + + pip <- or you may download a `.tgz` source + +archive from `pypi `_. +See the :doc:`installation` page for more detailed instructions. + +If you want to use the latest code, you can grab it from our +`Git repository `_, or `fork it `_. + +Usage +=================================== + +Authorization +------------- + +Frame.io Python SDK documentation: `Personal Access Tokens `_. + + +.. code-block:: python + + from frameioclient import FrameioClient + client = FrameioClient(token='my-token') diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..2119f510 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/modules/downloader.rst b/docs/modules/downloader.rst new file mode 100644 index 00000000..2c213f92 --- /dev/null +++ b/docs/modules/downloader.rst @@ -0,0 +1,8 @@ +FrameioDownloader +=================== + +.. autoclass:: frameioclient.FrameioDownloader + :members: + :private-members: + :inherited-members: + :undoc-members: diff --git a/docs/modules/helpers.rst b/docs/modules/helpers.rst new file mode 100644 index 00000000..9f8c2b4e --- /dev/null +++ b/docs/modules/helpers.rst @@ -0,0 +1,9 @@ +FrameioHelpers +========================= + +.. autoclass:: frameioclient.FrameioHelpers + :members: + :private-members: + :inherited-members: + :undoc-members: + diff --git a/docs/modules/index.rst b/docs/modules/index.rst new file mode 100644 index 00000000..ed40d893 --- /dev/null +++ b/docs/modules/index.rst @@ -0,0 +1,8 @@ +Modules +===================== + +.. toctree:: + downloader + uploader + helpers + utils diff --git a/docs/modules/uploader.rst b/docs/modules/uploader.rst new file mode 100644 index 00000000..385dfe1c --- /dev/null +++ b/docs/modules/uploader.rst @@ -0,0 +1,8 @@ +FrameioUploader +=================== + +.. autoclass:: frameioclient.FrameioUploader + :members: + :private-members: + :inherited-members: + :undoc-members: diff --git a/docs/modules/utils.rst b/docs/modules/utils.rst new file mode 100644 index 00000000..cfc24284 --- /dev/null +++ b/docs/modules/utils.rst @@ -0,0 +1,8 @@ +Utils +=================== + +.. autoclass:: frameioclient.Utils + :members: + :private-members: + :inherited-members: + :undoc-members: diff --git a/docs/publish.py b/docs/publish.py new file mode 100644 index 00000000..4df3d17d --- /dev/null +++ b/docs/publish.py @@ -0,0 +1,190 @@ +import os +import hashlib +import frontmatter +import contentful_management + +TOKEN = os.getenv("CONTENTFUL_TOKEN") +SPACE_ID = os.getenv("CONTENTFUL_SPACE_ID") +SDK_ID = os.getenv("CONTENTFUL_SDK_ID") + +docs_path = "./dist/jekyll/api" + + +def transform_path(path): + # The paths generated automatically need modifying. + # This function should be localized to each SDK. + + if path == '/api-frameioclient': + new_path = 'package' + else: + new_path = path.split('/api-frameioclient-')[1].lower() + + return new_path + + +def transform_title(docname): + if docname == 'api/frameioclient': + new_title = 'Frame.io Python SDK' + else: + print(docname) + new_title = docname.split('.')[1].title() + + return new_title + + +def load_local(directory): + # Load in the local docs + docs_data = list() + files = os.listdir(directory) + for fn in files: + fpath = os.path.join(directory, fn) + with open(fpath) as f: + post = frontmatter.load(f) + post['path'] = transform_path(post['path']) + post['title'] = transform_title(post['docname']) + docs_data.append(post) + + return docs_data + + +def load_remote(): + # Create the client + client = contentful_management.Client(TOKEN) + + # Grab all the autoDocs + autoDoc = client.content_types(SPACE_ID, 'master').find('autoDoc') + entries = autoDoc.entries().all() + + # Filter out the ones that aren't the right programming language + relevant_docs = list() + for entry in entries: + # entry = autoDoc.entries().find(entry.id) + entry.sys['locale'] = 'en-US' + sdk = entry.programming_language.id + if sdk == SDK_ID: + relevant_docs.append(entry) + + return relevant_docs + + +def hash_content(content): + # Returns an SHA-256 hash of the stringified content provided + hash_object = hashlib.sha256(bytes(content, 'utf-8')) + sha256 = hash_object.hexdigest() + return sha256 + + +def update_doc(): + pass + + +def publish_new_docs(docs, publish=False): + client = contentful_management.Client(TOKEN) + + for new_entry in docs: + entry_attributes = { + 'content_type_id': 'autoDoc', + 'fields': { + 'title': { + 'en-US': new_entry['title'] + }, + 'slug': { + 'en-US': new_entry['slug'] + }, + 'content': { + 'en-US': new_entry['content'] + }, + 'programmingLanguage': { + 'en-US': { + 'sys': { + "id": SDK_ID, + "type": "Link", + "linkType": "Entry" + } + } + } + } + } + + new_entry = client.entries(SPACE_ID, 'master').create( + attributes=entry_attributes + ) + + # Only publish the new stuff is `publish=True` + if publish == True: + new_entry.publish() + + print(f"Submitted {entry_attributes['fields']['title']}") + + print("Done submitting") + + +def compare_docs(local, remote): + # Compare the remote docs and the local docs + + # Enrich local docs + enriched_local = dict() + for doc in local: + # print(doc.keys()) + enriched_local[hash_content(doc.content)] = { + "date": doc['date'], + "title": doc['title'], + "slug": doc['path'], + "content": doc.content, + "hash": hash_content(doc.content) + } + + # Enrich remote docs + enriched_remote = dict() + for doc in remote: + # print(doc.fields()) + enriched_remote[hash_content(doc.fields()['content'])] = { + "date": doc.sys['updated_at'], + "title": doc.fields()['title'], + "slug": doc.fields()['slug'], + "content": doc.fields()['content'], + "hash": hash_content(doc.fields()['content']) + } + + + # Compare titles and content hashes, update only ones in which the hashes are different + + # Declare our now list that we'll be appending to shortly + docs_to_update = list() + docs_to_maybe_publish = list() + docs_to_definitely_publish = list() + + # Iterate over keys + for doc_hash in enriched_local.keys(): + # If key found in remote keys, skip it + if doc_hash in enriched_remote.keys(): + print(f"Local and remote match for {enriched_remote[doc_hash]['title']}, skipping...") + continue + else: + docs_to_maybe_publish.append(enriched_local[doc_hash]) + + # return docs_to_update, docs_to_publish + return docs_to_maybe_publish + + +def main(): + # Grab the remote docs + remote_docs = load_remote() + + # Grab the local docs + local_docs = load_local(docs_path) + + # docs_to_update, docs_to_publish = compare_docs(local=local_docs, remote=remote_docs) + docs_to_publish = compare_docs(local=local_docs, remote=remote_docs) + + # Publish those docs! + publish_new_docs(docs_to_publish) + + # Iterate over the new docs and if + # for doc in new_docs: + # # print(doc.content) + # print(doc.keys()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..a1f50ba6 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,8 @@ +sphinx +sphinx-jekyll-builder +sphinxcontrib-restbuilder +contentful_management +python-frontmatter +# frameioclient +xxhash +furo \ No newline at end of file diff --git a/examples/asset_tree.py b/examples/asset_tree.py new file mode 100644 index 00000000..8dcafac9 --- /dev/null +++ b/examples/asset_tree.py @@ -0,0 +1,33 @@ +import os + +import pdb +from time import time +from pprint import pprint +from frameioclient import FrameioClient + +def demo_folder_tree(project_id, slim): + TOKEN = os.getenv("FRAMEIO_TOKEN") + client = FrameioClient(TOKEN) + + start_time = time() + tree = client.projects.tree(project_id, slim) + + end_time = time() + elapsed = round((end_time - start_time), 2) + + item_count = len(tree) + pprint(tree) + # pdb.set_trace() + + print(f"Found {item_count} items") + print(f"Took {elapsed} second to fetch the slim payload for project: {project_id}") + print("\n") + +if __name__ == "__main__": + project_id = '2dfb6ce6-90d8-4994-881f-f02cd94b1c81' + # project_id='e2845993-7330-54c6-8b77-eafbd5144eac' + demo_folder_tree(project_id, slim=True) + # demo_folder_tree(project_id, slim=False) + +# 445 seconds for slim +# 509 seconds for non-slim \ No newline at end of file diff --git a/examples/new_tests.py b/examples/new_tests.py new file mode 100644 index 00000000..7b1fac6e --- /dev/null +++ b/examples/new_tests.py @@ -0,0 +1,20 @@ +import os +from pprint import pprint + +from frameioclient import FrameioClient, Asset, ClientVersion + + +token = os.getenv('FRAMEIO_TOKEN') +client = FrameioClient(token) +folder_id = 'dd8526ee-2c7d-4b48-9bf7-b847664666bb' +file_path = '/Users/jeff/Code/python-frameio-client/examples/downloads/accelerated_Test_Chart_5_Sec_embedded_meta_Mezzanine.mxf' + +client.assets.upload(folder_id, file_path) + + +print(client.users.get_me()) + +pprint(client.teams.list_projects(client.teams.list_all()[0]['id'])) + +for log in client.logs.list(client.users.get_me()['account_id']): + print(log) \ No newline at end of file diff --git a/frameioclient/__init__.py b/frameioclient/__init__.py index 8f02abec..04bcc1e2 100644 --- a/frameioclient/__init__.py +++ b/frameioclient/__init__.py @@ -1,3 +1,3 @@ from .lib import * from .services import * -from .client import FrameioClient \ No newline at end of file +from .client import FrameioClient diff --git a/frameioclient/client.py b/frameioclient/client.py index dd7e1976..ddb0f976 100644 --- a/frameioclient/client.py +++ b/frameioclient/client.py @@ -1,3 +1,8 @@ +""" +client.py +==================================== +The core module of the frameioclient +""" from .lib import ( APIClient, Telemetry, @@ -19,6 +24,79 @@ def me(self): def telemetry(self): return Telemetry(self) +<<<<<<< + +======= + self.adapter = HTTPAdapter(max_retries=self.retry_strategy) + self.session = requests.Session() + self.session.mount("https://", self.adapter) + + def _api_call(self, method, endpoint, payload={}, limit=None): + url = '{}/v2{}'.format(self.host, endpoint) + + r = self.session.request( + method, + url, + json=payload, + headers=self.headers, + ) + + if r.ok: + if r.headers.get('page-number'): + if int(r.headers.get('total-pages')) > 1: + return PaginatedResponse( + results=r.json(), + limit=limit, + page_size=r.headers['per-page'], + total_pages=r.headers['total-pages'], + total=r.headers['total'], + endpoint=endpoint, + method=method, + payload=payload, + client=self + ) + if isinstance(r.json(), list): + return r.json()[:limit] + return r.json() + + if r.status_code == 422 and "presentation" in endpoint: + raise PresentationException + + return r.raise_for_status() + + def get_specific_page(self, method, endpoint, payload, page): + """ + Gets a specific page for that endpoint, used by Pagination Class + + :Args: + method (string): 'get', 'post' + endpoint (string): endpoint ('/accounts//teams') + payload (dict): Request payload + page (int): What page to get + """ + if method == 'get': + # If we've already got a ? in the endpoint, then it has to be an & + if '?' in endpoint: + endpoint = '{}&page={}'.format(endpoint, page) + else: + endpoint = '{}?page={}'.format(endpoint, page) + return self._api_call(method, endpoint) + + if method == 'post': + payload['page'] = page + return self._api_call(method, endpoint, payload=payload) + + +class FrameioClient(FrameioConnection): + """[summary] + + Args: + FrameioConnection ([type]): [description] + + Returns: + [type]: [description] + """ +>>>>>>> @property def _auth(self): return self.token diff --git a/frameioclient/lib/download.py b/frameioclient/lib/download.py index 0f57c926..ef2b56ea 100644 --- a/frameioclient/lib/download.py +++ b/frameioclient/lib/download.py @@ -138,6 +138,8 @@ def get_download_key(self): return url def download_handler(self): + """Call this to perform the actual download of your asset! + """ if os.path.isdir(os.path.join(os.path.curdir, self.download_folder)): print("Folder exists, don't need to create it") else: @@ -147,10 +149,17 @@ def download_handler(self): if os.path.isfile(self.get_path()) == False: pass +<<<<<<< if os.path.isfile(self.get_path()) and self.replace == True: os.remove(self.get_path()) if os.path.isfile(self.get_path()) and self.replace == False: +======= + def download_handler(self): + """Call this to perform the actual download of your asset! + """ + if os.path.isfile(self.get_path()): +>>>>>>> print("File already exists at this location.") return self.destination diff --git a/frameioclient/lib/upload.py b/frameioclient/lib/upload.py index 97d7f1ec..2edcb915 100644 --- a/frameioclient/lib/upload.py +++ b/frameioclient/lib/upload.py @@ -17,6 +17,15 @@ def __init__(self, asset=None, file=None): self.file_num = 0 def _calculate_chunks(self, total_size, chunk_count): + """Calculate chunk size + + Args: + total_size (int): Total filesize in bytes + chunk_count (int): Total number of URL's we got back from the API + + Returns: + chunk_offsets (list): List of chunk offsets + """ self.chunk_size = int(math.ceil(total_size / chunk_count)) chunk_offsets = list() diff --git a/frameioclient/lib/utils.py b/frameioclient/lib/utils.py index 4c29d7ea..094519b0 100644 --- a/frameioclient/lib/utils.py +++ b/frameioclient/lib/utils.py @@ -7,18 +7,28 @@ MB = KB * KB +def Reference(*args, **kwargs): + print(kwargs['operation']) + def inner(func): + ''' + do operations with func + ''' + return func + return inner + class Utils: @staticmethod def stream(func, page=1, page_size=20): """ - Accepts a lambda of a call to a client list method, and streams the results until - the list has been exhausted + Accepts a lambda of a call to a client list method, and streams the results until \ + the list has been exhausted. - :Args: + Args: fun (function): A 1-arity function to apply during the stream - Example:: - stream(lambda pagination: client.get_collaborators(project_id, **pagination)) + Example:: + + stream(lambda pagination: client.get_collaborators(project_id, **pagination)) """ total_pages = page while page <= total_pages: diff --git a/frameioclient/service/assets.py b/frameioclient/service/assets.py new file mode 100644 index 00000000..320ca89d --- /dev/null +++ b/frameioclient/service/assets.py @@ -0,0 +1,355 @@ +import os +import mimetypes + +from .service import Service +from .projects import Project + +from ..lib import FrameioUploader, FrameioDownloader, constants, Reference + +class Asset(Service): + def _build_asset_info(self, filepath): + full_path = os.path.abspath(filepath) + + file_info = { + "filepath": full_path, + "filename": os.path.basename(full_path), + "filesize": os.path.getsize(full_path), + "mimetype": mimetypes.guess_type(full_path)[0] + } + + return file_info + + @Reference(operation="#getAsset") + def get(self, asset_id): + """ + Get an asset by id. + + Args: + asset_id (string): The asset id. + """ + endpoint = '/assets/{}'.format(asset_id) + return self.client._api_call('get', endpoint) + + @Reference(operation="#getAssets") + def get_children(self, asset_id, include=[], slim=False, **kwargs): + """ + Get a folder. + + Args: + asset_id (string): The asset id. + + :Keyword Arguments: + includes (list): List of includes you would like to add. + + Example:: + + client.assets.get_children( + asset_id='1231-12414-afasfaf-aklsajflaksjfla', + includes=['review_links','cover_asset','creator','presentation'] + ) + """ + endpoint = '/assets/{}/children'.format(asset_id) +<<<<<<< + +======= + + + if slim == True: + query_params = '' + + # Include children + query_params += '?' + 'include=children,creator' + + # Only fields + query_params += '&' + 'only_fields=' + ','.join(constants.asset_excludes['only_fields']) + + # # Drop includes + query_params += '&' + 'drop_includes=' + ','.join(constants.asset_excludes['drop_includes']) + + # # Hard drop fields + query_params += '&' + 'hard_drop_fields=' + ','.join(constants.asset_excludes['hard_drop_fields']) + + # Excluded fields + # query_params += '&' + 'excluded_fields=' + ','.join(constants.asset_excludes['excluded_fields']) + + # # Sort by inserted_at + # query_params += '&' + 'sort=-inserted_at' + + endpoint += query_params + + # print("Final URL", endpoint) + + if len(include) > 0: + endpoint += '&include={}'.format(include.join(',')) + + return self.client._api_call('get', endpoint, kwargs) + + if len(include) > 0: + endpoint += '?include={}'.format(include.join(',')) + +>>>>>>> + return self.client._api_call('get', endpoint, kwargs) + + @Reference(operation="#createAsset") + def create(self, parent_asset_id, **kwargs): + """ + Create an asset. + + Args: + parent_asset_id (string): The parent asset id. + + :Keyword Arguments: + (optional) kwargs: additional request parameters. + + Example:: + + client.assets.create( + parent_asset_id="123abc", + name="ExampleFile.mp4", + type="file", + filetype="video/mp4", + filesize=123456 + ) + """ + endpoint = '/assets/{}/children'.format(parent_asset_id) + return self.client._api_call('post', endpoint, payload=kwargs) + +<<<<<<< + +======= + @Reference(operation="#createAsset") + def create_folder(self, parent_asset_id, name="New Folder"): + """ + Create a new folder. + + Args: + parent_asset_id (string): The parent asset id. + name (string): The name of the new folder. + + Example:: + + client.assets.create_folder( + parent_asset_id="123abc", + name="ExampleFile.mp4", + ) + """ + endpoint = '/assets/{}/children'.format(parent_asset_id) + return self.client._api_call('post', endpoint, payload={"name": name, "type":"folder"}) + + @Reference(operation="#createAsset") +>>>>>>> + def from_url(self, parent_asset_id, name, url): + """ + Create an asset from a URL. + + Args: + parent_asset_id (str): The parent asset id. + name (str): The filename. + url (str): The remote URL. + + Example:: + + client.assets.from_url( + parent_asset_id="123abc", + name="ExampleFile.mp4", + type="file", + url="https://" + ) + """ + + payload = { + "name": name, + "type": "file", + "source": { + "url": url + } + } + + endpoint = '/assets/{}/children'.format(parent_asset_id) + return self.client._api_call('post', endpoint, payload=payload) + + @Reference(operation="#updateAsset") + def update(self, asset_id, **kwargs): + """ + Updates an asset + + Args: + asset_id (string): the asset's id + + :Keyword Arguments: + kwargs (optional): fields and values you wish to update + + Example:: + + client.assets.update("adeffee123342", name="updated_filename.mp4") + """ + endpoint = '/assets/{}'.format(asset_id) + return self.client._api_call('put', endpoint, kwargs) + + @Reference(operation="#copyAsset") + def copy(self, destination_folder_id, **kwargs): + """ + Copy an Asset + + Args: + destination_folder_id (str): The id of the folder you want to copy into. + + :Keyword Arguments: + id (str): The id of the asset you want to copy. + + Example:: + + client.assets.copy("adeffee123342", id="7ee008c5-49a2-f8b5-997d-8b64de153c30") + """ + endpoint = '/assets/{}/copy'.format(destination_folder_id) + return self.client._api_call('post', endpoint, kwargs) + +<<<<<<< + def bulk_copy(self, destination_folder_id, asset_list=[], copy_comments=False): +======= + @Reference(operation="#batchCopyAsset") + def bulk_copy(self, destination_folder_id, asset_list, copy_comments=False): +>>>>>>> + """Bulk copy assets + + Args: + destination_folder_id (string): The id of the folder you want to copy into. + + :Keyword Arguments: + asset_list (list): A list of the asset IDs you want to copy. + copy_comments (boolean): Whether or not to copy comments: True or False. + + Example:: + + client.assets.bulk_copy("adeffee123342", + asset_list=[ + "7ee008c5-49a2-f8b5-997d-8b64de153c30", + "7ee008c5-49a2-f8b5-997d-8b64de153c30" + ], + copy_comments=True + ) + """ + + payload = {"batch": []} + new_list = list() + + if copy_comments: + payload['copy_comments'] = "all" + + for asset in asset_list: + payload['batch'].append({"id": asset}) + + endpoint = '/batch/assets/{}/copy'.format(destination_folder_id) + return self.client._api_call('post', endpoint, payload) + + @Reference(operation="#deleteAsset") + def delete(self, asset_id): + """ + Delete an asset + + Args: + asset_id (string): the asset's id + """ + endpoint = '/assets/{}'.format(asset_id) + return self.client._api_call('delete', endpoint) + + def _upload(self, asset, file): + """ + Upload an asset. The method will exit once the file is uploaded. + + Args: + asset (object): The asset object. + file (file): The file to upload. + + Example:: + + client._upload(asset, open('example.mp4')) + """ + + uploader = FrameioUploader(asset, file) + uploader.upload() + + # def upload_folder(sFelf, destination_id, folderpath): + # try: + # if os.path.isdir(folderpath): + # # Good it's a directory, we can keep going + # pass + + # except OSError: + # if not os.path.exists(folderpath): + # sys.exit("Folder doesn't exist, exiting...") + + def build_asset_info(self, filepath): + full_path = os.path.abspath(filepath) + + file_info = { + "filepath": full_path, + "filename": os.path.basename(full_path), + "filesize": os.path.getsize(full_path), + "mimetype": mimetypes.guess_type(full_path)[0] + } + + return file_info + + def upload(self, destination_id, filepath, asset=None): + """ + Upload a file. The method will exit once the file is downloaded. + + Args: + destination_id (uuid): The destination Project or Folder ID. + filepath (string): The locaiton of the file on your local filesystem \ + that you want to upload. + + Example:: + + client.assets.upload('1231-12414-afasfaf-aklsajflaksjfla', "./file.mov") + """ + + # Check if destination is a project or folder + # If it's a project, well then we look up its root asset ID, otherwise we use the folder id provided + # Then we start our upload + + try: + # First try to grab it as a folder + folder_id = self.get(destination_id)['id'] + except Exception as e: + # Then try to grab it as a project + folder_id = Project(self.client).get_project(destination_id)['root_asset_id'] + finally: + file_info = self.build_asset_info(filepath) + + if not asset: + try: + asset = self.create(folder_id, + type="file", + name=file_info['filename'], + filetype=file_info['mimetype'], + filesize=file_info['filesize'] + ) + + except Exception as e: + print(e) + + try: + with open(file_info['filepath'], "rb") as fp: + self._upload(asset, fp) + + except Exception as e: + print(e) + + return asset + + def download(self, asset, download_folder, prefix=None, multi_part=False, concurrency=5): + """ + Download an asset. The method will exit once the file is downloaded. + + Args: + asset (object): The asset object. + download_folder (path): The location to download the file to. + + Example:: + + client.assets.download(asset, "~./Downloads") + """ + downloader = FrameioDownloader(asset, download_folder, prefix, multi_part, concurrency) + return downloader.download_handler() diff --git a/frameioclient/service/projects.py b/frameioclient/service/projects.py new file mode 100644 index 00000000..e8a78942 --- /dev/null +++ b/frameioclient/service/projects.py @@ -0,0 +1,134 @@ +from .service import Service +from .helpers import FrameioHelpers + +class Project(Service): + def create(self, team_id, **kwargs): + """Create a project. + + Args: + team_id (string): The team id. + + :Keyword Arguments: + (optional) kwargs: additional request parameters. + + Example:: + + client.projects.create( + team_id="123", + name="My Awesome Project" + ) + """ + endpoint = '/teams/{}/projects'.format(team_id) + return self.client._api_call('post', endpoint, payload=kwargs) + + def get(self, project_id): + """ + Get an individual project + + Args: + project_id (string): The project's id + + Example:: + + client.project.get( + project_id="123" + ) + """ + endpoint = '/projects/{}'.format(project_id) + return self.client._api_call('get', endpoint) + + def tree(self, project_id, slim): + """ + Fetch a tree representation of all files/folders in a project. + + Args: + project_id (string): The project's id + slim (bool): If true, fetch only the minimum information for the following: \ + filename, \ + filesize, \ + thumbnail, \ + creator_id, \ + inserted_at (date created), \ + path (represented like a filesystem) + + Example:: + + client.projects.get( + project_id="123", + slim=True + ) + """ + # endpoint = "/projects/{}/tree?depth=20&drop_includes=a.transcode_statuses,a.transcodes,a.source,a.checksums&only_fields=a.name,a.filesize,u.name,a.item_count,a.creator_id,a.inserted_at,a.uploaded_at".format(project_id) + # return self.client._api_call('get', endpoint) + + return FrameioHelpers(self.client).build_project_tree(project_id, slim) + + + def get_collaborators(self, project_id, **kwargs): + """ + Get collaborators for a project. + + Args: + project_id (uuid): The project's id. + + Example:: + + client.projects.get_collaborators( + project_id="123" + ) + """ + endpoint = "/projects/{}/collaborators?include=project_role".format(project_id) + return self.client._api_call('get', endpoint, kwargs) + + def get_pending_collaborators(self, project_id, **kwargs): + """ + Get pending collaborators for a project. + + Args: + project_id (uuid): The project's id. + + Example:: + + client.projects.get_pending_collaborators( + project_id="123" + ) + """ + endpoint = "/projects/{}/pending_collaborators".format(project_id) + return self.client._api_call('get', endpoint, kwargs) + + def add_collaborator(self, project_id, email): + """ + Add Collaborator to a Project Collaborator. + + Args: + project_id (uuid): The project id. + email (string): Email user's e-mail address. + + Example:: + + client.projects.add_collaborator( + project_id="123", + email="janedoe@frame.io" + ) + """ + payload = {"email": email} + endpoint = '/projects/{}/collaborators'.format(project_id) + return self._api_call('post', endpoint, payload=payload) + + def remove_collaborator(self, project_id, email): + """ + Remove Collaborator from Project. + + Args: + project_id (uuid): The Project ID. + email (string): The user's e-mail address. + + Example:: + + client.projects.remove_collaborator( + project_id="123", + email="janedoe@frame.io" + ) + """ + endpoint = '/projects/{}/collaborators/_?email={}'.format(project_id, email) + return self._api_call('delete', endpoint) diff --git a/frameioclient/service/service.py b/frameioclient/service/service.py new file mode 100644 index 00000000..a2ffa123 --- /dev/null +++ b/frameioclient/service/service.py @@ -0,0 +1,5 @@ +from ..client import FrameioClient + +class Service(object): + def __init__(self, client: FrameioClient): + self.client = client diff --git a/frameioclient/services/comments.py b/frameioclient/services/comments.py index a3fde975..36603281 100644 --- a/frameioclient/services/comments.py +++ b/frameioclient/services/comments.py @@ -5,17 +5,18 @@ def create(self, asset_id, **kwargs): """ Create a comment. - :Args: + Args: asset_id (string): The asset id. - :Kwargs: + + :Keyword Arguments: (optional) kwargs: additional request parameters. - Example:: + Example:: - client.comments.create( - asset_id="123abc", - text="Hello world" - ) + client.comments.create( + asset_id="123abc", + text="Hello world" + ) """ endpoint = '/assets/{}/comments'.format(asset_id) return self.client._api_call('post', endpoint, payload=kwargs) @@ -24,7 +25,7 @@ def get(self, comment_id, **kwargs): """ Get a comment. - :Args: + Args: comment_id (string): The comment id. """ endpoint = '/comments/{}'.format(comment_id) @@ -34,7 +35,7 @@ def list(self, asset_id, **kwargs): """ Get an asset's comments. - :Args: + Args: asset_id (string): The asset id. """ endpoint = '/assets/{}/comments'.format(asset_id) @@ -44,17 +45,18 @@ def update(self, comment_id, **kwargs): """ Update a comment. - :Args: + Args: comment_id (string): The comment id. - :Kwargs: + + :Keyword Arguments: (optional) kwargs: additional request parameters. - Example:: + Example:: - client.comments.update( - comment_id="123abc", - text="Hello world" - ) + client.comments.update( + comment_id="123abc", + text="Hello world" + ) """ endpoint = '/comments/{}'.format(comment_id) return self.client._api_call('post', endpoint, payload=kwargs) @@ -63,7 +65,7 @@ def delete(self, comment_id): """ Delete a comment. - :Args: + Args: comment_id (string): The comment id. """ endpoint = '/comments/{}'.format(comment_id) @@ -73,17 +75,18 @@ def reply(self, comment_id, **kwargs): """ Reply to an existing comment. - :Args: + Args: comment_id (string): The comment id. - :Kwargs: + + :Keyword Arguments: (optional) kwargs: additional request parameters. - Example:: + Example:: - client.comments.reply( - comment_id="123abc", - text="Hello world" - ) + client.comments.reply( + comment_id="123abc", + text="Hello world" + ) """ endpoint = '/comments/{}/replies'.format(comment_id) return self.client._api_call('post', endpoint, payload=kwargs) diff --git a/frameioclient/services/links.py b/frameioclient/services/links.py index b617a36d..127860b0 100644 --- a/frameioclient/services/links.py +++ b/frameioclient/services/links.py @@ -5,18 +5,19 @@ def create(self, project_id, **kwargs): """ Create a review link. - :Args: + Args: project_id (string): The project id. - :Kwargs: + + :Keyword Arguments: kwargs: additional request parameters. - Example:: + Example:: - client.review_links.create( - project_id="123", - name="My Review Link", - password="abc123" - ) + client.review_links.create( + project_id="123", + name="My Review Link", + password="abc123" + ) """ endpoint = '/projects/{}/review_links'.format(project_id) return self.client._api_call('post', endpoint, payload=kwargs) @@ -25,7 +26,7 @@ def list(self, project_id): """ Get the review links of a project - :Args: + Args: asset_id (string): The asset id. """ endpoint = '/projects/{}/review_links'.format(project_id) @@ -35,7 +36,7 @@ def get(self, link_id, **kwargs): """ Get a single review link - :Args: + Args: link_id (string): The review link id. """ endpoint = '/review_links/{}'.format(link_id) @@ -45,14 +46,14 @@ def get_assets(self, link_id): """ Get items from a single review link. - :Args: + Args: link_id (string): The review link id. - Example:: + Example:: - client.review_links.get_assets( - link_id="123" - ) + client.review_links.get_assets( + link_id="123" + ) """ endpoint = '/review_links/{}/items'.format(link_id) return self.client._api_call('get', endpoint) @@ -61,17 +62,18 @@ def update_assets(self, link_id, **kwargs): """ Add or update assets for a review link. - :Args: + Args: link_id (string): The review link id. - :Kwargs: + + :Keyword Arguments: kwargs: additional request parameters. - Example:: + Example:: - client.review_links.update_assets( - link_id="123", - asset_ids=["abc","def"] - ) + client.review_links.update_assets( + link_id="123", + asset_ids=["abc","def"] + ) """ endpoint = '/review_links/{}/assets'.format(link_id) return self.client._api_call('post', endpoint, payload=kwargs) @@ -80,20 +82,21 @@ def update_settings(self, link_id, **kwargs): """ Updates review link settings. - :Args: + Args: link_id (string): The review link id. - :Kwargs: + + :Keyword Arguments: kwargs: additional request parameters. - Example:: + Example:: - client.review_links.update_settings( - link_id, - expires_at="2020-04-08T12:00:00+00:00", - is_active=False, - name="Review Link 123", - password="my_fun_password", - ) + client.review_links.update_settings( + link_id, + expires_at="2020-04-08T12:00:00+00:00", + is_active=False, + name="Review Link 123", + password="my_fun_password", + ) """ endpoint = '/review_links/{}'.format(link_id) return self.client._api_call('put', endpoint, payload=kwargs) @@ -104,18 +107,19 @@ def create(self, asset_id, **kwargs): """ Create a presentation link. - :Args: + Args: asset_id (string): The asset id. - :Kwargs: + + :Keyword Arguments: kwargs: additional request parameters. - Example:: + Example:: - client.presentation_links.create( - asset_id="9cee7966-4066-b326-7db1-f9e6f5e929e4", - title="My fresh presentation", - password="abc123" - ) + client.presentation_links.create( + asset_id="9cee7966-4066-b326-7db1-f9e6f5e929e4", + title="My fresh presentation", + password="abc123" + ) """ endpoint = '/assets/{}/presentations'.format(asset_id) return self.client._api_call('post', endpoint, payload=kwargs) diff --git a/frameioclient/services/logs.py b/frameioclient/services/logs.py index 3dba9af9..25642620 100644 --- a/frameioclient/services/logs.py +++ b/frameioclient/services/logs.py @@ -5,13 +5,17 @@ def list(self, account_id): """ Get audit logs for the currently authenticated account. - :Args: + Args: + account_id (uuid): Account ID you want to get audit logs for. - Example:: + Example:: - client.logs.list( - account_id="6bdcb4d9-9a2e-a765-4548-ae6b27a6c024" - ) + client.logs.list( + account_id="6bdcb4d9-9a2e-a765-4548-ae6b27a6c024" + ) + + Returns: + list: List of audit logs. """ endpoint = '/accounts/{}/audit_logs'.format(account_id) return self.client._api_call('get', endpoint) diff --git a/frameioclient/services/teams.py b/frameioclient/services/teams.py index c515c14a..9df71294 100644 --- a/frameioclient/services/teams.py +++ b/frameioclient/services/teams.py @@ -6,17 +6,18 @@ def create(self, account_id, **kwargs): """ Create a Team - :Args: - account_id (string): The account id you want to create this Team under. - :Kwargs: + Args: + account_id (string): The account id you want to create this team under. + + :Keyword Arguments:: (optional) kwargs: additional request parameters. - Example:: + Example:: - client.teams.create( - account_id="6bdcb4d9-4548-4548-4548-27a6c024ae6b", - name="My Awesome Project", - ) + client.teams.create( + account_id="6bdcb4d9-4548-4548-4548-27a6c024ae6b", + name="My Awesome Project", + ) """ warnings.warn('Note: Your token must support team.create scopes') endpoint = '/accounts/{}/teams'.format(account_id) @@ -24,10 +25,10 @@ def create(self, account_id, **kwargs): def list(self, account_id, **kwargs): """ - Get teams owned by the specified account. - (To return all teams, use list_all()) + Get teams owned by the specified account. \ + (To return all teams, use list_all()) - :Args: + Args: account_id (string): The account id. """ endpoint = '/accounts/{}/teams'.format(account_id) @@ -37,7 +38,7 @@ def list_all(self, **kwargs): """ Get all teams for the authenticated user. - :Args: + Args: account_id (string): The account id. """ endpoint = '/teams' @@ -47,39 +48,39 @@ def get(self, team_id): """ Get team by id - :Args: - team_id (string): the Team's id + Args: + team_id (string): the team's id """ endpoint = '/teams/{}'.format(team_id) return self.client._api_call('get', endpoint) def get_members(self, team_id): """ - Get the member list for a given Team. + Get the member list for a given team. - :Args: - team_id (string): The Team id. + Args: + team_id (string): The team id. """ endpoint = '/teams/{}/members'.format(team_id) return self.client._api_call('get', endpoint) def list_projects(self, team_id, **kwargs): """ - Get projects owned by the Team. + Get projects owned by the team. - :Args: - team_id (string): The Team id. + Args: + team_id (string): The team id. """ endpoint = '/teams/{}/projects'.format(team_id) return self.client._api_call('get', endpoint, kwargs) def add_members(self, team_id, emails): """ - Add a list of users via their e-mail address to a given Team. + Add a list of users via their e-mail address to a given team. - :Args: - team_id (string): The team id. - emails (list): The e-mails you want to add. + Args: + team_id (string): The team id. + emails (list): The e-mails you want to add. """ payload = dict() payload['batch'] = list(map(lambda email: {"email": email}, emails)) @@ -89,11 +90,11 @@ def add_members(self, team_id, emails): def remove_members(self, team_id, emails): """ - Remove a list of users via their e-mail address from a given Team. + Remove a list of users via their e-mail address from a given team. - :Args: - team_id (string): The team id. - emails (list): The e-mails you want to add. + Args: + team_id (string): The team id. + emails (list): The e-mails you want to add. """ payload = dict() diff --git a/setup.py b/setup.py index a378aded..660e0315 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,8 @@ def run(self): extras_require={ 'dev': [ 'bump2version', + 'sphinx', + 'sphinx-jekyll-builder' ] }, entry_points ={ From c74d64a85d7ec235cbff65bf62206117f2b3f16b Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Mon, 2 Aug 2021 21:02:20 -0700 Subject: [PATCH 2/7] Resolve conflicts post-merge --- frameioclient/lib/__init__.py | 2 +- frameioclient/service/assets.py | 355 ------------------------------ frameioclient/service/projects.py | 134 ----------- frameioclient/service/service.py | 5 - frameioclient/services/assets.py | 11 +- 5 files changed, 11 insertions(+), 496 deletions(-) delete mode 100644 frameioclient/service/assets.py delete mode 100644 frameioclient/service/projects.py delete mode 100644 frameioclient/service/service.py diff --git a/frameioclient/lib/__init__.py b/frameioclient/lib/__init__.py index 64d35e8d..6303df14 100644 --- a/frameioclient/lib/__init__.py +++ b/frameioclient/lib/__init__.py @@ -6,4 +6,4 @@ from .upload import FrameioUploader from .download import FrameioDownloader from .transport import AWSClient, APIClient -from .utils import Utils, PaginatedResponse, KB, MB +from .utils import Utils, PaginatedResponse, KB, MB, Reference diff --git a/frameioclient/service/assets.py b/frameioclient/service/assets.py deleted file mode 100644 index 320ca89d..00000000 --- a/frameioclient/service/assets.py +++ /dev/null @@ -1,355 +0,0 @@ -import os -import mimetypes - -from .service import Service -from .projects import Project - -from ..lib import FrameioUploader, FrameioDownloader, constants, Reference - -class Asset(Service): - def _build_asset_info(self, filepath): - full_path = os.path.abspath(filepath) - - file_info = { - "filepath": full_path, - "filename": os.path.basename(full_path), - "filesize": os.path.getsize(full_path), - "mimetype": mimetypes.guess_type(full_path)[0] - } - - return file_info - - @Reference(operation="#getAsset") - def get(self, asset_id): - """ - Get an asset by id. - - Args: - asset_id (string): The asset id. - """ - endpoint = '/assets/{}'.format(asset_id) - return self.client._api_call('get', endpoint) - - @Reference(operation="#getAssets") - def get_children(self, asset_id, include=[], slim=False, **kwargs): - """ - Get a folder. - - Args: - asset_id (string): The asset id. - - :Keyword Arguments: - includes (list): List of includes you would like to add. - - Example:: - - client.assets.get_children( - asset_id='1231-12414-afasfaf-aklsajflaksjfla', - includes=['review_links','cover_asset','creator','presentation'] - ) - """ - endpoint = '/assets/{}/children'.format(asset_id) -<<<<<<< - -======= - - - if slim == True: - query_params = '' - - # Include children - query_params += '?' + 'include=children,creator' - - # Only fields - query_params += '&' + 'only_fields=' + ','.join(constants.asset_excludes['only_fields']) - - # # Drop includes - query_params += '&' + 'drop_includes=' + ','.join(constants.asset_excludes['drop_includes']) - - # # Hard drop fields - query_params += '&' + 'hard_drop_fields=' + ','.join(constants.asset_excludes['hard_drop_fields']) - - # Excluded fields - # query_params += '&' + 'excluded_fields=' + ','.join(constants.asset_excludes['excluded_fields']) - - # # Sort by inserted_at - # query_params += '&' + 'sort=-inserted_at' - - endpoint += query_params - - # print("Final URL", endpoint) - - if len(include) > 0: - endpoint += '&include={}'.format(include.join(',')) - - return self.client._api_call('get', endpoint, kwargs) - - if len(include) > 0: - endpoint += '?include={}'.format(include.join(',')) - ->>>>>>> - return self.client._api_call('get', endpoint, kwargs) - - @Reference(operation="#createAsset") - def create(self, parent_asset_id, **kwargs): - """ - Create an asset. - - Args: - parent_asset_id (string): The parent asset id. - - :Keyword Arguments: - (optional) kwargs: additional request parameters. - - Example:: - - client.assets.create( - parent_asset_id="123abc", - name="ExampleFile.mp4", - type="file", - filetype="video/mp4", - filesize=123456 - ) - """ - endpoint = '/assets/{}/children'.format(parent_asset_id) - return self.client._api_call('post', endpoint, payload=kwargs) - -<<<<<<< - -======= - @Reference(operation="#createAsset") - def create_folder(self, parent_asset_id, name="New Folder"): - """ - Create a new folder. - - Args: - parent_asset_id (string): The parent asset id. - name (string): The name of the new folder. - - Example:: - - client.assets.create_folder( - parent_asset_id="123abc", - name="ExampleFile.mp4", - ) - """ - endpoint = '/assets/{}/children'.format(parent_asset_id) - return self.client._api_call('post', endpoint, payload={"name": name, "type":"folder"}) - - @Reference(operation="#createAsset") ->>>>>>> - def from_url(self, parent_asset_id, name, url): - """ - Create an asset from a URL. - - Args: - parent_asset_id (str): The parent asset id. - name (str): The filename. - url (str): The remote URL. - - Example:: - - client.assets.from_url( - parent_asset_id="123abc", - name="ExampleFile.mp4", - type="file", - url="https://" - ) - """ - - payload = { - "name": name, - "type": "file", - "source": { - "url": url - } - } - - endpoint = '/assets/{}/children'.format(parent_asset_id) - return self.client._api_call('post', endpoint, payload=payload) - - @Reference(operation="#updateAsset") - def update(self, asset_id, **kwargs): - """ - Updates an asset - - Args: - asset_id (string): the asset's id - - :Keyword Arguments: - kwargs (optional): fields and values you wish to update - - Example:: - - client.assets.update("adeffee123342", name="updated_filename.mp4") - """ - endpoint = '/assets/{}'.format(asset_id) - return self.client._api_call('put', endpoint, kwargs) - - @Reference(operation="#copyAsset") - def copy(self, destination_folder_id, **kwargs): - """ - Copy an Asset - - Args: - destination_folder_id (str): The id of the folder you want to copy into. - - :Keyword Arguments: - id (str): The id of the asset you want to copy. - - Example:: - - client.assets.copy("adeffee123342", id="7ee008c5-49a2-f8b5-997d-8b64de153c30") - """ - endpoint = '/assets/{}/copy'.format(destination_folder_id) - return self.client._api_call('post', endpoint, kwargs) - -<<<<<<< - def bulk_copy(self, destination_folder_id, asset_list=[], copy_comments=False): -======= - @Reference(operation="#batchCopyAsset") - def bulk_copy(self, destination_folder_id, asset_list, copy_comments=False): ->>>>>>> - """Bulk copy assets - - Args: - destination_folder_id (string): The id of the folder you want to copy into. - - :Keyword Arguments: - asset_list (list): A list of the asset IDs you want to copy. - copy_comments (boolean): Whether or not to copy comments: True or False. - - Example:: - - client.assets.bulk_copy("adeffee123342", - asset_list=[ - "7ee008c5-49a2-f8b5-997d-8b64de153c30", - "7ee008c5-49a2-f8b5-997d-8b64de153c30" - ], - copy_comments=True - ) - """ - - payload = {"batch": []} - new_list = list() - - if copy_comments: - payload['copy_comments'] = "all" - - for asset in asset_list: - payload['batch'].append({"id": asset}) - - endpoint = '/batch/assets/{}/copy'.format(destination_folder_id) - return self.client._api_call('post', endpoint, payload) - - @Reference(operation="#deleteAsset") - def delete(self, asset_id): - """ - Delete an asset - - Args: - asset_id (string): the asset's id - """ - endpoint = '/assets/{}'.format(asset_id) - return self.client._api_call('delete', endpoint) - - def _upload(self, asset, file): - """ - Upload an asset. The method will exit once the file is uploaded. - - Args: - asset (object): The asset object. - file (file): The file to upload. - - Example:: - - client._upload(asset, open('example.mp4')) - """ - - uploader = FrameioUploader(asset, file) - uploader.upload() - - # def upload_folder(sFelf, destination_id, folderpath): - # try: - # if os.path.isdir(folderpath): - # # Good it's a directory, we can keep going - # pass - - # except OSError: - # if not os.path.exists(folderpath): - # sys.exit("Folder doesn't exist, exiting...") - - def build_asset_info(self, filepath): - full_path = os.path.abspath(filepath) - - file_info = { - "filepath": full_path, - "filename": os.path.basename(full_path), - "filesize": os.path.getsize(full_path), - "mimetype": mimetypes.guess_type(full_path)[0] - } - - return file_info - - def upload(self, destination_id, filepath, asset=None): - """ - Upload a file. The method will exit once the file is downloaded. - - Args: - destination_id (uuid): The destination Project or Folder ID. - filepath (string): The locaiton of the file on your local filesystem \ - that you want to upload. - - Example:: - - client.assets.upload('1231-12414-afasfaf-aklsajflaksjfla', "./file.mov") - """ - - # Check if destination is a project or folder - # If it's a project, well then we look up its root asset ID, otherwise we use the folder id provided - # Then we start our upload - - try: - # First try to grab it as a folder - folder_id = self.get(destination_id)['id'] - except Exception as e: - # Then try to grab it as a project - folder_id = Project(self.client).get_project(destination_id)['root_asset_id'] - finally: - file_info = self.build_asset_info(filepath) - - if not asset: - try: - asset = self.create(folder_id, - type="file", - name=file_info['filename'], - filetype=file_info['mimetype'], - filesize=file_info['filesize'] - ) - - except Exception as e: - print(e) - - try: - with open(file_info['filepath'], "rb") as fp: - self._upload(asset, fp) - - except Exception as e: - print(e) - - return asset - - def download(self, asset, download_folder, prefix=None, multi_part=False, concurrency=5): - """ - Download an asset. The method will exit once the file is downloaded. - - Args: - asset (object): The asset object. - download_folder (path): The location to download the file to. - - Example:: - - client.assets.download(asset, "~./Downloads") - """ - downloader = FrameioDownloader(asset, download_folder, prefix, multi_part, concurrency) - return downloader.download_handler() diff --git a/frameioclient/service/projects.py b/frameioclient/service/projects.py deleted file mode 100644 index e8a78942..00000000 --- a/frameioclient/service/projects.py +++ /dev/null @@ -1,134 +0,0 @@ -from .service import Service -from .helpers import FrameioHelpers - -class Project(Service): - def create(self, team_id, **kwargs): - """Create a project. - - Args: - team_id (string): The team id. - - :Keyword Arguments: - (optional) kwargs: additional request parameters. - - Example:: - - client.projects.create( - team_id="123", - name="My Awesome Project" - ) - """ - endpoint = '/teams/{}/projects'.format(team_id) - return self.client._api_call('post', endpoint, payload=kwargs) - - def get(self, project_id): - """ - Get an individual project - - Args: - project_id (string): The project's id - - Example:: - - client.project.get( - project_id="123" - ) - """ - endpoint = '/projects/{}'.format(project_id) - return self.client._api_call('get', endpoint) - - def tree(self, project_id, slim): - """ - Fetch a tree representation of all files/folders in a project. - - Args: - project_id (string): The project's id - slim (bool): If true, fetch only the minimum information for the following: \ - filename, \ - filesize, \ - thumbnail, \ - creator_id, \ - inserted_at (date created), \ - path (represented like a filesystem) - - Example:: - - client.projects.get( - project_id="123", - slim=True - ) - """ - # endpoint = "/projects/{}/tree?depth=20&drop_includes=a.transcode_statuses,a.transcodes,a.source,a.checksums&only_fields=a.name,a.filesize,u.name,a.item_count,a.creator_id,a.inserted_at,a.uploaded_at".format(project_id) - # return self.client._api_call('get', endpoint) - - return FrameioHelpers(self.client).build_project_tree(project_id, slim) - - - def get_collaborators(self, project_id, **kwargs): - """ - Get collaborators for a project. - - Args: - project_id (uuid): The project's id. - - Example:: - - client.projects.get_collaborators( - project_id="123" - ) - """ - endpoint = "/projects/{}/collaborators?include=project_role".format(project_id) - return self.client._api_call('get', endpoint, kwargs) - - def get_pending_collaborators(self, project_id, **kwargs): - """ - Get pending collaborators for a project. - - Args: - project_id (uuid): The project's id. - - Example:: - - client.projects.get_pending_collaborators( - project_id="123" - ) - """ - endpoint = "/projects/{}/pending_collaborators".format(project_id) - return self.client._api_call('get', endpoint, kwargs) - - def add_collaborator(self, project_id, email): - """ - Add Collaborator to a Project Collaborator. - - Args: - project_id (uuid): The project id. - email (string): Email user's e-mail address. - - Example:: - - client.projects.add_collaborator( - project_id="123", - email="janedoe@frame.io" - ) - """ - payload = {"email": email} - endpoint = '/projects/{}/collaborators'.format(project_id) - return self._api_call('post', endpoint, payload=payload) - - def remove_collaborator(self, project_id, email): - """ - Remove Collaborator from Project. - - Args: - project_id (uuid): The Project ID. - email (string): The user's e-mail address. - - Example:: - - client.projects.remove_collaborator( - project_id="123", - email="janedoe@frame.io" - ) - """ - endpoint = '/projects/{}/collaborators/_?email={}'.format(project_id, email) - return self._api_call('delete', endpoint) diff --git a/frameioclient/service/service.py b/frameioclient/service/service.py deleted file mode 100644 index a2ffa123..00000000 --- a/frameioclient/service/service.py +++ /dev/null @@ -1,5 +0,0 @@ -from ..client import FrameioClient - -class Service(object): - def __init__(self, client: FrameioClient): - self.client = client diff --git a/frameioclient/services/assets.py b/frameioclient/services/assets.py index 064406a0..5d2de6b8 100644 --- a/frameioclient/services/assets.py +++ b/frameioclient/services/assets.py @@ -4,7 +4,7 @@ from .projects import Project from ..lib.service import Service -from ..lib import FrameioUploader, FrameioDownloader, constants +from ..lib import FrameioUploader, FrameioDownloader, constants, Reference class Asset(Service): def _build_asset_info(self, filepath): @@ -19,6 +19,7 @@ def _build_asset_info(self, filepath): return file_info + @Reference(operation="#getAsset") def get(self, asset_id): """ Get an asset by id. @@ -29,6 +30,7 @@ def get(self, asset_id): endpoint = '/assets/{}'.format(asset_id) return self.client._api_call('get', endpoint) + @Reference(operation="#getAssets") def get_children(self, asset_id, include=[], slim=False, **kwargs): """ Get a folder. @@ -78,6 +80,7 @@ def get_children(self, asset_id, include=[], slim=False, **kwargs): return self.client._api_call('get', endpoint, kwargs) + @Reference(operation="#createAsset") def create(self, parent_asset_id, **kwargs): """ Create an asset. @@ -100,6 +103,7 @@ def create(self, parent_asset_id, **kwargs): endpoint = '/assets/{}/children'.format(parent_asset_id) return self.client._api_call('post', endpoint, payload=kwargs) + @Reference(operation="#createAsset") def create_folder(self, parent_asset_id, name="New Folder"): """ Create a new folder. @@ -118,6 +122,7 @@ def create_folder(self, parent_asset_id, name="New Folder"): endpoint = '/assets/{}/children'.format(parent_asset_id) return self.client._api_call('post', endpoint, payload={"name": name, "type":"folder"}) + @Reference(operation="#createAsset") def from_url(self, parent_asset_id, name, url): """ Create an asset from a URL. @@ -147,6 +152,7 @@ def from_url(self, parent_asset_id, name, url): endpoint = '/assets/{}/children'.format(parent_asset_id) return self.client._api_call('post', endpoint, payload=payload) + @Reference(operation="#updateAsset") def update(self, asset_id, **kwargs): """ Updates an asset @@ -162,6 +168,7 @@ def update(self, asset_id, **kwargs): endpoint = '/assets/{}'.format(asset_id) return self.client._api_call('put', endpoint, kwargs) + @Reference(operation="#copyAsset") def copy(self, destination_folder_id, **kwargs): """ Copy an asset @@ -177,6 +184,7 @@ def copy(self, destination_folder_id, **kwargs): endpoint = '/assets/{}/copy'.format(destination_folder_id) return self.client._api_call('post', endpoint, kwargs) + @Reference(operation="#batchCopyAsset") def bulk_copy(self, destination_folder_id, asset_list=[], copy_comments=False): """Bulk copy assets @@ -201,6 +209,7 @@ def bulk_copy(self, destination_folder_id, asset_list=[], copy_comments=False): endpoint = '/batch/assets/{}/copy'.format(destination_folder_id) return self.client._api_call('post', endpoint, payload) + @Reference(operation="#deleteAsset") def delete(self, asset_id): """ Delete an asset From 2c05ca4f37b6bc2580bc8aa75ee9dbf07569058a Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Mon, 2 Aug 2021 21:04:03 -0700 Subject: [PATCH 3/7] Fix CI config --- .circleci/config.yml | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2d510f62..d589e07f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ workflows: - docs: requires: - # - deploy + - deploy - build # upload_test: @@ -138,3 +138,30 @@ jobs: cd /tmp/artifact/docs python publish.py + deploy: + docker: + - image: circleci/python:latest + + steps: + - attach_workspace: + at: /tmp/artifact + name: Attach build artifact + + - run: + name: Install dependencies + command: | + pip install setuptools wheel twine + + - run: + name: init .pypirc + command: | + cd /tmp/artifact + echo -e "[pypi]" >> ~/.pypirc + echo -e "username = $TWINE_USERNAME" >> ~/.pypirc + echo -e "password = $TWINE_PASSWORD" >> ~/.pypirc + + - run: + name: Upload to pypi + command: | + cd /tmp/artifact + twine upload dist/* From 14d730af8b363093263dc87c9b7da481d9328165 Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Mon, 2 Aug 2021 21:08:26 -0700 Subject: [PATCH 4/7] Restore streaming download (RAM friendly) --- frameioclient/lib/download.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/frameioclient/lib/download.py b/frameioclient/lib/download.py index ef2b56ea..4883053c 100644 --- a/frameioclient/lib/download.py +++ b/frameioclient/lib/download.py @@ -140,31 +140,29 @@ def get_download_key(self): def download_handler(self): """Call this to perform the actual download of your asset! """ + + # Check folders if os.path.isdir(os.path.join(os.path.curdir, self.download_folder)): print("Folder exists, don't need to create it") else: print("Destination folder not found, creating") os.mkdir(self.download_folder) + # Check files if os.path.isfile(self.get_path()) == False: pass -<<<<<<< if os.path.isfile(self.get_path()) and self.replace == True: os.remove(self.get_path()) if os.path.isfile(self.get_path()) and self.replace == False: -======= - def download_handler(self): - """Call this to perform the actual download of your asset! - """ - if os.path.isfile(self.get_path()): ->>>>>>> print("File already exists at this location.") return self.destination + # Get URL url = self.get_download_key() + # Handle watermarking if self.watermarked == True: return self.single_part_download(url) else: @@ -180,6 +178,10 @@ def single_part_download(self, url): start_time = time.time() print("Beginning download -- {} -- {}".format(self.asset["name"], Utils.format_bytes(self.file_size, type="size"))) + # Downloading + r = self.session.get(url, stream=True) + open(self.destination, "wb").write(r.content) + # Downloading with open(self.destination, 'wb') as handle: try: From 5585c6bc6a47c7deb17c9fbe047aac774ef8497a Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Mon, 2 Aug 2021 21:12:37 -0700 Subject: [PATCH 5/7] Remove merge straggler --- frameioclient/client.py | 74 ----------------------------------------- 1 file changed, 74 deletions(-) diff --git a/frameioclient/client.py b/frameioclient/client.py index ddb0f976..6312a598 100644 --- a/frameioclient/client.py +++ b/frameioclient/client.py @@ -9,7 +9,6 @@ ClientVersion, ClientVersion, FrameioDownloader, - PresentationException ) class FrameioClient(APIClient, object): @@ -24,79 +23,6 @@ def me(self): def telemetry(self): return Telemetry(self) -<<<<<<< - -======= - self.adapter = HTTPAdapter(max_retries=self.retry_strategy) - self.session = requests.Session() - self.session.mount("https://", self.adapter) - - def _api_call(self, method, endpoint, payload={}, limit=None): - url = '{}/v2{}'.format(self.host, endpoint) - - r = self.session.request( - method, - url, - json=payload, - headers=self.headers, - ) - - if r.ok: - if r.headers.get('page-number'): - if int(r.headers.get('total-pages')) > 1: - return PaginatedResponse( - results=r.json(), - limit=limit, - page_size=r.headers['per-page'], - total_pages=r.headers['total-pages'], - total=r.headers['total'], - endpoint=endpoint, - method=method, - payload=payload, - client=self - ) - if isinstance(r.json(), list): - return r.json()[:limit] - return r.json() - - if r.status_code == 422 and "presentation" in endpoint: - raise PresentationException - - return r.raise_for_status() - - def get_specific_page(self, method, endpoint, payload, page): - """ - Gets a specific page for that endpoint, used by Pagination Class - - :Args: - method (string): 'get', 'post' - endpoint (string): endpoint ('/accounts//teams') - payload (dict): Request payload - page (int): What page to get - """ - if method == 'get': - # If we've already got a ? in the endpoint, then it has to be an & - if '?' in endpoint: - endpoint = '{}&page={}'.format(endpoint, page) - else: - endpoint = '{}?page={}'.format(endpoint, page) - return self._api_call(method, endpoint) - - if method == 'post': - payload['page'] = page - return self._api_call(method, endpoint, payload=payload) - - -class FrameioClient(FrameioConnection): - """[summary] - - Args: - FrameioConnection ([type]): [description] - - Returns: - [type]: [description] - """ ->>>>>>> @property def _auth(self): return self.token From 291fff211c04d67a96ccfb41f7dfaac099fd5302 Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Mon, 2 Aug 2021 21:17:29 -0700 Subject: [PATCH 6/7] Refactor examples, and add one more for RBC --- examples/{ => assets}/asset_scraper.py | 0 examples/{ => assets}/asset_tree.py | 0 examples/{ => assets}/recursive_upload.py | 0 examples/{ => assets}/upload_asset.py | 0 examples/{ => comments}/comment_scraper.py | 0 examples/comments/range_based_comment.py | 15 +++++++++++++++ examples/new_tests.py | 20 -------------------- examples/{ => projects}/download_project.py | 0 examples/{ => projects}/project_tree.py | 0 examples/{ => users}/get_me.py | 0 examples/{ => users}/invite_users.py | 0 examples/{ => users}/user_management.py | 0 12 files changed, 15 insertions(+), 20 deletions(-) rename examples/{ => assets}/asset_scraper.py (100%) rename examples/{ => assets}/asset_tree.py (100%) rename examples/{ => assets}/recursive_upload.py (100%) rename examples/{ => assets}/upload_asset.py (100%) rename examples/{ => comments}/comment_scraper.py (100%) create mode 100644 examples/comments/range_based_comment.py delete mode 100644 examples/new_tests.py rename examples/{ => projects}/download_project.py (100%) rename examples/{ => projects}/project_tree.py (100%) rename examples/{ => users}/get_me.py (100%) rename examples/{ => users}/invite_users.py (100%) rename examples/{ => users}/user_management.py (100%) diff --git a/examples/asset_scraper.py b/examples/assets/asset_scraper.py similarity index 100% rename from examples/asset_scraper.py rename to examples/assets/asset_scraper.py diff --git a/examples/asset_tree.py b/examples/assets/asset_tree.py similarity index 100% rename from examples/asset_tree.py rename to examples/assets/asset_tree.py diff --git a/examples/recursive_upload.py b/examples/assets/recursive_upload.py similarity index 100% rename from examples/recursive_upload.py rename to examples/assets/recursive_upload.py diff --git a/examples/upload_asset.py b/examples/assets/upload_asset.py similarity index 100% rename from examples/upload_asset.py rename to examples/assets/upload_asset.py diff --git a/examples/comment_scraper.py b/examples/comments/comment_scraper.py similarity index 100% rename from examples/comment_scraper.py rename to examples/comments/comment_scraper.py diff --git a/examples/comments/range_based_comment.py b/examples/comments/range_based_comment.py new file mode 100644 index 00000000..4e0c75c4 --- /dev/null +++ b/examples/comments/range_based_comment.py @@ -0,0 +1,15 @@ +import os +from frameioclient import FrameioClient + +def leave_range_based_comment(asset_id, comment): + client = FrameioClient(os.getenv("FRAME_IO_TOKEN")) + res = client.create_comment( + asset_id=asset_id, + text="This is my range based comment", + timestamp=1911, + duration=3.5 + ) + + +if __name__ == "__main__": + leave_range_based_comment("id", "this is my comment!") \ No newline at end of file diff --git a/examples/new_tests.py b/examples/new_tests.py deleted file mode 100644 index 7b1fac6e..00000000 --- a/examples/new_tests.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -from pprint import pprint - -from frameioclient import FrameioClient, Asset, ClientVersion - - -token = os.getenv('FRAMEIO_TOKEN') -client = FrameioClient(token) -folder_id = 'dd8526ee-2c7d-4b48-9bf7-b847664666bb' -file_path = '/Users/jeff/Code/python-frameio-client/examples/downloads/accelerated_Test_Chart_5_Sec_embedded_meta_Mezzanine.mxf' - -client.assets.upload(folder_id, file_path) - - -print(client.users.get_me()) - -pprint(client.teams.list_projects(client.teams.list_all()[0]['id'])) - -for log in client.logs.list(client.users.get_me()['account_id']): - print(log) \ No newline at end of file diff --git a/examples/download_project.py b/examples/projects/download_project.py similarity index 100% rename from examples/download_project.py rename to examples/projects/download_project.py diff --git a/examples/project_tree.py b/examples/projects/project_tree.py similarity index 100% rename from examples/project_tree.py rename to examples/projects/project_tree.py diff --git a/examples/get_me.py b/examples/users/get_me.py similarity index 100% rename from examples/get_me.py rename to examples/users/get_me.py diff --git a/examples/invite_users.py b/examples/users/invite_users.py similarity index 100% rename from examples/invite_users.py rename to examples/users/invite_users.py diff --git a/examples/user_management.py b/examples/users/user_management.py similarity index 100% rename from examples/user_management.py rename to examples/users/user_management.py From 97cd98b80ef1c8992665d1e111b6ce850ce507a9 Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Mon, 2 Aug 2021 21:18:00 -0700 Subject: [PATCH 7/7] Update range-based comment example --- examples/comments/range_based_comment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/comments/range_based_comment.py b/examples/comments/range_based_comment.py index 4e0c75c4..61cb7331 100644 --- a/examples/comments/range_based_comment.py +++ b/examples/comments/range_based_comment.py @@ -3,13 +3,15 @@ def leave_range_based_comment(asset_id, comment): client = FrameioClient(os.getenv("FRAME_IO_TOKEN")) - res = client.create_comment( + res = client.comments.create( asset_id=asset_id, text="This is my range based comment", timestamp=1911, duration=3.5 ) + print(res) + if __name__ == "__main__": leave_range_based_comment("id", "this is my comment!") \ No newline at end of file