Skip to content

Commit 2dff6ea

Browse files
authored
Merge pull request #8 from imagekit-developer/add-test
test case improvement
2 parents 37552db + caf766b commit 2dff6ea

File tree

15 files changed

+654
-217
lines changed

15 files changed

+654
-217
lines changed

DEVELOPMENT.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Development Guide
2+
3+
**1. Setup dependencies**
4+
5+
```shell
6+
pip install -r requirements/requirements.txt
7+
```
8+
9+
**2. Run test cases**
10+
11+
```shell
12+
pip install -r requirements/test.txt
13+
tox -e py
14+
```
15+
16+
**3. Running the sample app**
17+
18+
```shell
19+
pip install -r sample/requirements.txt
20+
cd sample
21+
python sample.py
22+
```

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ https://ik.imagekit.io/your_imagekit_id/endpoint/default-image.jpg?tr=f-jpg%2Cpr
162162
image_url = imagekit.url({
163163
"path": "/default-image",
164164
"query_parameters": {
165-
"v": "123"
165+
"p1": "123",
166+
"p2": "345"
166167
},
167168
"transformation": [{
168169
"height": "300",

imagekitio/constants/defaults.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ class Default(enum.Enum):
99
QUERY_TRANSFORMATION_POSITION,
1010
]
1111
DEFAULT_TIMESTAMP = "9999999999"
12-
SDK_VERSION = "python-1.0.0"
12+
SDK_VERSION = "python-2.2.4"

imagekitio/constants/errors.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ class ERRORS(enum.Enum):
1010
"message": "Invalid transformationPosition parameter",
1111
help: "",
1212
}
13+
MANDATORY_SRC_OR_PATH = {
14+
"message": "Pass one of the mandatory parameter path or src"
15+
}
1316
INVALID_URL_GENERATION_PARAMETER = {"message": "Invalid url parameter", help: ""}
1417
INVALID_TRANSFORMATION_OPTIONS = {
1518
"message": "Invalid transformation parameter options",

imagekitio/file.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,8 @@ def update_file_details(self, file_id: str, options: dict):
102102
update details identified by file_id and options,
103103
which is already uploaded
104104
"""
105-
106-
if not ("tags" in options.keys() or "custom_coordinates" in options.keys()):
107-
raise ValueError(ERRORS.UPDATE_DATA_MISSING.value)
108-
if not isinstance(options.get("tags", []), list):
109-
raise ValueError(ERRORS.UPDATE_DATA_TAGS_INVALID.value)
110-
if not isinstance(options.get("custom_coordinates", ""), str):
111-
raise ValueError(ERRORS.UPDATE_DATA_COORDS_INVALID.value)
105+
if not file_id:
106+
raise TypeError(ERRORS.FILE_ID_MISSING.value)
112107
url = "{}/{}/details/".format(URL.BASE_URL.value, file_id)
113108
headers = {"Content-Type": "application/json"}
114109
headers.update(self.request.get_auth_headers())
@@ -161,7 +156,7 @@ def batch_delete(self, file_ids: list = None):
161156
response = None
162157
else:
163158
error = None
164-
response = None
159+
response = resp.json()
165160

166161
response = {"error": error, "response": response}
167162
return response
@@ -275,14 +270,18 @@ def validate_upload(options):
275270
if j not in VALID_UPLOAD_OPTIONS:
276271
return False
277272
response_list.append(snake_to_lower_camel(j))
278-
options[key] = ",".join(response_list)
273+
val = ",".join(response_list)
274+
if val:
275+
options[key] = ",".join(response_list)
279276
continue
280277
if isinstance(val, list):
281278
val = ",".join(val)
282-
options[key] = val
279+
if val:
280+
options[key] = val
283281
continue
284282
# imagekit server accepts 'true/false'
285283
elif isinstance(val, bool):
286284
val = str(val).lower()
287-
options[key] = val
285+
if val:
286+
options[key] = val
288287
return request_formatter(options)

imagekitio/resource.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,7 @@ def get_auth_headers(self):
4242
4343
:return: dictionary of encoded private key
4444
"""
45-
# checking if ':' is appearing for basic authentication
46-
# otherwise password will be required with username
47-
# see basic authentication related articles about
48-
# being authenticated only with username
49-
if self.private_key[-1] != ":":
50-
self.private_key += ":"
51-
encoded_private_key = base64.b64encode(self.private_key.encode()).decode(
45+
encoded_private_key = base64.b64encode((self.private_key + ":").encode()).decode(
5246
"utf-8"
5347
)
5448
return {"Authorization": "Basic {}".format(encoded_private_key)}

imagekitio/url.py

Lines changed: 51 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import hashlib
22
import hmac
3+
import sys
34
from datetime import datetime as dt
45
from typing import Any, Dict, List
5-
from urllib.parse import ParseResult, urlparse, urlunparse
6+
from urllib.parse import ParseResult, urlparse, urlunparse, parse_qsl, urlencode
67

78
from imagekitio.constants.defaults import Default
89
from imagekitio.constants.supported_transform import SUPPORTED_TRANS
910
from imagekitio.utils.formatter import camel_dict_to_snake_dict, flatten_dict
10-
from imagekitio.constants.supported_transform import SUPPORTED_TRANS
11+
1112
from .constants import ERRORS
1213

1314
TRANSFORMATION_PARAMETER = "tr"
@@ -33,106 +34,69 @@ def __init__(self, request_obj):
3334

3435
def generate_url(self, options: Dict = None) -> str:
3536
options = camel_dict_to_snake_dict(options)
36-
if options.get("src"):
37-
options["transformation_position"] = DEFAULT_TRANSFORMATION_POSITION
3837
extended_options = self.request.extend_url_options(options)
3938
return self.build_url(extended_options)
4039

4140
def build_url(self, options: dict) -> str:
4241
"""
4342
builds url for from all options,
4443
"""
45-
path = options.get("path", "")
46-
src = options.get("src", "")
47-
url_endpoint = options.get("url_endpoint", "")
48-
transformation_position = options.get("transformation_position")
44+
45+
path = options.get("path", "").strip("/")
46+
src = options.get("src", "").strip("/")
47+
url_endpoint = options.get("url_endpoint", "").strip("/")
48+
transformation_str = self.transformation_to_str(options.get("transformation"))
49+
transformation_position = options.get("transformation_position") or DEFAULT_TRANSFORMATION_POSITION
50+
4951
if transformation_position not in Default.VALID_TRANSFORMATION_POSITION.value:
5052
raise ValueError(ERRORS.INVALID_TRANSFORMATION_POSITION.value)
5153

52-
if src or (
53-
options.get("transformation_position") == QUERY_TRANSFORMATION_POSITION
54-
):
55-
src_param_used_for_url = True
56-
else:
57-
src_param_used_for_url = False
58-
if not (path or src):
54+
if (path is "" and src is ""):
5955
return ""
60-
result_url_dict = {"netloc": "", "path": "", "query": ""}
61-
if path:
62-
parsed_url = urlparse(path)
63-
parsed_host = urlparse(url_endpoint)
64-
result_url_dict["scheme"] = parsed_host.scheme
65-
result_url_dict["netloc"] = (parsed_host.netloc + parsed_host.path).lstrip(
66-
"/"
67-
)
68-
result_url_dict["path"] = parsed_url.path.strip("/")
6956

57+
if src:
58+
temp_url = src.strip("/")
59+
transformation_position = QUERY_TRANSFORMATION_POSITION
7060
else:
71-
parsed_url = urlparse(src)
72-
host = parsed_url.netloc
73-
if parsed_url.username:
74-
# creating host like username:[email protected] if username is there in parsed url
75-
host = "{}:{}@{}".format(
76-
parsed_url.username, parsed_url.password, parsed_url.netloc
77-
)
78-
result_url_dict["netloc"] = host
79-
result_url_dict["scheme"] = parsed_url.scheme
80-
result_url_dict["path"] = parsed_url.path
81-
src_param_used_for_url = True
82-
83-
query_params = options.get("query_parameters", {})
84-
transformation_str = self.transformation_to_str(options.get("transformation"))
85-
if transformation_str:
86-
if (
87-
transformation_position == Default.QUERY_TRANSFORMATION_POSITION.value
88-
) or src_param_used_for_url:
89-
result_url_dict["query"] = "{}={}".format(
90-
TRANSFORMATION_PARAMETER, transformation_str
61+
if transformation_position == "path":
62+
temp_url = "{}/{}:{}/{}".format(
63+
url_endpoint.strip("/"),
64+
TRANSFORMATION_PARAMETER,
65+
transformation_str.strip("/"),
66+
path.strip("/")
9167
)
92-
9368
else:
94-
result_url_dict["path"] = "{}:{}/{}".format(
95-
TRANSFORMATION_PARAMETER,
96-
transformation_str,
97-
result_url_dict["path"],
69+
temp_url = "{}/{}".format(
70+
url_endpoint.strip("/"),
71+
path.strip("/")
9872
)
9973

100-
result_url_dict["scheme"] = result_url_dict["scheme"] or "https"
74+
url_object = urlparse(temp_url.strip("/"))
10175

102-
# Signature String and Timestamp
103-
# We can do this only for URLs that are created using urlEndpoint and path parameter
104-
# because we need to know the endpoint to be able to remove it from the URL to create a signature
105-
# for the remaining. With the src parameter, we would not know the "pattern" in the URL
106-
if options.get("signed") and (not options.get("src")):
76+
query_params = dict(parse_qsl(url_object.query))
77+
query_params.update(options.get("query_parameters", {}))
78+
if transformation_position == QUERY_TRANSFORMATION_POSITION:
79+
query_params.update({"tr": transformation_str})
80+
query_params.update({"ik-sdk-version": Default.SDK_VERSION.value})
81+
82+
# Update query params
83+
url_object = url_object._replace(query=urlencode(query_params))
84+
85+
if options.get("signed"):
10786
expire_seconds = options.get("expire_seconds")
10887
private_key = options.get("private_key")
10988
expiry_timestamp = self.get_signature_timestamp(expire_seconds)
110-
111-
intermediate_url = urlunparse(
112-
result_url_dict.get(f, "") for f in ParseResult._fields
113-
)
11489
url_signature = self.get_signature(
11590
private_key=private_key,
116-
url=intermediate_url,
91+
url=url_object.geturl(),
11792
url_endpoint=url_endpoint,
11893
expiry_timestamp=expiry_timestamp,
11994
)
120-
if expiry_timestamp and (expiry_timestamp != DEFAULT_TIMESTAMP):
121-
query_params[TIMESTAMP_PARAMETER] = expiry_timestamp
122-
query_params[SIGNATURE_PARAMETER] = url_signature
123-
query_params_str = "&".join(
124-
str(k) + "=" + str(v) for k, v in query_params.items()
125-
)
126-
result_url_dict["query"] = query_params_str
127-
result_url_dict = self.prepare_dict_for_unparse(result_url_dict)
128-
generated_url = urlunparse(
129-
result_url_dict.get(f, "") for f in ParseResult._fields
130-
)
131-
if result_url_dict["query"]:
132-
generated_url = generated_url + "&sdk-version=" + Default.SDK_VERSION.value
133-
else:
134-
generated_url = generated_url + "?sdk-version=" + Default.SDK_VERSION.value
135-
return generated_url
95+
query_params.update({TIMESTAMP_PARAMETER: expiry_timestamp, SIGNATURE_PARAMETER: url_signature})
96+
# Update signature related query params
97+
url_object = url_object._replace(query=urlencode(query_params))
98+
99+
return url_object.geturl()
136100

137101
@staticmethod
138102
def get_signature_timestamp(seconds: int = None) -> int:
@@ -165,9 +129,17 @@ def get_signature(private_key, url, url_endpoint, expiry_timestamp) -> str:
165129
create signature(hashed hex key) from
166130
private_key, url, url_endpoint and expiry_timestamp
167131
"""
132+
# ensure url_endpoint has a trailing slash
133+
if url_endpoint[-1] != '/':
134+
url_endpoint += '/'
135+
136+
if isinstance(expiry_timestamp, int) and expiry_timestamp < 1:
137+
expiry_timestamp = DEFAULT_TIMESTAMP
138+
168139
replaced_url = url.replace(url_endpoint, "") + str(expiry_timestamp)
140+
169141
signature = hmac.new(
170-
key=replaced_url.encode(), msg=private_key.encode(), digestmod=hashlib.sha1
142+
key=private_key.encode(), msg=replaced_url.encode(), digestmod=hashlib.sha1
171143
)
172144
return signature.hexdigest()
173145

@@ -220,6 +192,7 @@ def transformation_to_str(transformation):
220192
)
221193
)
222194

223-
parsed_transforms.append(TRANSFORM_DELIMITER.join(parsed_transform_step))
195+
parsed_transforms.append(
196+
TRANSFORM_DELIMITER.join(parsed_transform_step))
224197

225198
return CHAIN_TRANSFORM_DELIMITER.join(parsed_transforms)

sample/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests==2.22.0

0 commit comments

Comments
 (0)