Skip to content

Commit 21995d9

Browse files
committed
split upload photo into upload_job_photo and upload_profile_photo. updated docs
1 parent 780eb07 commit 21995d9

File tree

3 files changed

+81
-46
lines changed

3 files changed

+81
-46
lines changed

README.md

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
# Skylab Studio Python Client
22

3-
[![CircleCI](https://circleci.com/gh/skylab-tech/studio_client_python.svg?style=svg)](https://circleci.com/gh/skylab-tech/studio_client_python)
4-
[![Maintainability](https://api.codeclimate.com/v1/badges/6e3316f60d72a9ca9276/maintainability)](https://codeclimate.com/github/skylab-tech/studio_client_python/maintainability)
5-
[![Test Coverage](https://api.codeclimate.com/v1/badges/6e3316f60d72a9ca9276/test_coverage)](https://codeclimate.com/github/skylab-tech/studio_client_python/test_coverage)
6-
73
SkylabTech Studio Python client.
84

95
[studio.skylabtech.ai](https://studio.skylabtech.ai)
@@ -18,6 +14,36 @@ SkylabTech Studio Python client.
1814
$ pip install skylab_studio
1915
```
2016

17+
## Example usage
18+
19+
```python
20+
# CREATE PROFILE
21+
payload = {
22+
"name": "profile name",
23+
}
24+
25+
api.create_profile(payload=payload)
26+
27+
# CREATE JOB
28+
payload={
29+
"name": "job name",
30+
"profile_id": profile_id
31+
}
32+
33+
job = api.create_job(payload)
34+
35+
# UPLOAD JOB PHOTO(S)
36+
filePath = "/path/to/photo"
37+
api.upload_job_photo(filePath, job.id)
38+
39+
# QUEUE JOB
40+
payload = { "callback_url" = "YOUR_CALLBACK_ENDPOINT" }
41+
api.queue_job(job.id, payload)
42+
43+
# NOTE: Once the job is queued, it will get processed then complete
44+
# We will send a response to the specified callback_url with the output photo download urls
45+
```
46+
2147
## Usage
2248

2349
For all examples, assume:
@@ -148,26 +174,20 @@ api.list_photos()
148174
api.get_photo(photo_id)
149175
```
150176

151-
#### Upload photo
177+
#### Upload job photo
152178

153179
This function handles validating a photo, creating a photo object and uploading it to your job/profile's s3 bucket. If the bucket upload process fails, it retries 3 times and if failures persist, the photo object is deleted.
154180

155181
```python
156-
upload_photo(photo_path, model, model_id)
182+
api.upload_job_photo(photo_path, job_id)
157183
```
158184

159-
model can either be 'job' or 'profile'
160-
161-
model_id is the jobs/profiles respective id
162-
163-
```python
164-
api.upload_photo('/path/to/photo', 'job', job_id)
165-
```
185+
#### Upload profile photo
166186

167-
OR
187+
This function handles validating a background photo for a profile. Note: enable_extract and replace_background (profile attributes) MUST be true in order to create background photos. Follows the same upload process as upload_job_photo.
168188

169189
```python
170-
api.upload_photo('/path/to/photo', 'profile', profile_id)
190+
api.upload_profile_photo(photo_path, profile_id)
171191
```
172192

173193
`Returns: { photo: { photo_object }, upload_response: bucket_upload_response_status }`

skylab_studio/__init__.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,14 @@ def calculate_md5(self, file_path):
271271
for chunk in iter(lambda: file.read(4096), b""):
272272
md5_hash.update(chunk)
273273
return md5_hash.hexdigest()
274+
275+
def upload_job_photo(self, photo_path, id):
276+
return self._upload_photo(photo_path, id, 'job')
274277

275-
def upload_photo(self, photo_path, model, id):
278+
def upload_profile_photo(self, photo_path, id):
279+
return self._upload_photo(photo_path, id, 'profile')
280+
281+
def _upload_photo(self, photo_path, id, model='job'):
276282
res = {}
277283
valid_exts_to_check = ('.jpg', '.jpeg', '.png', '.webp')
278284
if not photo_path.lower().endswith(valid_exts_to_check):
@@ -294,19 +300,18 @@ def upload_photo(self, photo_path, model, id):
294300
photo_data = { f"{model}_id": id, "name": photo_name, "use_cache_upload": False }
295301

296302
if model == 'job':
297-
job_type = self.get_job(id).json()['type']
303+
job_type = self.get_job(id)['type']
298304

299305
if job_type == 'regular':
300306
headers = { 'X-Amz-Tagging': 'job=photo&api=true' }
301307

302308
# Ask studio to create the photo record
303309
photo_resp = self._create_photo(photo_data)
310+
if not photo_resp:
311+
raise Exception('Unable to create the photo object, if creating profile photo, ensure enable_extract and replace_background is set to: True')
304312

305-
if photo_resp.status_code != 201:
306-
raise Exception('Unable to create the photo object')
307-
308-
photo_id = photo_resp.json()['id']
309-
res['photo'] = photo_resp.json()
313+
photo_id = photo_resp['id']
314+
res['photo'] = photo_resp
310315

311316
# md5 = self.calculate_md5(photo_path)
312317
b64md5 = base64.b64encode(bytes.fromhex(md5hash)).decode('utf-8')
@@ -318,22 +323,22 @@ def upload_photo(self, photo_path, model, id):
318323

319324
# Ask studio for a presigned url
320325
upload_url_resp = self._get_upload_url(payload=payload)
321-
upload_url = upload_url_resp.json()['url']
326+
upload_url = upload_url_resp['url']
322327

323328
# PUT request to presigned url with image data
324329
headers["Content-MD5"] = b64md5
325330

326331
try:
327332
upload_photo_resp = requests.put(upload_url, data, headers=headers)
328333

329-
if upload_photo_resp.status_code != 200:
334+
if not upload_photo_resp:
330335
print('First upload attempt failed, retrying...')
331336
retry = 0
332337
# retry upload
333338
while retry < 3:
334339
upload_photo_resp = requests.put(upload_url, data, headers=headers)
335340

336-
if upload_photo_resp.status_code == 200:
341+
if upload_photo_resp:
337342
break # Upload was successful, exit the loop
338343
elif retry == 2: # Check if retry count is 2 (0-based indexing)
339344
raise Exception('Unable to upload to the bucket after retrying.')

test/test_api.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_api_debug():
4545
def test_list_jobs(api):
4646
""" Test list jobs endpoint. """
4747
result = api.list_jobs()
48-
assert result.status_code == 200
48+
assert result is not None
4949

5050
def test_create_job(api):
5151
""" Test list jobs endpoint. """
@@ -58,16 +58,16 @@ def test_create_job(api):
5858
result = api.create_job(payload=job_payload)
5959
global job_id
6060

61-
job_id = result.json()['id']
61+
job_id = result['id']
6262
assert job_id is not None
63-
assert result.status_code == 201
63+
assert result is not None
6464

6565
def test_get_job(api):
6666
global job_id
6767
assert job_id is not 0
6868

6969
result = api.get_job(job_id)
70-
assert result.status_code == 200
70+
assert result is not None
7171

7272
def test_update_job(api):
7373
global job_id
@@ -76,7 +76,7 @@ def test_update_job(api):
7676
'name': new_job_name
7777
}
7878
result = api.update_job(job_id, payload=payload)
79-
assert result.status_code == 200
79+
assert result is not None
8080

8181
def test_cancel_job(api):
8282
job_name = str(uuid.uuid4())
@@ -86,9 +86,10 @@ def test_cancel_job(api):
8686
}
8787

8888
result = api.create_job(payload=job_payload)
89-
job_id = result.json()['id']
89+
90+
job_id = result['id']
9091
job_cancel_result = api.cancel_job(job_id)
91-
assert job_cancel_result.status_code == 200
92+
assert job_cancel_result is not None
9293

9394
def test_delete_job(api):
9495
job_name = str(uuid.uuid4())
@@ -98,62 +99,71 @@ def test_delete_job(api):
9899
}
99100

100101
result = api.create_job(payload=job_payload)
101-
job_id = result.json()['id']
102+
job_id = result['id']
102103

103104
result = api.delete_job(job_id)
104-
assert result.status_code == 200
105+
assert result is not None
105106

106107

107108
def test_list_profiles(api):
108109
result = api.list_profiles()
109-
assert result.status_code == 200
110+
assert result is not None
110111

111112
def test_create_profile(api):
112113
global profile_id
113114
profile_name = str(uuid.uuid4())
114115
payload = {
115116
'name': profile_name,
116-
'enable_crop': False
117+
'enable_crop': False,
118+
'enable_extract': True,
119+
'replace_background': True
117120
}
118121
result = api.create_profile(payload=payload)
119-
profile_id = result.json()['id']
122+
profile_id = result['id']
120123

121124
assert profile_id is not None
122-
assert result.status_code == 201
125+
assert result is not None
123126

124-
def test_upload_photo(api, pytestconfig):
127+
def test_upload_job_photo(api, pytestconfig):
125128
global job_id
126129
global photo_id
127130

128-
result = api.upload_photo(f"{pytestconfig.rootdir}/test/test-portrait-1.JPG", 'job', job_id)
131+
result = api.upload_job_photo(f"{pytestconfig.rootdir}/test/test-portrait-1.JPG", job_id)
129132

130-
assert result['photo']['id'] is not None
131133
photo_id = result['photo']['id']
132134
assert result['upload_response'] == 200
133135

136+
def test_upload_profile_photo(api, pytestconfig):
137+
global profile_id
138+
139+
result = api.upload_profile_photo(f"{pytestconfig.rootdir}/test/test-portrait-1.JPG", profile_id)
140+
141+
assert result['upload_response'] == 200
142+
143+
134144
def test_get_profile(api):
135145
global profile_id
136146
result = api.get_profile(profile_id)
137-
assert result.status_code == 200
147+
assert result is not None
138148

139149
def test_update_profile(api):
140150
global profile_id
141151
payload = {
142152
'description': 'a description!'
143153
}
144154
result = api.update_profile(profile_id, payload=payload)
145-
assert result.status_code == 200
155+
assert result is not None
146156

147157
def test_list_photos(api):
148158
result = api.list_photos()
149-
assert result.status_code == 200
159+
assert result is not None
150160

151161
def test_get_photo(api):
152162
global photo_id
153163
result = api.get_photo(photo_id)
154-
assert result.status_code == 200
164+
assert result is not None
155165

156166
def test_delete_photo(api):
157167
global photo_id
158168
result = api.delete_photo(photo_id)
159-
assert result.status_code == 200
169+
assert result is not None

0 commit comments

Comments
 (0)