We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
transip provider needs requests>=2.27.0 in dependency spec
The update of the transip provider from #1086 included in 3.9.2 uses `from requests.exceptions import JSONDecodeError` which was first included in requests 2.27.0 but the requirements spec only requires `>=2`:
https://github.com/AnalogJ/lexicon/blob/d8b26bcfffd484f34444c91ceb3ced4a2583bd18/pyproject.toml#L43

This is passed directly through to the requirement spec in the wheel:
```
Requires-Dist: requests (>=2)
```

And means that someone who upgrades dns-lexicon from a previous version using pip will get an import error.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lexicon/providers/transip.py`
Content:
```
1 """Module provider for TransIP"""
2 import binascii
3 import json
4 import logging
5 import uuid
6 from base64 import b64decode, b64encode
7 from typing import Any, Dict, List, Optional
8 
9 import requests
10 from cryptography.hazmat.backends import default_backend
11 from cryptography.hazmat.primitives import hashes
12 from cryptography.hazmat.primitives.asymmetric import padding
13 from cryptography.hazmat.primitives.serialization import load_pem_private_key
14 from requests.exceptions import JSONDecodeError
15 
16 from lexicon.exceptions import LexiconError
17 from lexicon.providers.base import Provider as BaseProvider
18 
19 LOGGER = logging.getLogger(__name__)
20 
21 NAMESERVER_DOMAINS: List[str] = []
22 
23 API_BASE_URL = "https://api.transip.nl/v6"
24 
25 
26 def provider_parser(subparser):
27     """Configure provider parser for TransIP"""
28     subparser.add_argument(
29         "--auth-username", help="specify username for authentication"
30     )
31     subparser.add_argument(
32         "--auth-api-key",
33         help="specify the private key to use for API authentication, in PEM format: can be either "
34         "the path of the key file (eg. /tmp/key.pem) or the base64 encoded content of this "
35         "file prefixed by 'base64::' (eg. base64::eyJhbGciOyJ...)",
36     )
37     subparser.add_argument(
38         "--auth-key-is-global",
39         action="store_true",
40         help="set this flag is the private key used is a global key with no IP whitelist restriction",
41     )
42 
43 
44 class Provider(BaseProvider):
45     """
46     Provider class for TransIP
47 
48     provider_options can be overwritten by a Provider to setup custom defaults.
49     They will be overwritten by any options set via the CLI or Env.
50     order is:
51 
52     """
53 
54     def __init__(self, config):
55         super(Provider, self).__init__(config)
56         self.provider_name = "transip"
57         self.domain_id = None
58 
59         private_key_conf = self._get_provider_option("auth_api_key")
60         if private_key_conf.startswith("base64::"):
61             private_key_bytes = b64decode(private_key_conf.replace("base64::", ""))
62         else:
63             with open(
64                 private_key_conf,
65                 "rb",
66             ) as file:
67                 private_key_bytes = file.read()
68 
69         self.private_key = load_pem_private_key(private_key_bytes, password=None)
70         self.token: str
71 
72     def _authenticate(self):
73         request_body = {
74             "login": self._get_provider_option("auth_username"),
75             "nonce": uuid.uuid4().hex,
76             "global_key": self._get_provider_option("auth_key_is_global") or False,
77         }
78 
79         request_body_bytes = json.dumps(request_body).encode()
80 
81         signature = self.private_key.sign(
82             request_body_bytes,
83             padding.PKCS1v15(),
84             hashes.SHA512(),
85         )
86 
87         headers = {"Signature": b64encode(signature).decode()}
88 
89         response = requests.request(
90             "POST", f"{API_BASE_URL}/auth", json=request_body, headers=headers
91         )
92         response.raise_for_status()
93 
94         self.token = response.json()["token"]
95 
96         data = self._get(f"/domains/{self.domain}")
97 
98         self.domain_id = data["domain"]["authCode"]
99 
100     def _create_record(self, rtype: str, name: str, content: str) -> bool:
101         if not rtype or not name or not content:
102             raise Exception(
103                 "Error, rtype, name and content are mandatory to create a record."
104             )
105 
106         identifier = Provider._identifier(
107             {"type": rtype, "name": self._full_name(name), "content": content}
108         )
109 
110         if any(
111             record
112             for record in self._list_records(rtype=rtype, name=name, content=content)
113             if record["id"] == identifier
114         ):
115             LOGGER.debug("create_record (ignored, duplicate): %s", identifier)
116             return True
117 
118         data = {
119             "dnsEntry": {
120                 "type": rtype,
121                 "name": self._relative_name(name),
122                 "content": content,
123                 "expire": self._get_lexicon_option("ttl"),
124             },
125         }
126 
127         self._post(f"/domains/{self.domain}/dns", data=data)
128 
129         LOGGER.debug("create_record: %s", identifier)
130 
131         return True
132 
133     def _list_records(
134         self,
135         rtype: Optional[str] = None,
136         name: Optional[str] = None,
137         content: Optional[str] = None,
138     ) -> List[Dict[str, Any]]:
139         data = self._get(f"/domains/{self.domain}/dns")
140 
141         records = []
142         for entry in data["dnsEntries"]:
143             record = {
144                 "type": entry["type"],
145                 "name": self._full_name(entry["name"]),
146                 "ttl": entry["expire"],
147                 "content": entry["content"],
148             }
149             record["id"] = Provider._identifier(record)
150             records.append(record)
151 
152         if rtype:
153             records = [record for record in records if record["type"] == rtype]
154         if name:
155             records = [
156                 record for record in records if record["name"] == self._full_name(name)
157             ]
158         if content:
159             records = [record for record in records if record["content"] == content]
160 
161         LOGGER.debug("list_records: %s", records)
162 
163         return records
164 
165     def _update_record(
166         self,
167         identifier: Optional[str] = None,
168         rtype: Optional[str] = None,
169         name: Optional[str] = None,
170         content: Optional[str] = None,
171     ) -> bool:
172         if not identifier and (not rtype or not name):
173             raise Exception("Error, identifier or rtype+name parameters are required.")
174 
175         if identifier:
176             records = self._list_records()
177             records_to_update = [
178                 record for record in records if record["id"] == identifier
179             ]
180         else:
181             records_to_update = self._list_records(rtype=rtype, name=name)
182 
183         if not records_to_update:
184             raise Exception(
185                 f"Error, could not find a record for given identifier: {identifier}"
186             )
187 
188         if len(records_to_update) > 1:
189             LOGGER.warning(
190                 "Warning, multiple records found for given parameters, "
191                 "only first one will be updated: %s",
192                 records_to_update,
193             )
194 
195         record = records_to_update[0]
196 
197         # TransIP API is not designed to update one record out of several records
198         # matching the same type+name (eg. multi-valued TXT entries).
199         # To circumvent the limitation, we remove first the record to update, then
200         # recreate it with the updated content.
201 
202         data = {
203             "dnsEntry": {
204                 "type": record["type"],
205                 "name": self._relative_name(record["name"]),
206                 "content": record["content"],
207                 "expire": record["ttl"],
208             },
209         }
210 
211         self._request("DELETE", f"/domains/{self.domain}/dns", data=data)
212 
213         data["dnsEntry"]["content"] = content
214 
215         self._post(f"/domains/{self.domain}/dns", data=data)
216 
217         LOGGER.debug("update_record: %s", record["id"])
218 
219         return True
220 
221     def _delete_record(
222         self,
223         identifier: Optional[str] = None,
224         rtype: Optional[str] = None,
225         name: Optional[str] = None,
226         content: Optional[str] = None,
227     ) -> bool:
228         if identifier:
229             records = self._list_records()
230             records = [record for record in records if record["id"] == identifier]
231 
232             if not records:
233                 raise LexiconError(
234                     f"Could not find a record matching the identifier provider: {identifier}"
235                 )
236         else:
237             records = self._list_records(rtype, name, content)
238 
239         for record in records:
240             data = {
241                 "dnsEntry": {
242                     "type": record["type"],
243                     "name": self._relative_name(record["name"]),
244                     "content": record["content"],
245                     "expire": record["ttl"],
246                 },
247             }
248 
249             self._request("DELETE", f"/domains/{self.domain}/dns", data=data)
250 
251         LOGGER.debug("delete_records: %s %s %s %s", identifier, rtype, name, content)
252 
253         return True
254 
255     def _request(
256         self,
257         action: str = "GET",
258         url: str = "/",
259         data: Optional[Dict] = None,
260         query_params: Optional[Dict] = None,
261     ) -> Optional[Dict[str, Any]]:
262         response = requests.request(
263             action,
264             f"{API_BASE_URL}{url}",
265             params=query_params,
266             json=data,
267             headers={"Authorization": f"Bearer {self.token}"},
268         )
269 
270         response.raise_for_status()
271 
272         try:
273             return response.json()
274         except JSONDecodeError:
275             return None
276 
277     @staticmethod
278     def _identifier(record):
279         digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
280         digest.update(("type=" + record.get("type", "") + ",").encode("utf-8"))
281         digest.update(("name=" + record.get("name", "") + ",").encode("utf-8"))
282         digest.update(("content=" + record.get("content", "") + ",").encode("utf-8"))
283 
284         return binascii.hexlify(digest.finalize()).decode("utf-8")[0:7]
285
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lexicon/providers/transip.py b/lexicon/providers/transip.py
--- a/lexicon/providers/transip.py
+++ b/lexicon/providers/transip.py
@@ -11,7 +11,11 @@
 from cryptography.hazmat.primitives import hashes
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.serialization import load_pem_private_key
-from requests.exceptions import JSONDecodeError
+
+try:
+    from simplejson import JSONDecodeError
+except ImportError:
+    from json import JSONDecodeError  # type: ignore[misc]
 
 from lexicon.exceptions import LexiconError
 from lexicon.providers.base import Provider as BaseProvider

