Skip to content

Commit cec3ac9

Browse files
authored
fix: Fix a csvimport error 'List index (0) out of bounds' (#120)
1 parent cb0e474 commit cec3ac9

File tree

6 files changed

+129
-16
lines changed

6 files changed

+129
-16
lines changed

.pre-commit-config.yaml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ repos:
6464
exclude: ^tests
6565

6666
- repo: https://github.com/python-jsonschema/check-jsonschema
67-
rev: 0.33.0
67+
rev: 0.33.2
6868
hooks:
6969
- id: check-github-workflows
7070

@@ -90,7 +90,7 @@ repos:
9090
- --ignore-init-module-imports
9191

9292
- repo: https://github.com/pycqa/flake8
93-
rev: 7.2.0
93+
rev: 7.3.0
9494
hooks:
9595
- id: flake8
9696
additional_dependencies:
@@ -108,14 +108,14 @@ repos:
108108
- --exit-zero
109109

110110
- repo: https://github.com/asottile/pyupgrade
111-
rev: v3.19.1
111+
rev: v3.20.0
112112
hooks:
113113
- id: pyupgrade
114114
args: [--py39-plus, --keep-runtime-typing]
115115

116116
- repo: https://github.com/charliermarsh/ruff-pre-commit
117117
# Ruff version.
118-
rev: v0.11.8
118+
rev: v0.12.2
119119
hooks:
120120
# Run the linter.
121121
- id: ruff
@@ -124,12 +124,13 @@ repos:
124124
- id: ruff-format
125125

126126
- repo: https://github.com/dosisod/refurb
127-
rev: v2.0.0
127+
rev: v2.1.0
128128
hooks:
129129
- id: refurb
130+
args: [--ignore, FURB184]
130131

131132
- repo: https://github.com/pre-commit/mirrors-mypy
132-
rev: v1.15.0
133+
rev: v1.16.1
133134
hooks:
134135
- id: mypy
135136
args:
@@ -139,12 +140,12 @@ repos:
139140
exclude: ^samples/
140141

141142
- repo: https://github.com/RobertCraigie/pyright-python
142-
rev: v1.1.400
143+
rev: v1.1.403
143144
hooks:
144145
- id: pyright
145146

146147
- repo: https://github.com/PyCQA/bandit
147-
rev: 1.8.3
148+
rev: 1.8.6
148149
hooks:
149150
- id: bandit
150151
args: ["-c", "pyproject.toml", "-r", "."]

mailjet_rest/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.4.0"
1+
__version__ = "1.4.0"

mailjet_rest/client.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def get(
253253

254254
def create(
255255
self,
256-
data: dict | None = None,
256+
data: str | bytes | dict[Any, Any] | None = None,
257257
filters: Mapping[str, str | Any] | None = None,
258258
id: str | None = None,
259259
action_id: str | None = None,
@@ -264,7 +264,7 @@ def create(
264264
"""Perform a POST request to create a new resource.
265265
266266
Parameters:
267-
- data (dict | None): The data to include in the request body.
267+
- data (str | bytes | dict[Any, Any] | None): The data to include in the request body.
268268
- filters (Mapping[str, str | Any] | None): Filters to be applied in the request.
269269
- id (str | None): The ID of the specific resource to be created.
270270
- action_id (str | None): The specific action ID to be performed.
@@ -275,18 +275,20 @@ def create(
275275
Returns:
276276
- Response: The response object from the API call.
277277
"""
278-
json_data: str | bytes | None = None
279278
if self.headers.get("Content-type") == "application/json" and data is not None:
280-
json_data = json.dumps(data, ensure_ascii=ensure_ascii)
279+
data = json.dumps(
280+
data,
281+
ensure_ascii=ensure_ascii,
282+
)
281283
if not ensure_ascii:
282-
json_data = json_data.encode(data_encoding)
284+
data = data.encode(data_encoding)
283285
return api_call(
284286
self._auth,
285287
"post",
286288
self._url,
287289
headers=self.headers,
288290
resource_id=id,
289-
data=json_data,
291+
data=data, # type: ignore[arg-type]
290292
action=self.action,
291293
action_id=action_id,
292294
filters=filters,
@@ -406,7 +408,7 @@ def __getattr__(self, name: str) -> Any:
406408
- Endpoint: An instance of the `Endpoint` class, initialized with the constructed URL, headers, action, and authentication details.
407409
"""
408410
name_regex: str = re.sub(r"[A-Z]", prepare_url, name)
409-
split: list[str] = name_regex.split("_") # noqa: RUF100, FURB184
411+
split: list[str] = name_regex.split("_") # noqa: RUF100
410412
# identify the resource
411413
fname: str = split[0]
412414
action: str | None = None

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ ignore = [
236236
"S311", # S311 Standard pseudo-random generators are not suitable for cryptographic purposes
237237
# TODO: T201 Replace `print` with logging functions
238238
"T201", # T201 `print` found
239+
"PLC0207", # PLC0207 Accessing only the first or last element of `str.split()` without setting `maxsplit=1`
240+
239241
]
240242

241243

test.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import random
55
import string
66
import unittest
7+
from pathlib import Path
78
from typing import Any
9+
from typing import ClassVar
810

911
from mailjet_rest import Client
1012

@@ -216,5 +218,106 @@ def test_user_agent(self) -> None:
216218
self.assertEqual(self.client.config.user_agent, "mailjet-apiv3-python/v1.4.0")
217219

218220

221+
class TestCsvImport(unittest.TestCase):
222+
"""Tests for Mailjet API csv import functionality.
223+
224+
This class provides setup and teardown functionality for tests involving the
225+
csv import functionality, with authentication and client initialization handled
226+
in `setUp`. Each test in this suite operates with the configured Mailjet client
227+
instance to simulate API interactions.
228+
229+
Attributes:
230+
- _shared_state (dict[str, str]): A dictionary containing values taken from tests to share them in other tests.
231+
"""
232+
233+
_shared_state: ClassVar[dict[str, Any]] = {}
234+
235+
@classmethod
236+
def get_shared(cls, key: str) -> Any:
237+
"""Retrieve a value from shared test state.
238+
239+
Parameters:
240+
- key (str): The key to look up in shared state.
241+
242+
Returns:
243+
- Any: The stored value, or None if key doesn't exist.
244+
"""
245+
return cls._shared_state.get(key)
246+
247+
@classmethod
248+
def set_shared(cls, key: str, value: Any) -> None:
249+
"""Store a value in shared test state.
250+
251+
Parameters:
252+
- key (str): The key to store the value under.
253+
- value (Any): The value to store.
254+
"""
255+
cls._shared_state[key] = value
256+
257+
def setUp(self) -> None:
258+
"""Set up the test environment by initializing authentication credentials and the Mailjet client.
259+
260+
This method is called before each test to ensure a consistent testing
261+
environment. It retrieves the API keys and ID_CONTACTSLIST from environment variables and
262+
uses them to create an instance of the Mailjet `Client` for authenticated
263+
API interactions.
264+
265+
Attributes:
266+
- self.auth (tuple[str, str]): A tuple containing the public and private API keys obtained from the environment variables 'MJ_APIKEY_PUBLIC' and 'MJ_APIKEY_PRIVATE' respectively.
267+
- self.client (Client): An instance of the Mailjet Client class, initialized with the provided authentication credentials.
268+
- self.id_contactslist (str): A string of the contacts list ID from https://app.mailjet.com/contacts
269+
"""
270+
self.auth: tuple[str, str] = (
271+
os.environ["MJ_APIKEY_PUBLIC"],
272+
os.environ["MJ_APIKEY_PRIVATE"],
273+
)
274+
self.client: Client = Client(auth=self.auth)
275+
self.id_contactslist: str = os.environ["ID_CONTACTSLIST"]
276+
277+
def test_01_upload_the_csv(self) -> None:
278+
"""Test uploading a csv file.
279+
280+
POST https://api.mailjet.com/v3/DATA/contactslist
281+
/$ID_CONTACTLIST/CSVData/text:plain
282+
"""
283+
result = self.client.contactslist_csvdata.create(
284+
id=self.id_contactslist,
285+
data=Path("tests/doc_tests/files/data.csv").read_text(encoding="utf-8"),
286+
)
287+
self.assertEqual(result.status_code, 200)
288+
289+
self.set_shared("data_id", result.json().get("ID"))
290+
data_id = self.get_shared("data_id")
291+
self.assertIsNotNone(data_id)
292+
293+
def test_02_import_csv_content_to_a_list(self) -> None:
294+
"""Test importing a csv content to a list.
295+
296+
POST https://api.mailjet.com/v3/REST/csvimport
297+
"""
298+
data_id = self.get_shared("data_id")
299+
self.assertIsNotNone(data_id)
300+
data = {
301+
"Method": "addnoforce",
302+
"ContactsListID": self.id_contactslist,
303+
"DataID": data_id,
304+
}
305+
result = self.client.csvimport.create(data=data)
306+
self.assertEqual(result.status_code, 201)
307+
self.assertIn("ID", result.json()["Data"][0])
308+
309+
self.set_shared("id_value", result.json()["Data"][0]["ID"])
310+
311+
def test_03_monitor_the_import_progress(self) -> None:
312+
"""Test getting a csv content import.
313+
314+
GET https://api.mailjet.com/v3/REST/csvimport/$importjob_ID
315+
"""
316+
result = self.client.csvimport.get(id=self.get_shared("id_value"))
317+
self.assertEqual(result.status_code, 200)
318+
self.assertIn("ID", result.json()["Data"][0])
319+
self.assertEqual(0, result.json()["Data"][0]["Errcount"])
320+
321+
219322
if __name__ == "__main__":
220323
unittest.main()

tests/doc_tests/files/data.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
address,name,subscribed,vars
2+
[email protected],Bob3,TRUE,34
3+
[email protected],Janen,TRUE,21
4+
[email protected],Pete,TRUE,44
5+
[email protected],Foo,TRUE,20

0 commit comments

Comments
 (0)