Repository: gdcc/pyDataverse (language: python)

Issue:
API Key and User-Agent should preferrably be passed as headers
### Issue

The API key and User-Agent are currently passed as GET parameters, ideally they should be passed as headers. User-Agents are commonly passed as headers, and for the key this is [actually preferred](https://guides.dataverse.org/en/latest/api/auth.html).
It also is slightly more secure, as the dataverse API will return the full request URL on error, possibly exposing API keys in logs:

```json
{"status":"ERROR","code":404,"message":"API endpoint does not exist on this server. Please check your code for typos, or consult our API guide at http://guides.dataverse.org.","requestUrl":"http://localhost:8080/api/v1/access/datafile/:persistentId/?persistentId=23&User-Agent=pydataverse&key=<the API token>","requestMethod":"GET"}
```
(taken from datalad/datalad-dataverse#310, where I found out about this; it also contains a little bit of context)

I am not sure if this request is a bug or feature, so I decided to open a normal issue. The pyDataverse version I use is 0.3.3, and the code doing this is in pyDataverse/api.py:

https://github.com/gdcc/pyDataverse/blob/3b6fe063dfc73d6fa3aa674cc02313504c40204f/pyDataverse/api.py#L103-L138

The same is done for post_request, etc.

A possible change would be to use headers and pass them as headers:
```python
        headers = {}
        headers["User-Agent"] = "pydataverse"
        if self.api_token:
            headers["X-Dataverse-key"] = str(self.api_token)

        if self.client is None:
            return self._sync_request(
                method=httpx.get,
                url=url,
                headers=headers,
            )
```

Additionally, I noticed that neither the `params` nor the `auth` attributes are used.

For the params a fix could be, especially after moving the key and user-agent to the headers:

```python
self._sync_request(
    method=httpx.get,
    url=url,
    headers=headers,
    params=params or {},  # maybe httpx.get even handles params=None, in that case they could simply be passed through
)
```

For the auth parameter it's a bit more difficult, since it currently does not affect the behavior although it should.
One possible way would be to update the if statement before adding the token to the header:
```
if auth and self.api_token:
```
However, it might make sense to change the default value to True in this case, to keep the behavior stable. However, I am not 100% sure what is the best way to handle this. Of course, one option would also be to remove the auth argument completely.

Lastly, the docstring of the function mentions the requests.Response, while it's now a httpx.Response. While they do have a similar interface, they have some minor differences; e.g., requests has an .ok property and uses iter_content, while httpx does not have .ok and uses iter_bytes.

I think I could try to "fix" this and draft a PR.

dataverse:unstable increased the version to 6.3
### Prerequisites

* [x] Are you running the expected version of pyDataverse? (check via `pip freeze`).

main branch

### Bug report

The :unstable docker image now ships dataverse 6.3, but DV_VERSION in the docker-compose-test-all.yml is pinned to 6.2, causing several tests to fail.

**1. Describe your environment**

* [x] OS: Ubuntu (I think... in the container)
* [x] pyDataverse: main branch
* [x] Python: 3.11 (in the container)
* [x] Dataverse: 6.3 (!)

**2. Actual behaviour:**

Some tests fail (here one example):
```
_______________________ TestApiToken.test_token_missing ________________________

self = <tests.api.test_api.TestApiToken object at 0xffff8b262fd0>

    def test_token_missing(self):
        BASE_URL = os.getenv("BASE_URL").rstrip("/")
        api = NativeApi(BASE_URL)
        resp = api.get_info_version()
>       assert resp.json()["data"]["version"] == os.getenv("DV_VERSION")
E       AssertionError: assert '6.3' == '6.2'
E         
E         - 6.2
E         + 6.3

tests/api/test_api.py:75: AssertionError
```

**3. Expected behaviour:**

The tests pass.

**4. Steps to reproduce**

1. `sh run-tests.sh`.

❌ Unit tests failed. Printing logs...

**5. Possible solution**

- Update DV_VERSION in docker-compose-test-all.yml to 6.3.
- Do not use :unstable but pin to a specific image version. Since Dataverse only tags two versions, I guess a hash might work instead.
- Ask if Dataverse's docker images can be tagged with the versions, so that we could even test against different versions easily, for example to handle different API changes.

I will provide a PR to increase the version to 6.3, as described in the first variant. I don't think the second is feasible right now, but I think it might be worth exploring the third option, though I really don't know what the status of the Dataverse development in general is on that matter.
Run pre-commit run --all to unify code formatting
While preparing the PR for #192, I ran `pre-commit run --all` and then decided to revert those changes and instead run it before I'd make my changes, i.e., on the main branch directly. The result will be in the PR for this issue.

Note that I ran the tests locally after running `pre-commit run --all`, so I guess even though it changed lots of data files and even some docs, this should still be fine. (Except I had to change the DV_VERSION, but that's on a separate branch. See #195)


Discussion / hints:
Thanks for pointing these issues out, @shoeffner! I completely agree – the API Key should be included in the headers, and it looks like `auth` and `params` are either redundant or unused. Here’s what I’m thinking:

* Option 1: We put the API-Token directly into the headers and remove the auth parameter.
* Option 2: We create a custom class using `httpx.Auth` to add the key to the header, like in this [example](https://www.python-httpx.org/advanced/authentication/). The auth parameter stays, and if it’s None, our custom class handles authentication.

The custom class seems more future-proof, especially if Dataverse introduces new authentication methods. The direct fix is quicker, but I’m leaning towards the custom class and keeping auth.

As for the `params` argument, we haven’t used it yet since query parameters are added to the URL string directly. However, I think we’re missing out on some great httpx features that could make our code more readable and stable. This would mean refactoring most endpoints, and since we’ve already discussed a refactor in #189, I suggest we include this in the `api.py` update.
Reworking `auth` to be non-boolean but instead httpx.Auth or None sounds like a great idea! In fact, in this case it might be as simple as adding 
> A callable, accepting a request and returning an authenticated request instance,
so there might not be a class required after all to get the mechanism going.

I can't say much about the refactoring in #189, as I am not familiar with the code base in general. But I agree that using a dict for params instead of manually building up the URLs is a good idea.

Let me know if I can help with anything.


Great, that sounds like a plan! Would you be willing to open a PR for this issue? I’d be happy to help out with it.

By the way, we talked about this in today’s PyDataverse Working Group meeting with @pdurbin and @donsizemore. You can watch the recording [here](https://drive.google.com/file/d/1Jkk1RWPwV_6YW3KdkrZFFEfL6MlFLta9/view?usp=share_link) and check out our upcoming meetings on [py.gdcc.io](https://py.gdcc.io/) if you'd like to join next time. For discussions outside of these meetings, we also have a [Zulip channel](https://dataverse.zulipchat.com/#narrow/stream/377090-python/topic/PyDataverse.20Re-Vamp) for everything related to Python and Dataverse.
Thanks @JR-1991, I'll take a chance implementing that.
I checked the recording (here's my gist from the relevant parts of the recording, for those in a rush and cannot access the notes or do not want to go through the recording):

- It's in general a good idea to refactor the authentication to use httpx.Auth
- While using the header (X-Dataverse-key) is a good first step, an even better but "create-new-issue-and-check-out-in-the-future" way is to use [signed URLs](https://guides.dataverse.org/en/latest/api/external-tools.html#signed-urls)

There was not much talk about the User-Agent or params, but as @JR-1991 already mentioned that the params might become part of #189, I'll leave that part out and focus on the first step for the authentication, that is using the X-Dataverse-key header.
> focus on the first step for the authentication, that is using the X-Dataverse-key header

Sounds perfect. Thanks, @shoeffner! ❤️ 

You're welcome to test against https://demo.dataverse.org

Or you can run Dataverse in Docker, as mentioned in the README.
Thanks, I'll focus on the docker-setup for the moment, and once it's done and green, I'll go for the live instance :)



Fix patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 0dbe275..d434203 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -29,7 +29,7 @@ First, start always by reading the [Contribution Guide](https://pydataverse.read
 * [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/gdcc/pyDataverse/pulls) for the same update/change?
 * [ ] Have you followed the guidelines in our [Contribution Guide](https://pydataverse.readthedocs.io/en/master/contributing/contributing.html)?
 * [ ] Have you read the [Code of Conduct](https://github.com/gdcc/pyDataverse/blob/master/CODE_OF_CONDUCT.md)?
-* [ ] Do your changes in a seperate branch. Branches MUST have descriptive names.
+* [ ] Do your changes in a separate branch. Branches MUST have descriptive names.
 * [ ] Have you merged the latest changes from upstream to your branch?
 
 **Describe the PR**
@@ -45,7 +45,7 @@ First, start always by reading the [Contribution Guide](https://pydataverse.read
 
 * [ ] Have you used tox and/or pytest for testing the changes?
 * [ ] Did the local testing ran successfully?
-* [ ] Did the Continous Integration testing (Travis-CI) ran successfully?
+* [ ] Did the Continuous Integration testing (Travis-CI) ran successfully?
 
 **Commits**
 


Test patch:
diff --git a/local-test.env b/local-test.env
index 7400574..1eeeaa0 100644
--- a/local-test.env
+++ b/local-test.env
@@ -6,4 +6,4 @@ CONFIGBAKER_IMAGE=docker.io/gdcc/configbaker:unstable
 
 # Services
 POSTGRES_VERSION=15
-SOLR_VERSION=9.3.0
\ No newline at end of file
+SOLR_VERSION=9.3.0
diff --git a/tests/api/test_async_api.py b/tests/api/test_async_api.py
index 61b1910..12c4e3a 100644
--- a/tests/api/test_async_api.py
+++ b/tests/api/test_async_api.py
@@ -3,10 +3,8 @@
 
 
 class TestAsyncAPI:
-
     @pytest.mark.asyncio
     async def test_async_api(self, native_api):
-
         async with native_api:
             tasks = [native_api.get_info_version() for _ in range(10)]
             responses = await asyncio.gather(*tasks)
diff --git a/tests/data/dataset_upload_full_default.json b/tests/data/dataset_upload_full_default.json
index 848dd1b..6ab8614 100644
--- a/tests/data/dataset_upload_full_default.json
+++ b/tests/data/dataset_upload_full_default.json
@@ -437,13 +437,18 @@
             "typeName": "kindOfData",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["KindOfData1", "KindOfData2"]
+            "value": [
+              "KindOfData1",
+              "KindOfData2"
+            ]
           },
           {
             "typeName": "language",
             "multiple": true,
             "typeClass": "controlledVocabulary",
-            "value": ["German"]
+            "value": [
+              "German"
+            ]
           },
           {
             "typeName": "series",
@@ -489,25 +494,37 @@
             "typeName": "relatedMaterial",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["RelatedMaterial1", "RelatedMaterial2"]
+            "value": [
+              "RelatedMaterial1",
+              "RelatedMaterial2"
+            ]
           },
           {
             "typeName": "relatedDatasets",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["RelatedDatasets1", "RelatedDatasets2"]
+            "value": [
+              "RelatedDatasets1",
+              "RelatedDatasets2"
+            ]
           },
           {
             "typeName": "otherReferences",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["OtherReferences1", "OtherReferences2"]
+            "value": [
+              "OtherReferences1",
+              "OtherReferences2"
+            ]
           },
           {
             "typeName": "dataSources",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["DataSources1", "DataSources2"]
+            "value": [
+              "DataSources1",
+              "DataSources2"
+            ]
           },
           {
             "typeName": "originOfSources",
@@ -569,7 +586,10 @@
             "typeName": "geographicUnit",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["GeographicUnit1", "GeographicUnit2"]
+            "value": [
+              "GeographicUnit1",
+              "GeographicUnit2"
+            ]
           },
           {
             "typeName": "geographicBoundingBox",
@@ -613,13 +633,19 @@
             "typeName": "unitOfAnalysis",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["UnitOfAnalysis1", "UnitOfAnalysis2"]
+            "value": [
+              "UnitOfAnalysis1",
+              "UnitOfAnalysis2"
+            ]
           },
           {
             "typeName": "universe",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["Universe1", "Universe2"]
+            "value": [
+              "Universe1",
+              "Universe2"
+            ]
           },
           {
             "typeName": "timeMethod",
@@ -776,25 +802,39 @@
             "typeName": "astroType",
             "multiple": true,
             "typeClass": "controlledVocabulary",
-            "value": ["Image", "Mosaic", "EventList", "Cube"]
+            "value": [
+              "Image",
+              "Mosaic",
+              "EventList",
+              "Cube"
+            ]
           },
           {
             "typeName": "astroFacility",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["Facility1", "Facility2"]
+            "value": [
+              "Facility1",
+              "Facility2"
+            ]
           },
           {
             "typeName": "astroInstrument",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["Instrument1", "Instrument2"]
+            "value": [
+              "Instrument1",
+              "Instrument2"
+            ]
           },
           {
             "typeName": "astroObject",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["Object1", "Object2"]
+            "value": [
+              "Object1",
+              "Object2"
+            ]
           },
           {
             "typeName": "resolution.Spatial",
@@ -818,13 +858,19 @@
             "typeName": "coverage.Spectral.Bandpass",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["Bandpass1", "Bandpass2"]
+            "value": [
+              "Bandpass1",
+              "Bandpass2"
+            ]
           },
           {
             "typeName": "coverage.Spectral.CentralWavelength",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["3001", "3002"]
+            "value": [
+              "3001",
+              "3002"
+            ]
           },
           {
             "typeName": "coverage.Spectral.Wavelength",
@@ -900,7 +946,10 @@
             "typeName": "coverage.Spatial",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["SkyCoverage1", "SkyCoverage2"]
+            "value": [
+              "SkyCoverage1",
+              "SkyCoverage2"
+            ]
           },
           {
             "typeName": "coverage.Depth",
@@ -1007,7 +1056,10 @@
             "typeName": "studyAssayOtherOrganism",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["OtherOrganism1", "OtherOrganism2"]
+            "value": [
+              "OtherOrganism1",
+              "OtherOrganism2"
+            ]
           },
           {
             "typeName": "studyAssayMeasurementType",
@@ -1024,7 +1076,10 @@
             "typeName": "studyAssayOtherMeasurmentType",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["OtherMeasurementType1", "OtherMeasurementType2"]
+            "value": [
+              "OtherMeasurementType1",
+              "OtherMeasurementType2"
+            ]
           },
           {
             "typeName": "studyAssayTechnologyType",
@@ -1052,7 +1107,10 @@
             "typeName": "studyAssayCellType",
             "multiple": true,
             "typeClass": "primitive",
-            "value": ["CellType1", "CellType2"]
+            "value": [
+              "CellType1",
+              "CellType2"
+            ]
           }
         ]
       },
diff --git a/tests/data/file_upload_ds_minimum.json b/tests/data/file_upload_ds_minimum.json
index 7746005..4611b81 100644
--- a/tests/data/file_upload_ds_minimum.json
+++ b/tests/data/file_upload_ds_minimum.json
@@ -1,75 +1,75 @@
 {
-    "datasetVersion": {
-        "metadataBlocks": {
-            "citation": {
-                "fields": [
-                    {
-                        "multiple": true,
-                        "typeClass": "compound",
-                        "typeName": "author",
-                        "value": [
-                            {
-                                "authorName": {
-                                    "multiple": false,
-                                    "typeClass": "primitive",
-                                    "typeName": "authorName",
-                                    "value": "John Doe"
-                                }
-                            }
-                        ]
-                    },
-                    {
-                        "multiple": true,
-                        "typeClass": "compound",
-                        "typeName": "datasetContact",
-                        "value": [
-                            {
-                                "datasetContactName": {
-                                    "multiple": false,
-                                    "typeClass": "primitive",
-                                    "typeName": "datasetContactName",
-                                    "value": "John Doe"
-                                },
-                                "datasetContactEmail": {
-                                    "multiple": false,
-                                    "typeClass": "primitive",
-                                    "typeName": "datasetContactEmail",
-                                    "value": "john@doe.com"
-                                }
-                            }
-                        ]
-                    },
-                    {
-                        "multiple": true,
-                        "typeClass": "compound",
-                        "typeName": "dsDescription",
-                        "value": [
-                            {
-                                "dsDescriptionValue": {
-                                    "multiple": false,
-                                    "typeClass": "primitive",
-                                    "typeName": "dsDescriptionValue",
-                                    "value": "This is a description of the dataset"
-                                }
-                            }
-                        ]
-                    },
-                    {
-                        "multiple": true,
-                        "typeClass": "controlledVocabulary",
-                        "typeName": "subject",
-                        "value": [
-                            "Other"
-                        ]
-                    },
-                    {
-                        "multiple": false,
-                        "typeClass": "primitive",
-                        "typeName": "title",
-                        "value": "My dataset"
-                    }
-                ]
-            }
-        }
+  "datasetVersion": {
+    "metadataBlocks": {
+      "citation": {
+        "fields": [
+          {
+            "multiple": true,
+            "typeClass": "compound",
+            "typeName": "author",
+            "value": [
+              {
+                "authorName": {
+                  "multiple": false,
+                  "typeClass": "primitive",
+                  "typeName": "authorName",
+                  "value": "John Doe"
+                }
+              }
+            ]
+          },
+          {
+            "multiple": true,
+            "typeClass": "compound",
+            "typeName": "datasetContact",
+            "value": [
+              {
+                "datasetContactName": {
+                  "multiple": false,
+                  "typeClass": "primitive",
+                  "typeName": "datasetContactName",
+                  "value": "John Doe"
+                },
+                "datasetContactEmail": {
+                  "multiple": false,
+                  "typeClass": "primitive",
+                  "typeName": "datasetContactEmail",
+                  "value": "john@doe.com"
+                }
+              }
+            ]
+          },
+          {
+            "multiple": true,
+            "typeClass": "compound",
+            "typeName": "dsDescription",
+            "value": [
+              {
+                "dsDescriptionValue": {
+                  "multiple": false,
+                  "typeClass": "primitive",
+                  "typeName": "dsDescriptionValue",
+                  "value": "This is a description of the dataset"
+                }
+              }
+            ]
+          },
+          {
+            "multiple": true,
+            "typeClass": "controlledVocabulary",
+            "typeName": "subject",
+            "value": [
+              "Other"
+            ]
+          },
+          {
+            "multiple": false,
+            "typeClass": "primitive",
+            "typeName": "title",
+            "value": "My dataset"
+          }
+        ]
+      }
     }
+  }
 }
diff --git a/tests/data/replace.xyz b/tests/data/replace.xyz
index 6065e4c..4436af4 100644
--- a/tests/data/replace.xyz
+++ b/tests/data/replace.xyz
@@ -1,3 +1,3 @@
 A B C
 3.4292 -4.32647 -1.66819
-3.4292 -4.51647 -1.65310
\ No newline at end of file
+3.4292 -4.51647 -1.65310
diff --git a/tests/models/test_datafile.py b/tests/models/test_datafile.py
index 55793da..7fec1c5 100644
--- a/tests/models/test_datafile.py
+++ b/tests/models/test_datafile.py
@@ -1,4 +1,5 @@
 """Datafile data model tests."""
+
 import json
 import jsonschema
 import os
@@ -244,7 +245,10 @@ def test_datafile_from_json_valid(self):
             ),
             (({json_upload_min()}, {"validate": False}), object_data_min()),
             (
-                ({json_upload_min()}, {"filename_schema": "wrong", "validate": False},),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 object_data_min(),
             ),
             (
@@ -345,7 +349,10 @@ def test_datafile_to_json_valid(self):
                 json.loads(json_upload_min()),
             ),
             (
-                (dict_flat_set_min(), {"filename_schema": "wrong", "validate": False},),
+                (
+                    dict_flat_set_min(),
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 json.loads(json_upload_min()),
             ),
             (
@@ -517,7 +524,10 @@ def test_dataverse_from_json_to_json_valid(self):
                 ({json_upload_full()}, {}),
                 ({json_upload_min()}, {"data_format": "dataverse_upload"}),
                 ({json_upload_min()}, {"validate": False}),
-                ({json_upload_min()}, {"filename_schema": "wrong", "validate": False},),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 (
                     {json_upload_min()},
                     {
@@ -550,4 +560,6 @@ def test_dataverse_from_json_to_json_valid(self):
 
                 for key, val in pdv_end.get().items():
                     assert getattr(pdv_start, key) == getattr(pdv_end, key)
-                assert len(pdv_start.__dict__) == len(pdv_end.__dict__,)
+                assert len(pdv_start.__dict__) == len(
+                    pdv_end.__dict__,
+                )
diff --git a/tests/models/test_dataset.py b/tests/models/test_dataset.py
index fb01d0f..601bb15 100644
--- a/tests/models/test_dataset.py
+++ b/tests/models/test_dataset.py
@@ -1,4 +1,5 @@
 """Dataset data model tests."""
+
 import json
 import os
 import platform
@@ -950,11 +951,17 @@ def test_dataset_from_json_valid(self):
             ),
             (({json_upload_min()}, {"validate": False}), object_data_min()),
             (
-                ({json_upload_min()}, {"filename_schema": "", "validate": False},),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "", "validate": False},
+                ),
                 object_data_min(),
             ),
             (
-                ({json_upload_min()}, {"filename_schema": "wrong", "validate": False},),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 object_data_min(),
             ),
             (
@@ -995,11 +1002,17 @@ def test_dataset_to_json_valid(self):
                 json.loads(json_upload_min()),
             ),
             (
-                (dict_flat_set_min(), {"filename_schema": "", "validate": False},),
+                (
+                    dict_flat_set_min(),
+                    {"filename_schema": "", "validate": False},
+                ),
                 json.loads(json_upload_min()),
             ),
             (
-                (dict_flat_set_min(), {"filename_schema": "wrong", "validate": False},),
+                (
+                    dict_flat_set_min(),
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 json.loads(json_upload_min()),
             ),
             (
@@ -1167,7 +1180,10 @@ def test_dataset_to_json_from_json_valid(self):
                 (dict_flat_set_full(), {}),
                 (dict_flat_set_min(), {"data_format": "dataverse_upload"}),
                 (dict_flat_set_min(), {"validate": False}),
-                (dict_flat_set_min(), {"filename_schema": "wrong", "validate": False},),
+                (
+                    dict_flat_set_min(),
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 (
                     dict_flat_set_min(),
                     {
@@ -1180,7 +1196,6 @@ def test_dataset_to_json_from_json_valid(self):
             ]
 
             for data_set, kwargs_from in data:
-
                 kwargs = {}
                 pdv_start = data_object()
                 pdv_start.set(data_set)
@@ -1200,4 +1215,6 @@ def test_dataset_to_json_from_json_valid(self):
 
                 for key, val in pdv_end.get().items():
                     assert getattr(pdv_start, key) == getattr(pdv_end, key)
-                assert len(pdv_start.__dict__) == len(pdv_end.__dict__,)
+                assert len(pdv_start.__dict__) == len(
+                    pdv_end.__dict__,
+                )
diff --git a/tests/models/test_dataverse.py b/tests/models/test_dataverse.py
index a4c362e..946fdc6 100644
--- a/tests/models/test_dataverse.py
+++ b/tests/models/test_dataverse.py
@@ -1,4 +1,5 @@
 """Dataverse data model tests."""
+
 import json
 import jsonschema
 import os
@@ -296,11 +297,17 @@ def test_dataverse_from_json_valid(self):
             ),
             (({json_upload_min()}, {"validate": False}), object_data_min()),
             (
-                ({json_upload_min()}, {"filename_schema": "", "validate": False},),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "", "validate": False},
+                ),
                 object_data_min(),
             ),
             (
-                ({json_upload_min()}, {"filename_schema": "wrong", "validate": False},),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 object_data_min(),
             ),
             (
@@ -401,11 +408,17 @@ def test_dataverse_to_json_valid(self):
                 json.loads(json_upload_min()),
             ),
             (
-                (dict_flat_set_min(), {"filename_schema": "", "validate": False},),
+                (
+                    dict_flat_set_min(),
+                    {"filename_schema": "", "validate": False},
+                ),
                 json.loads(json_upload_min()),
             ),
             (
-                (dict_flat_set_min(), {"filename_schema": "wrong", "validate": False},),
+                (
+                    dict_flat_set_min(),
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 json.loads(json_upload_min()),
             ),
             (
@@ -583,8 +596,14 @@ def test_dataverse_from_json_to_json_valid(self):
                 ({json_upload_full()}, {}),
                 ({json_upload_min()}, {"data_format": "dataverse_upload"}),
                 ({json_upload_min()}, {"validate": False}),
-                ({json_upload_min()}, {"filename_schema": "", "validate": False},),
-                ({json_upload_min()}, {"filename_schema": "wrong", "validate": False},),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "", "validate": False},
+                ),
+                (
+                    {json_upload_min()},
+                    {"filename_schema": "wrong", "validate": False},
+                ),
                 (
                     {json_upload_min()},
                     {
@@ -614,4 +633,6 @@ def test_dataverse_from_json_to_json_valid(self):
 
                 for key, val in pdv_end.get().items():
                     assert getattr(pdv_start, key) == getattr(pdv_end, key)
-                assert len(pdv_start.__dict__) == len(pdv_end.__dict__,)
+                assert len(pdv_start.__dict__) == len(
+                    pdv_end.__dict__,
+                )
diff --git a/tests/models/test_dvobject.py b/tests/models/test_dvobject.py
index 26e96d3..9164011 100644
--- a/tests/models/test_dvobject.py
+++ b/tests/models/test_dvobject.py
@@ -1,4 +1,5 @@
 """Dataverse data model tests."""
+
 from pyDataverse.models import DVObject