{"golden_diff": "diff --git a/lexicon/providers/transip.py b/lexicon/providers/transip.py\n--- a/lexicon/providers/transip.py\n+++ b/lexicon/providers/transip.py\n@@ -11,7 +11,11 @@\n from cryptography.hazmat.primitives import hashes\n from cryptography.hazmat.primitives.asymmetric import padding\n from cryptography.hazmat.primitives.serialization import load_pem_private_key\n-from requests.exceptions import JSONDecodeError\n+\n+try:\n+    from simplejson import JSONDecodeError\n+except ImportError:\n+    from json import JSONDecodeError  # type: ignore[misc]\n \n from lexicon.exceptions import LexiconError\n from lexicon.providers.base import Provider as BaseProvider\n", "issue": "transip provider needs requests>=2.27.0 in dependency spec\nThe update of the transip provider from #1086 included in 3.9.2 uses `from requests.exceptions import JSONDecodeError` which was first included in requests 2.27.0 but the requirements spec only requires `>=2`:\r\nhttps://github.com/AnalogJ/lexicon/blob/d8b26bcfffd484f34444c91ceb3ced4a2583bd18/pyproject.toml#L43\r\n\r\nThis is passed directly through to the requirement spec in the wheel:\r\n```\r\nRequires-Dist: requests (>=2)\r\n```\r\n\r\nAnd means that someone who upgrades dns-lexicon from a previous version using pip will get an import error.\n", "before_files": [{"content": "\"\"\"Module provider for TransIP\"\"\"\nimport binascii\nimport json\nimport logging\nimport uuid\nfrom base64 import b64decode, b64encode\nfrom typing import Any, Dict, List, Optional\n\nimport requests\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives.asymmetric import padding\nfrom cryptography.hazmat.primitives.serialization import load_pem_private_key\nfrom requests.exceptions import JSONDecodeError\n\nfrom lexicon.exceptions import LexiconError\nfrom lexicon.providers.base import Provider as BaseProvider\n\nLOGGER = logging.getLogger(__name__)\n\nNAMESERVER_DOMAINS: List[str] = []\n\nAPI_BASE_URL = \"https://api.transip.nl/v6\"\n\n\ndef provider_parser(subparser):\n    \"\"\"Configure provider parser for TransIP\"\"\"\n    subparser.add_argument(\n        \"--auth-username\", help=\"specify username for authentication\"\n    )\n    subparser.add_argument(\n        \"--auth-api-key\",\n        help=\"specify the private key to use for API authentication, in PEM format: can be either \"\n        \"the path of the key file (eg. /tmp/key.pem) or the base64 encoded content of this \"\n        \"file prefixed by 'base64::' (eg. base64::eyJhbGciOyJ...)\",\n    )\n    subparser.add_argument(\n        \"--auth-key-is-global\",\n        action=\"store_true\",\n        help=\"set this flag is the private key used is a global key with no IP whitelist restriction\",\n    )\n\n\nclass Provider(BaseProvider):\n    \"\"\"\n    Provider class for TransIP\n\n    provider_options can be overwritten by a Provider to setup custom defaults.\n    They will be overwritten by any options set via the CLI or Env.\n    order is:\n\n    \"\"\"\n\n    def __init__(self, config):\n        super(Provider, self).__init__(config)\n        self.provider_name = \"transip\"\n        self.domain_id = None\n\n        private_key_conf = self._get_provider_option(\"auth_api_key\")\n        if private_key_conf.startswith(\"base64::\"):\n            private_key_bytes = b64decode(private_key_conf.replace(\"base64::\", \"\"))\n        else:\n            with open(\n                private_key_conf,\n                \"rb\",\n            ) as file:\n                private_key_bytes = file.read()\n\n        self.private_key = load_pem_private_key(private_key_bytes, password=None)\n        self.token: str\n\n    def _authenticate(self):\n        request_body = {\n            \"login\": self._get_provider_option(\"auth_username\"),\n            \"nonce\": uuid.uuid4().hex,\n            \"global_key\": self._get_provider_option(\"auth_key_is_global\") or False,\n        }\n\n        request_body_bytes = json.dumps(request_body).encode()\n\n        signature = self.private_key.sign(\n            request_body_bytes,\n            padding.PKCS1v15(),\n            hashes.SHA512(),\n        )\n\n        headers = {\"Signature\": b64encode(signature).decode()}\n\n        response = requests.request(\n            \"POST\", f\"{API_BASE_URL}/auth\", json=request_body, headers=headers\n        )\n        response.raise_for_status()\n\n        self.token = response.json()[\"token\"]\n\n        data = self._get(f\"/domains/{self.domain}\")\n\n        self.domain_id = data[\"domain\"][\"authCode\"]\n\n    def _create_record(self, rtype: str, name: str, content: str) -> bool:\n        if not rtype or not name or not content:\n            raise Exception(\n                \"Error, rtype, name and content are mandatory to create a record.\"\n            )\n\n        identifier = Provider._identifier(\n            {\"type\": rtype, \"name\": self._full_name(name), \"content\": content}\n        )\n\n        if any(\n            record\n            for record in self._list_records(rtype=rtype, name=name, content=content)\n            if record[\"id\"] == identifier\n        ):\n            LOGGER.debug(\"create_record (ignored, duplicate): %s\", identifier)\n            return True\n\n        data = {\n            \"dnsEntry\": {\n                \"type\": rtype,\n                \"name\": self._relative_name(name),\n                \"content\": content,\n                \"expire\": self._get_lexicon_option(\"ttl\"),\n            },\n        }\n\n        self._post(f\"/domains/{self.domain}/dns\", data=data)\n\n        LOGGER.debug(\"create_record: %s\", identifier)\n\n        return True\n\n    def _list_records(\n        self,\n        rtype: Optional[str] = None,\n        name: Optional[str] = None,\n        content: Optional[str] = None,\n    ) -> List[Dict[str, Any]]:\n        data = self._get(f\"/domains/{self.domain}/dns\")\n\n        records = []\n        for entry in data[\"dnsEntries\"]:\n            record = {\n                \"type\": entry[\"type\"],\n                \"name\": self._full_name(entry[\"name\"]),\n                \"ttl\": entry[\"expire\"],\n                \"content\": entry[\"content\"],\n            }\n            record[\"id\"] = Provider._identifier(record)\n            records.append(record)\n\n        if rtype:\n            records = [record for record in records if record[\"type\"] == rtype]\n        if name:\n            records = [\n                record for record in records if record[\"name\"] == self._full_name(name)\n            ]\n        if content:\n            records = [record for record in records if record[\"content\"] == content]\n\n        LOGGER.debug(\"list_records: %s\", records)\n\n        return records\n\n    def _update_record(\n        self,\n        identifier: Optional[str] = None,\n        rtype: Optional[str] = None,\n        name: Optional[str] = None,\n        content: Optional[str] = None,\n    ) -> bool:\n        if not identifier and (not rtype or not name):\n            raise Exception(\"Error, identifier or rtype+name parameters are required.\")\n\n        if identifier:\n            records = self._list_records()\n            records_to_update = [\n                record for record in records if record[\"id\"] == identifier\n            ]\n        else:\n            records_to_update = self._list_records(rtype=rtype, name=name)\n\n        if not records_to_update:\n            raise Exception(\n                f\"Error, could not find a record for given identifier: {identifier}\"\n            )\n\n        if len(records_to_update) > 1:\n            LOGGER.warning(\n                \"Warning, multiple records found for given parameters, \"\n                \"only first one will be updated: %s\",\n                records_to_update,\n            )\n\n        record = records_to_update[0]\n\n        # TransIP API is not designed to update one record out of several records\n        # matching the same type+name (eg. multi-valued TXT entries).\n        # To circumvent the limitation, we remove first the record to update, then\n        # recreate it with the updated content.\n\n        data = {\n            \"dnsEntry\": {\n                \"type\": record[\"type\"],\n                \"name\": self._relative_name(record[\"name\"]),\n                \"content\": record[\"content\"],\n                \"expire\": record[\"ttl\"],\n            },\n        }\n\n        self._request(\"DELETE\", f\"/domains/{self.domain}/dns\", data=data)\n\n        data[\"dnsEntry\"][\"content\"] = content\n\n        self._post(f\"/domains/{self.domain}/dns\", data=data)\n\n        LOGGER.debug(\"update_record: %s\", record[\"id\"])\n\n        return True\n\n    def _delete_record(\n        self,\n        identifier: Optional[str] = None,\n        rtype: Optional[str] = None,\n        name: Optional[str] = None,\n        content: Optional[str] = None,\n    ) -> bool:\n        if identifier:\n            records = self._list_records()\n            records = [record for record in records if record[\"id\"] == identifier]\n\n            if not records:\n                raise LexiconError(\n                    f\"Could not find a record matching the identifier provider: {identifier}\"\n                )\n        else:\n            records = self._list_records(rtype, name, content)\n\n        for record in records:\n            data = {\n                \"dnsEntry\": {\n                    \"type\": record[\"type\"],\n                    \"name\": self._relative_name(record[\"name\"]),\n                    \"content\": record[\"content\"],\n                    \"expire\": record[\"ttl\"],\n                },\n            }\n\n            self._request(\"DELETE\", f\"/domains/{self.domain}/dns\", data=data)\n\n        LOGGER.debug(\"delete_records: %s %s %s %s\", identifier, rtype, name, content)\n\n        return True\n\n    def _request(\n        self,\n        action: str = \"GET\",\n        url: str = \"/\",\n        data: Optional[Dict] = None,\n        query_params: Optional[Dict] = None,\n    ) -> Optional[Dict[str, Any]]:\n        response = requests.request(\n            action,\n            f\"{API_BASE_URL}{url}\",\n            params=query_params,\n            json=data,\n            headers={\"Authorization\": f\"Bearer {self.token}\"},\n        )\n\n        response.raise_for_status()\n\n        try:\n            return response.json()\n        except JSONDecodeError:\n            return None\n\n    @staticmethod\n    def _identifier(record):\n        digest = hashes.Hash(hashes.SHA256(), backend=default_backend())\n        digest.update((\"type=\" + record.get(\"type\", \"\") + \",\").encode(\"utf-8\"))\n        digest.update((\"name=\" + record.get(\"name\", \"\") + \",\").encode(\"utf-8\"))\n        digest.update((\"content=\" + record.get(\"content\", \"\") + \",\").encode(\"utf-8\"))\n\n        return binascii.hexlify(digest.finalize()).decode(\"utf-8\")[0:7]\n", "path": "lexicon/providers/transip.py"}], "after_files": [{"content": "\"\"\"Module provider for TransIP\"\"\"\nimport binascii\nimport json\nimport logging\nimport uuid\nfrom base64 import b64decode, b64encode\nfrom typing import Any, Dict, List, Optional\n\nimport requests\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives.asymmetric import padding\nfrom cryptography.hazmat.primitives.serialization import load_pem_private_key\n\ntry:\n    from simplejson import JSONDecodeError\nexcept ImportError:\n    from json import JSONDecodeError  # type: ignore[misc]\n\nfrom lexicon.exceptions import LexiconError\nfrom lexicon.providers.base import Provider as BaseProvider\n\nLOGGER = logging.getLogger(__name__)\n\nNAMESERVER_DOMAINS: List[str] = []\n\nAPI_BASE_URL = \"https://api.transip.nl/v6\"\n\n\ndef provider_parser(subparser):\n    \"\"\"Configure provider parser for TransIP\"\"\"\n    subparser.add_argument(\n        \"--auth-username\", help=\"specify username for authentication\"\n    )\n    subparser.add_argument(\n        \"--auth-api-key\",\n        help=\"specify the private key to use for API authentication, in PEM format: can be either \"\n        \"the path of the key file (eg. /tmp/key.pem) or the base64 encoded content of this \"\n        \"file prefixed by 'base64::' (eg. base64::eyJhbGciOyJ...)\",\n    )\n    subparser.add_argument(\n        \"--auth-key-is-global\",\n        action=\"store_true\",\n        help=\"set this flag is the private key used is a global key with no IP whitelist restriction\",\n    )\n\n\nclass Provider(BaseProvider):\n    \"\"\"\n    Provider class for TransIP\n\n    provider_options can be overwritten by a Provider to setup custom defaults.\n    They will be overwritten by any options set via the CLI or Env.\n    order is:\n\n    \"\"\"\n\n    def __init__(self, config):\n        super(Provider, self).__init__(config)\n        self.provider_name = \"transip\"\n        self.domain_id = None\n\n        private_key_conf = self._get_provider_option(\"auth_api_key\")\n        if private_key_conf.startswith(\"base64::\"):\n            private_key_bytes = b64decode(private_key_conf.replace(\"base64::\", \"\"))\n        else:\n            with open(\n                private_key_conf,\n                \"rb\",\n            ) as file:\n                private_key_bytes = file.read()\n\n        self.private_key = load_pem_private_key(private_key_bytes, password=None)\n        self.token: str\n\n    def _authenticate(self):\n        request_body = {\n            \"login\": self._get_provider_option(\"auth_username\"),\n            \"nonce\": uuid.uuid4().hex,\n            \"global_key\": self._get_provider_option(\"auth_key_is_global\") or False,\n        }\n\n        request_body_bytes = json.dumps(request_body).encode()\n\n        signature = self.private_key.sign(\n            request_body_bytes,\n            padding.PKCS1v15(),\n            hashes.SHA512(),\n        )\n\n        headers = {\"Signature\": b64encode(signature).decode()}\n\n        response = requests.request(\n            \"POST\", f\"{API_BASE_URL}/auth\", json=request_body, headers=headers\n        )\n        response.raise_for_status()\n\n        self.token = response.json()[\"token\"]\n\n        data = self._get(f\"/domains/{self.domain}\")\n\n        self.domain_id = data[\"domain\"][\"authCode\"]\n\n    def _create_record(self, rtype: str, name: str, content: str) -> bool:\n        if not rtype or not name or not content:\n            raise Exception(\n                \"Error, rtype, name and content are mandatory to create a record.\"\n            )\n\n        identifier = Provider._identifier(\n            {\"type\": rtype, \"name\": self._full_name(name), \"content\": content}\n        )\n\n        if any(\n            record\n            for record in self._list_records(rtype=rtype, name=name, content=content)\n            if record[\"id\"] == identifier\n        ):\n            LOGGER.debug(\"create_record (ignored, duplicate): %s\", identifier)\n            return True\n\n        data = {\n            \"dnsEntry\": {\n                \"type\": rtype,\n                \"name\": self._relative_name(name),\n                \"content\": content,\n                \"expire\": self._get_lexicon_option(\"ttl\"),\n            },\n        }\n\n        self._post(f\"/domains/{self.domain}/dns\", data=data)\n\n        LOGGER.debug(\"create_record: %s\", identifier)\n\n        return True\n\n    def _list_records(\n        self,\n        rtype: Optional[str] = None,\n        name: Optional[str] = None,\n        content: Optional[str] = None,\n    ) -> List[Dict[str, Any]]:\n        data = self._get(f\"/domains/{self.domain}/dns\")\n\n        records = []\n        for entry in data[\"dnsEntries\"]:\n            record = {\n                \"type\": entry[\"type\"],\n                \"name\": self._full_name(entry[\"name\"]),\n                \"ttl\": entry[\"expire\"],\n                \"content\": entry[\"content\"],\n            }\n            record[\"id\"] = Provider._identifier(record)\n            records.append(record)\n\n        if rtype:\n            records = [record for record in records if record[\"type\"] == rtype]\n        if name:\n            records = [\n                record for record in records if record[\"name\"] == self._full_name(name)\n            ]\n        if content:\n            records = [record for record in records if record[\"content\"] == content]\n\n        LOGGER.debug(\"list_records: %s\", records)\n\n        return records\n\n    def _update_record(\n        self,\n        identifier: Optional[str] = None,\n        rtype: Optional[str] = None,\n        name: Optional[str] = None,\n        content: Optional[str] = None,\n    ) -> bool:\n        if not identifier and (not rtype or not name):\n            raise Exception(\"Error, identifier or rtype+name parameters are required.\")\n\n        if identifier:\n            records = self._list_records()\n            records_to_update = [\n                record for record in records if record[\"id\"] == identifier\n            ]\n        else:\n            records_to_update = self._list_records(rtype=rtype, name=name)\n\n        if not records_to_update:\n            raise Exception(\n                f\"Error, could not find a record for given identifier: {identifier}\"\n            )\n\n        if len(records_to_update) > 1:\n            LOGGER.warning(\n                \"Warning, multiple records found for given parameters, \"\n                \"only first one will be updated: %s\",\n                records_to_update,\n            )\n\n        record = records_to_update[0]\n\n        # TransIP API is not designed to update one record out of several records\n        # matching the same type+name (eg. multi-valued TXT entries).\n        # To circumvent the limitation, we remove first the record to update, then\n        # recreate it with the updated content.\n\n        data = {\n            \"dnsEntry\": {\n                \"type\": record[\"type\"],\n                \"name\": self._relative_name(record[\"name\"]),\n                \"content\": record[\"content\"],\n                \"expire\": record[\"ttl\"],\n            },\n        }\n\n        self._request(\"DELETE\", f\"/domains/{self.domain}/dns\", data=data)\n\n        data[\"dnsEntry\"][\"content\"] = content\n\n        self._post(f\"/domains/{self.domain}/dns\", data=data)\n\n        LOGGER.debug(\"update_record: %s\", record[\"id\"])\n\n        return True\n\n    def _delete_record(\n        self,\n        identifier: Optional[str] = None,\n        rtype: Optional[str] = None,\n        name: Optional[str] = None,\n        content: Optional[str] = None,\n    ) -> bool:\n        if identifier:\n            records = self._list_records()\n            records = [record for record in records if record[\"id\"] == identifier]\n\n            if not records:\n                raise LexiconError(\n                    f\"Could not find a record matching the identifier provider: {identifier}\"\n                )\n        else:\n            records = self._list_records(rtype, name, content)\n\n        for record in records:\n            data = {\n                \"dnsEntry\": {\n                    \"type\": record[\"type\"],\n                    \"name\": self._relative_name(record[\"name\"]),\n                    \"content\": record[\"content\"],\n                    \"expire\": record[\"ttl\"],\n                },\n            }\n\n            self._request(\"DELETE\", f\"/domains/{self.domain}/dns\", data=data)\n\n        LOGGER.debug(\"delete_records: %s %s %s %s\", identifier, rtype, name, content)\n\n        return True\n\n    def _request(\n        self,\n        action: str = \"GET\",\n        url: str = \"/\",\n        data: Optional[Dict] = None,\n        query_params: Optional[Dict] = None,\n    ) -> Optional[Dict[str, Any]]:\n        response = requests.request(\n            action,\n            f\"{API_BASE_URL}{url}\",\n            params=query_params,\n            json=data,\n            headers={\"Authorization\": f\"Bearer {self.token}\"},\n        )\n\n        response.raise_for_status()\n\n        try:\n            return response.json()\n        except JSONDecodeError:\n            return None\n\n    @staticmethod\n    def _identifier(record):\n        digest = hashes.Hash(hashes.SHA256(), backend=default_backend())\n        digest.update((\"type=\" + record.get(\"type\", \"\") + \",\").encode(\"utf-8\"))\n        digest.update((\"name=\" + record.get(\"name\", \"\") + \",\").encode(\"utf-8\"))\n        digest.update((\"content=\" + record.get(\"content\", \"\") + \",\").encode(\"utf-8\"))\n\n        return binascii.hexlify(digest.finalize()).decode(\"utf-8\")[0:7]\n", "path": "lexicon/providers/transip.py"}]}