Pyzotero: a Python client for the Zotero API pyzotero.readthedocs.io
zotero

Set contentType in attachment item metadata during upload

authored by urschrei.bsky.social and committed by urschrei.bsky.social 48969752 b1fcf78f

Changed files
+112 -2
src
pyzotero
tests
+1 -1
pyproject.toml
··· 1 1 [project] 2 2 name = "pyzotero" 3 - version = "1.7.5" 3 + version = "1.7.6" 4 4 description = "Python wrapper for the Zotero API" 5 5 readme = "README.md" 6 6 requires-python = ">=3.9"
+6
src/pyzotero/zotero.py
··· 1879 1879 msg, 1880 1880 ) 1881 1881 return None # Don't do anything if payload comes with keys 1882 + # Set contentType for each attachment if not already provided 1883 + for item in self.payload: 1884 + if not item.get("contentType"): 1885 + filepath = str(self.basedir.joinpath(item["filename"])) 1886 + detected_type = mimetypes.guess_type(filepath)[0] 1887 + item["contentType"] = detected_type or "application/octet-stream" 1882 1888 liblevel = "/{t}/{u}/items" 1883 1889 # Create one or more new attachments 1884 1890 headers = {"Zotero-Write-Token": token(), "Content-Type": "application/json"}
+104
tests/test_zotero.py
··· 1157 1157 os.remove(temp_file_path) 1158 1158 1159 1159 @httpretty.activate 1160 + def testFileUploadSetsContentType(self): 1161 + """Tests that contentType is automatically set during upload based on file extension""" 1162 + zot = z.Zotero("myuserID", "user", "myuserkey") 1163 + 1164 + # Create a temporary PDF file for testing 1165 + temp_file_path = os.path.join(self.cwd, "api_responses", "test_upload.pdf") 1166 + with open(temp_file_path, "w") as f: 1167 + f.write("Fake PDF content") 1168 + 1169 + # Variable to capture the request body 1170 + captured_body = [] 1171 + 1172 + def request_callback(request, uri, response_headers): 1173 + body = json.loads(request.body) 1174 + captured_body.append(body) 1175 + return [200, response_headers, json.dumps({"success": {"0": "ITEMKEY123"}})] 1176 + 1177 + HTTPretty.register_uri( 1178 + HTTPretty.POST, 1179 + "https://api.zotero.org/users/myuserID/items", 1180 + body=request_callback, 1181 + content_type="application/json", 1182 + ) 1183 + 1184 + # Create payload with empty contentType (mimics Zotero API template) 1185 + payload = [ 1186 + { 1187 + "filename": "test_upload.pdf", 1188 + "title": "Test PDF", 1189 + "linkMode": "imported_file", 1190 + "contentType": "", 1191 + } 1192 + ] 1193 + 1194 + mock_auth_data = {"exists": True} 1195 + 1196 + with ( 1197 + patch.object(z.Zupload, "_verify", return_value=None), 1198 + patch.object(z.Zupload, "_get_auth", return_value=mock_auth_data), 1199 + ): 1200 + upload = z.Zupload( 1201 + zot, payload, basedir=os.path.join(self.cwd, "api_responses") 1202 + ) 1203 + upload.upload() 1204 + 1205 + # Verify contentType was automatically set to application/pdf 1206 + self.assertEqual(len(captured_body), 1) 1207 + self.assertEqual(captured_body[0][0].get("contentType"), "application/pdf") 1208 + 1209 + os.remove(temp_file_path) 1210 + 1211 + @httpretty.activate 1212 + def testFileUploadPreservesUserContentType(self): 1213 + """Tests that user-provided contentType is not overridden""" 1214 + zot = z.Zotero("myuserID", "user", "myuserkey") 1215 + 1216 + temp_file_path = os.path.join(self.cwd, "api_responses", "test_upload.txt") 1217 + with open(temp_file_path, "w") as f: 1218 + f.write("Test content") 1219 + 1220 + captured_body = [] 1221 + 1222 + def request_callback(request, uri, response_headers): 1223 + body = json.loads(request.body) 1224 + captured_body.append(body) 1225 + return [200, response_headers, json.dumps({"success": {"0": "ITEMKEY123"}})] 1226 + 1227 + HTTPretty.register_uri( 1228 + HTTPretty.POST, 1229 + "https://api.zotero.org/users/myuserID/items", 1230 + body=request_callback, 1231 + content_type="application/json", 1232 + ) 1233 + 1234 + # Create payload WITH explicit contentType 1235 + payload = [ 1236 + { 1237 + "filename": "test_upload.txt", 1238 + "title": "Test File", 1239 + "linkMode": "imported_file", 1240 + "contentType": "application/custom-type", 1241 + } 1242 + ] 1243 + 1244 + mock_auth_data = {"exists": True} 1245 + 1246 + with ( 1247 + patch.object(z.Zupload, "_verify", return_value=None), 1248 + patch.object(z.Zupload, "_get_auth", return_value=mock_auth_data), 1249 + ): 1250 + upload = z.Zupload( 1251 + zot, payload, basedir=os.path.join(self.cwd, "api_responses") 1252 + ) 1253 + upload.upload() 1254 + 1255 + # Verify user-provided contentType was preserved 1256 + self.assertEqual(len(captured_body), 1) 1257 + self.assertEqual( 1258 + captured_body[0][0].get("contentType"), "application/custom-type" 1259 + ) 1260 + 1261 + os.remove(temp_file_path) 1262 + 1263 + @httpretty.activate 1160 1264 def testFileUploadWithPreexistingKeys(self): 1161 1265 """Tests file upload process when the payload already contains keys""" 1162 1266 zot = z.Zotero("myuserID", "user", "myuserkey")
+1 -1
uv.lock
··· 784 784 785 785 [[package]] 786 786 name = "pyzotero" 787 - version = "1.7.5" 787 + version = "1.7.6" 788 788 source = { editable = "." } 789 789 dependencies = [ 790 790 { name = "bibtexparser" },