Skip to content

Commit 12161cd

Browse files
committed
Add CLI for uploading/downloading assets
Fix issue with (auth=True) in _get_session() Abstract API call formatter Remove duplicate code Add support for include on get_asset_children() Convert build_asset_info to private method Add missing kwargs for pagination (chore): Add pull request template Add CLI Add fiocli <- command line uploader/downloader
1 parent 755afe6 commit 12161cd

File tree

20 files changed

+359
-687
lines changed

20 files changed

+359
-687
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## [DEVREL-XXXX]
2+
3+
### Description:
4+
Please provide a short description of what this PR does
5+
6+
### Depends on:
7+
- Does this PR depend on any other ones?
8+
9+
### Includes changes from:
10+
- Does this PR includ changes from another PR?
11+
12+
### I'd like feedback on:
13+
- What would you like feedback on?

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ $ git clone https://github.com/frameio/python-frameio-client
2424
$ pip install .
2525
```
2626

27+
### Developing
28+
Install the package into your development environment and link to it by running the following:
29+
30+
```sh
31+
pipenv install -e . -pre
32+
```
33+
2734
## Documentation
2835

2936
[Frame.io API Documentation](https://developer.frame.io/docs)
@@ -34,6 +41,27 @@ _Note: A valid token is required to make requests to Frame.io. Go to our [Develo
3441

3542
In addition to the snippets below, examples are included in [/examples](/examples).
3643

44+
### Use CLI
45+
When you install this package, a cli tool called `fioctl` will also be installed to your environment.
46+
47+
**To upload a file or folder**
48+
```sh
49+
fioctl \
50+
--token fio-u-YOUR_TOKEN_HERE \
51+
--destination "YOUR TARGET FRAME.IO PROJECT OR FOLDER" \
52+
--target "YOUR LOCAL SYSTEM DIRECTORY" \
53+
--threads 8
54+
```
55+
56+
**To download a file, project, or folder**
57+
```sh
58+
fioctl \
59+
--token fio-u-YOUR_TOKEN_HERE \
60+
--destination "YOUR LOCAL SYSTEM DIRECTORY" \
61+
--target "YOUR TARGET FRAME.IO PROJECT OR FOLDER" \
62+
--threads 2
63+
```
64+
3765
### Get User Info
3866

3967
Get basic info on the authenticated user.

examples/recursive_upload.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import os
2+
import time
3+
import mimetypes
4+
import concurrent.futures
5+
import threading
6+
from frameioclient import FrameioClient
7+
from pprint import pprint
8+
9+
global file_num
10+
file_num = 0
11+
12+
global file_count
13+
file_count = 0
14+
15+
def create_n_upload(task):
16+
client=task[0]
17+
file_p=task[1]
18+
parent_asset_id=task[2]
19+
abs_path = os.path.abspath(file_p)
20+
file_s = os.path.getsize(file_p)
21+
file_n = os.path.split(file_p)[1]
22+
file_mime = mimetypes.guess_type(abs_path)[0]
23+
24+
asset = client.create_asset(
25+
parent_asset_id=parent_asset_id,
26+
name=file_n,
27+
type="file",
28+
filetype=file_mime,
29+
filesize=file_s
30+
)
31+
32+
with open(abs_path, "rb") as ul_file:
33+
asset_info = client.upload(asset, ul_file)
34+
35+
return asset_info
36+
37+
38+
def create_folder(folder_n, parent_asset_id):
39+
asset = client.create_asset(
40+
parent_asset_id=parent_asset_id,
41+
name=folder_n,
42+
type="folder",
43+
)
44+
45+
return asset['id']
46+
47+
48+
def file_counter(root_folder):
49+
matches = []
50+
for root, dirnames, filenames in os.walk(root_folder):
51+
for filename in filenames:
52+
matches.append(os.path.join(filename))
53+
54+
return matches
55+
56+
57+
def recursive_upload(client, folder, parent_asset_id):
58+
# Seperate files and folders:
59+
file_list = list()
60+
folder_list = list()
61+
62+
for item in os.listdir(folder):
63+
if item == ".DS_Store": # Ignore .DS_Store files on Mac
64+
continue
65+
66+
complete_item_path = os.path.join(folder, item)
67+
68+
if os.path.isfile(complete_item_path):
69+
file_list.append(item)
70+
else:
71+
folder_list.append(item)
72+
73+
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
74+
for file_p in file_list:
75+
global file_num
76+
file_num += 1
77+
print(f"Starting {file_num}/{file_count}")
78+
complete_dir_obj = os.path.join(folder, file_p)
79+
task = (client, complete_dir_obj, parent_asset_id)
80+
executor.submit(create_n_upload, task)
81+
82+
for folder_i in folder_list:
83+
new_folder = os.path.join(folder, folder_i)
84+
new_parent_asset_id = create_folder(folder_i, parent_asset_id)
85+
recursive_upload(client, new_folder, new_parent_asset_id)
86+
87+
88+
if __name__ == "__main__":
89+
root_folder = "./test_structure"
90+
parent_asset_id = "PARENT_ASSET_ID"
91+
client = FrameioClient(os.getenv("FRAME_IO_TOKEN"))
92+
93+
file_count = len(file_counter(root_folder))
94+
recursive_upload(client, root_folder, parent_asset_id)

examples/upload_asset.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import os
12
from frameioclient import FrameioClient
23

34
def main():
4-
client = FrameioClient("TOKEN")
5-
file_path = './file_to_upload.mov'
6-
parent_asset_id = ''
5+
client = FrameioClient(os.getenv('FRAMEIO_TOKEN'))
6+
file_path = '/Users/jeff/downloads/📼 Video/🎨 Graded Dailies/A001C034_210518_ROGH.mov'
7+
parent_asset_id = 'b91433a2-8c9d-4da4-a9dd-80649cb91adf'
78

8-
asset = client.assets.upload(parent_asset_id,file_path)
9+
# asset = client.assets.upload(parent_asset_id,file_path)
10+
client.assets.upload(parent_asset_id, file_path)
911

1012
if __name__ == "__main__":
1113
main()

frameioclient/__init__.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
11
from .lib import *
2-
from .services import *
3-
from .client import FrameioClient
4-
<<<<<<<
5-
6-
=======
72
from .service import *
8-
from .lib import *
9-
>>>>>>>
3+
from .client import FrameioClient

frameioclient/client.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
from .lib import (
2-
Utils,
32
APIClient,
4-
AWSClient,
53
Telemetry,
64
ClientVersion,
75
ClientVersion,
86
FrameioDownloader
97
)
108

119
class FrameioClient(APIClient, object):
12-
def __init__(self, token, host):
13-
super().__init__(token, host)
10+
def __init__(self, token, host='https://api.frame.io', threads=5, progress=False):
11+
super().__init__(token, host, threads, progress)
1412

1513
@property
1614
def me(self):
@@ -34,41 +32,40 @@ def _download(self):
3432

3533
@property
3634
def users(self):
37-
from .services import User
35+
from .service import User
3836
return User(self)
3937

4038
@property
4139
def assets(self):
42-
from .services import Asset
40+
from .service import Asset
4341
return Asset(self)
44-
total=3,
4542

4643
@property
4744
def comments(self):
48-
from .services import Comment
45+
from .service import Comment
4946
return Comment(self)
5047

5148
@property
5249
def logs(self):
53-
from .services import AuditLogs
50+
from .service import AuditLogs
5451
return AuditLogs(self)
5552

5653
@property
5754
def review_links(self):
58-
from .services import ReviewLink
55+
from .service import ReviewLink
5956
return ReviewLink(self)
6057

6158
@property
6259
def presentation_links(self):
63-
from .services import PresentationLink
60+
from .service import PresentationLink
6461
return PresentationLink(self)
6562

6663
@property
6764
def projects(self):
68-
from .services import Project
65+
from .service import Project
6966
return Project(self)
7067

7168
@property
7269
def teams(self):
73-
from .services import Team
70+
from .service import Team
7471
return Team(self)

frameioclient/fiocli.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
import sys
3+
import argparse
4+
5+
from frameioclient import FrameioClient
6+
7+
8+
def main():
9+
parser=argparse.ArgumentParser(prog='fiocli', description='Frame.io Python SDK CLI')
10+
11+
## Define args
12+
parser.add_argument('--token', action='store', metavar='token', type=str, nargs='+', help='Developer Token')
13+
# parser.add_argument('--op', action='store', metavar='op', type=str, nargs='+', help='Operation: upload, download')
14+
parser.add_argument('--target', action='store', metavar='target', type=str, nargs='+', help='Target: remote project or folder, or alternatively a local file/folder')
15+
parser.add_argument('--destination', action='store', metavar='destination', type=str, nargs='+', help='Destination: remote project or folder, or alternatively a local file/folder')
16+
parser.add_argument('--threads', action='store', metavar='threads', type=int, nargs='+', help='Number of threads to use')
17+
18+
## Parse args
19+
args = parser.parse_args()
20+
21+
if args.threads:
22+
threads = args.threads[0]
23+
else:
24+
threads = 5
25+
26+
## Handle args
27+
if args.token:
28+
client = None
29+
# print(args.token)
30+
try:
31+
client = FrameioClient(args.token[0], progress=True, threads=threads)
32+
except Exception as e:
33+
print("Failed")
34+
sys.exit(1)
35+
36+
# If args.op == 'upload':
37+
if args.target:
38+
if args.destination:
39+
# Check to see if this is a local target and thus a download
40+
if os.path.isdir(args.destination[0]):
41+
asset = client.assets.get(args.target[0])
42+
return client.assets.download(asset, args.destination[0], progress=True, multi_part=True, concurrency=threads)
43+
else: # This is an upload
44+
if os.path.isdir(args.target[0]):
45+
return client.assets.upload_folder(args.target[0], args.destination[0])
46+
else:
47+
return client.assets.upload(args.destination[0], args.target[0])
48+
else:
49+
print("No destination supplied")
50+
else:
51+
print("No target supplied")
52+

frameioclient/lib/download.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def __init__(self, asset, download_folder, prefix, multi_part=False, concurrency
4242
self.in_progress = 0
4343
self.filename = Utils.normalize_filename(asset["name"])
4444
self.request_logs = list()
45-
self.session = AWSClient()._get_session(auth=None)
45+
self.session = AWSClient()._get_session()
4646

4747
self._evaluate_asset()
4848

@@ -135,7 +135,7 @@ def single_part_download(self, url):
135135
print("Beginning download -- {} -- {}".format(self.asset["name"], Utils.format_bytes(self.file_size, type="size")))
136136

137137
# Downloading
138-
r = requests.get(url)
138+
r = self.session.get(url)
139139
open(self.destination, "wb").write(r.content)
140140

141141
download_time = time.time() - start_time

0 commit comments

Comments
 (0)