commonmeta-py 0.130__py3-none-any.whl → 0.132__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- commonmeta/__init__.py +1 -1
- commonmeta/writers/crossref_xml_writer.py +75 -46
- {commonmeta_py-0.130.dist-info → commonmeta_py-0.132.dist-info}/METADATA +1 -1
- {commonmeta_py-0.130.dist-info → commonmeta_py-0.132.dist-info}/RECORD +7 -7
- {commonmeta_py-0.130.dist-info → commonmeta_py-0.132.dist-info}/WHEEL +0 -0
- {commonmeta_py-0.130.dist-info → commonmeta_py-0.132.dist-info}/entry_points.txt +0 -0
- {commonmeta_py-0.130.dist-info → commonmeta_py-0.132.dist-info}/licenses/LICENSE +0 -0
commonmeta/__init__.py
CHANGED
@@ -4,7 +4,7 @@ import io
|
|
4
4
|
import logging
|
5
5
|
from datetime import datetime
|
6
6
|
from time import time
|
7
|
-
from typing import Optional
|
7
|
+
from typing import Dict, Optional
|
8
8
|
|
9
9
|
import orjson as json
|
10
10
|
import requests
|
@@ -371,7 +371,7 @@ def write_crossref_xml_list(metalist) -> Optional[str]:
|
|
371
371
|
crossref_xml_list = []
|
372
372
|
for item in metalist.items:
|
373
373
|
data = convert_crossref_xml(item)
|
374
|
-
if data is None:
|
374
|
+
if data is None or not isinstance(data, dict):
|
375
375
|
logger.error(f"Could not convert metadata to Crossref XML: {item.id}")
|
376
376
|
continue
|
377
377
|
|
@@ -390,25 +390,25 @@ def write_crossref_xml_list(metalist) -> Optional[str]:
|
|
390
390
|
|
391
391
|
|
392
392
|
def push_crossref_xml_list(
|
393
|
-
metalist, login_id: str, login_passwd: str, legacy_key: str = None
|
394
|
-
) ->
|
393
|
+
metalist, login_id: str, login_passwd: str, legacy_key: Optional[str] = None
|
394
|
+
) -> str:
|
395
395
|
"""Push crossref_xml list to Crossref API, returns the API response."""
|
396
396
|
|
397
|
-
|
398
|
-
if not
|
397
|
+
input_xml = write_crossref_xml_list(metalist)
|
398
|
+
if not input_xml:
|
399
399
|
logger.error("Failed to generate XML for upload")
|
400
400
|
return "{}"
|
401
401
|
|
402
402
|
# Convert string to bytes if necessary
|
403
|
-
if isinstance(
|
404
|
-
|
403
|
+
if isinstance(input_xml, str):
|
404
|
+
input_xml = input_xml.encode("utf-8")
|
405
405
|
|
406
406
|
# The filename displayed in the Crossref admin interface
|
407
407
|
filename = f"{int(time())}"
|
408
408
|
|
409
409
|
multipart_data = MultipartEncoder(
|
410
410
|
fields={
|
411
|
-
"fname": (filename, io.BytesIO(
|
411
|
+
"fname": (filename, io.BytesIO(input_xml), "application/xml"),
|
412
412
|
"operation": "doMDUpload",
|
413
413
|
"login_id": login_id,
|
414
414
|
"login_passwd": login_passwd,
|
@@ -417,16 +417,25 @@ def push_crossref_xml_list(
|
|
417
417
|
|
418
418
|
post_url = "https://doi.crossref.org/servlet/deposit"
|
419
419
|
headers = {"Content-Type": multipart_data.content_type}
|
420
|
-
|
421
|
-
|
420
|
+
|
421
|
+
try:
|
422
|
+
resp = requests.post(post_url, data=multipart_data, headers=headers, timeout=30)
|
423
|
+
resp.raise_for_status()
|
424
|
+
except requests.exceptions.RequestException as e:
|
425
|
+
logger.error(f"Failed to upload to Crossref: {e}")
|
426
|
+
return "{}"
|
422
427
|
|
423
428
|
# Parse the response
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
429
|
+
try:
|
430
|
+
response = parse_xml(resp.content)
|
431
|
+
status = py_.get(response, "html.body.h2")
|
432
|
+
if status != "SUCCESS":
|
433
|
+
# Handle error response
|
434
|
+
message = py_.get(response, "html.body.p")
|
435
|
+
logger.error(f"Crossref API error: {message}")
|
436
|
+
return "{}"
|
437
|
+
except Exception as e:
|
438
|
+
logger.error(f"Failed to parse Crossref response: {e}")
|
430
439
|
return "{}"
|
431
440
|
|
432
441
|
items = []
|
@@ -439,12 +448,16 @@ def push_crossref_xml_list(
|
|
439
448
|
|
440
449
|
# update rogue-scholar legacy record if legacy_key is provided
|
441
450
|
if is_rogue_scholar_doi(item.id, ra="crossref") and legacy_key is not None:
|
442
|
-
|
443
|
-
|
451
|
+
uuid = py_.get(item, "identifiers.0.identifier")
|
452
|
+
if uuid:
|
453
|
+
record["uuid"] = uuid
|
454
|
+
record = update_legacy_record(
|
455
|
+
record, legacy_key=legacy_key, field="doi"
|
456
|
+
)
|
444
457
|
items.append(record)
|
445
458
|
|
446
459
|
# Return JSON response
|
447
|
-
return json.dumps(items, option=json.OPT_INDENT_2)
|
460
|
+
return json.dumps(items, option=json.OPT_INDENT_2).decode("utf-8")
|
448
461
|
|
449
462
|
|
450
463
|
def get_attributes(obj, **kwargs) -> dict:
|
@@ -729,10 +742,16 @@ def get_item_number(obj) -> Optional[dict]:
|
|
729
742
|
}
|
730
743
|
|
731
744
|
|
732
|
-
def get_publication_date(obj, media_type: str = None) -> Optional[
|
745
|
+
def get_publication_date(obj, media_type: Optional[str] = None) -> Optional[Dict]:
|
733
746
|
"""get publication date"""
|
734
|
-
|
735
|
-
if
|
747
|
+
pub_date_str = py_.get(obj, "date.published")
|
748
|
+
if pub_date_str is None:
|
749
|
+
return None
|
750
|
+
|
751
|
+
try:
|
752
|
+
pub_date = date_parse(pub_date_str)
|
753
|
+
except (ValueError, TypeError) as e:
|
754
|
+
logger.warning(f"Failed to parse publication date '{pub_date_str}': {e}")
|
736
755
|
return None
|
737
756
|
|
738
757
|
return compact(
|
@@ -763,17 +782,20 @@ def get_archive_locations(obj) -> Optional[list]:
|
|
763
782
|
]
|
764
783
|
|
765
784
|
|
766
|
-
def get_references(obj) -> Optional[
|
785
|
+
def get_references(obj) -> Optional[Dict]:
|
767
786
|
"""get references"""
|
768
787
|
if py_.get(obj, "references") is None or len(py_.get(obj, "references")) == 0:
|
769
788
|
return None
|
770
789
|
|
771
790
|
citations = []
|
772
791
|
for i, ref in enumerate(py_.get(obj, "references")):
|
792
|
+
# Validate DOI before using it
|
793
|
+
doi = doi_from_url(ref.get("id", None))
|
794
|
+
|
773
795
|
reference = compact(
|
774
796
|
{
|
775
797
|
"@key": ref.get("key", f"ref{i + 1}"),
|
776
|
-
"doi":
|
798
|
+
"doi": doi,
|
777
799
|
"journal_title": ref.get("journal_title", None),
|
778
800
|
"author": ref.get("author", None),
|
779
801
|
"volume": ref.get("volume", None),
|
@@ -849,15 +871,18 @@ def get_funding_references(obj) -> Optional[dict]:
|
|
849
871
|
}
|
850
872
|
|
851
873
|
|
852
|
-
def get_relations(obj) ->
|
874
|
+
def get_relations(obj) -> Optional[Dict]:
|
853
875
|
"""get relations"""
|
854
876
|
if py_.get(obj, "relations") is None or len(py_.get(obj, "relations")) == 0:
|
855
877
|
return None
|
856
878
|
|
857
879
|
def format_relation(relation):
|
858
880
|
"""format relation"""
|
881
|
+
relation_type = relation.get("type", None)
|
882
|
+
if relation_type is None:
|
883
|
+
return None
|
859
884
|
|
860
|
-
if
|
885
|
+
if relation_type in [
|
861
886
|
"IsPartOf",
|
862
887
|
"HasPart",
|
863
888
|
"IsReviewOf",
|
@@ -866,7 +891,7 @@ def get_relations(obj) -> list:
|
|
866
891
|
"HasRelatedMaterial",
|
867
892
|
]:
|
868
893
|
group = "rel:inter_work_relation"
|
869
|
-
elif
|
894
|
+
elif relation_type in [
|
870
895
|
"IsIdenticalTo",
|
871
896
|
"IsPreprintOf",
|
872
897
|
"HasPreprint",
|
@@ -879,43 +904,47 @@ def get_relations(obj) -> list:
|
|
879
904
|
else:
|
880
905
|
return None
|
881
906
|
|
882
|
-
|
883
|
-
if
|
907
|
+
relation_id = relation.get("id", None)
|
908
|
+
if relation_id is None:
|
909
|
+
return None
|
910
|
+
|
911
|
+
f = furl(relation_id)
|
912
|
+
if validate_doi(relation_id):
|
884
913
|
identifier_type = "doi"
|
885
|
-
_id = doi_from_url(
|
886
|
-
elif f.host == "portal.issn.org" and obj.type in [
|
887
|
-
"Article",
|
888
|
-
"BlogPost",
|
889
|
-
]:
|
914
|
+
_id = doi_from_url(relation_id)
|
915
|
+
elif f.host == "portal.issn.org" and obj.type in ["Article", "BlogPost"]:
|
890
916
|
identifier_type = "issn"
|
891
917
|
_id = f.path.segments[-1] if f.path.segments else None
|
892
|
-
elif validate_url(
|
918
|
+
elif validate_url(relation_id) == "URL":
|
893
919
|
identifier_type = "uri"
|
894
|
-
_id =
|
920
|
+
_id = relation_id
|
895
921
|
else:
|
896
922
|
identifier_type = "other"
|
897
|
-
_id =
|
923
|
+
_id = relation_id
|
898
924
|
|
899
925
|
return {
|
900
926
|
group: compact(
|
901
927
|
{
|
902
|
-
"@relationship-type": py_.lower_first(
|
903
|
-
if relation.get("type", None) is not None
|
904
|
-
else None,
|
928
|
+
"@relationship-type": py_.lower_first(relation_type),
|
905
929
|
"@identifier-type": identifier_type,
|
906
930
|
"#text": _id,
|
907
931
|
},
|
908
932
|
)
|
909
933
|
}
|
910
934
|
|
935
|
+
related_items = [
|
936
|
+
format_relation(i)
|
937
|
+
for i in py_.get(obj, "relations")
|
938
|
+
if format_relation(i) is not None
|
939
|
+
]
|
940
|
+
|
941
|
+
if not related_items:
|
942
|
+
return None
|
943
|
+
|
911
944
|
return {
|
912
945
|
"@xmlns:rel": "http://www.crossref.org/relations.xsd",
|
913
946
|
"@name": "relations",
|
914
|
-
"rel:related_item":
|
915
|
-
format_relation(i)
|
916
|
-
for i in py_.get(obj, "relations")
|
917
|
-
if format_relation(i) is not None
|
918
|
-
],
|
947
|
+
"rel:related_item": related_items,
|
919
948
|
}
|
920
949
|
|
921
950
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: commonmeta-py
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.132
|
4
4
|
Summary: Library for conversions to/from the Commonmeta scholarly metadata format
|
5
5
|
Project-URL: Homepage, https://python.commonmeta.org
|
6
6
|
Project-URL: Repository, https://github.com/front-matter/commonmeta-py
|
@@ -1,4 +1,4 @@
|
|
1
|
-
commonmeta/__init__.py,sha256=
|
1
|
+
commonmeta/__init__.py,sha256=QeL1QY1Hrvi0a2tFjlFNahbgbEcV638GH5mEYBx43Sw,2118
|
2
2
|
commonmeta/api_utils.py,sha256=P8LMHHYiF4OTi97_5k4KstcBreooMkOAKZ4ebxsAv4o,2691
|
3
3
|
commonmeta/author_utils.py,sha256=3lYW5s1rOUWNTKs1FP6XLfEUY3yCLOe_3L_VdJTDMp0,8585
|
4
4
|
commonmeta/base_utils.py,sha256=-MGy9q2uTiJEkPWQUYOJMdq-3tRpNnvBwlLjvllQ5g8,11164
|
@@ -77,14 +77,14 @@ commonmeta/writers/__init__.py,sha256=47-snms6xBHkoEXKYV1DBtH1npAtlVtvY29Z4Zr45q
|
|
77
77
|
commonmeta/writers/bibtex_writer.py,sha256=doAdyl1NEp60mPkHPo3GMH8B-HA6MzLAdlyNsIecTzU,4972
|
78
78
|
commonmeta/writers/citation_writer.py,sha256=qs_4X3BjrSqHexmJFPvPDTp0mRIqzb0F70_Wuc7S9x0,2343
|
79
79
|
commonmeta/writers/commonmeta_writer.py,sha256=QpfyhG__7o_XpsOTCPWxGymO7YKwZi2LQh8Zic44bdc,1365
|
80
|
-
commonmeta/writers/crossref_xml_writer.py,sha256=
|
80
|
+
commonmeta/writers/crossref_xml_writer.py,sha256=nyNgnyluzd9jZ6jysrSg_TpMNOQuVjfMo7pN0HUb3c0,34832
|
81
81
|
commonmeta/writers/csl_writer.py,sha256=4gDYs1EzK4_L2UIRTfs25wgHmYRwdRP2zmfxF9387oU,2779
|
82
82
|
commonmeta/writers/datacite_writer.py,sha256=bcinpwhq7XnVthKHH8-sdXA34dSlvFH4ImYH768iaQU,6428
|
83
83
|
commonmeta/writers/inveniordm_writer.py,sha256=tiuq9JEkz02l615yVe9wUcJQqiIPJLUqNDyofEE8Aus,26726
|
84
84
|
commonmeta/writers/ris_writer.py,sha256=3SdyEvMRaPRP1SV1MB-MXBlunE7x6og7RF1zuWtetPc,2094
|
85
85
|
commonmeta/writers/schema_org_writer.py,sha256=s18_x0ReXwAGBoEAwp2q-HCgFQ-h5qRg6JyAlqCoSFE,5871
|
86
|
-
commonmeta_py-0.
|
87
|
-
commonmeta_py-0.
|
88
|
-
commonmeta_py-0.
|
89
|
-
commonmeta_py-0.
|
90
|
-
commonmeta_py-0.
|
86
|
+
commonmeta_py-0.132.dist-info/METADATA,sha256=EjTcKw-mi9NnFlRqUTmMh2x11eqabfZRqQqJDCOEbqc,7656
|
87
|
+
commonmeta_py-0.132.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
88
|
+
commonmeta_py-0.132.dist-info/entry_points.txt,sha256=U4w4BoRuS3rN5t5Y-uYSyOeU5Lh_VRVMS9OIDzIgw4w,50
|
89
|
+
commonmeta_py-0.132.dist-info/licenses/LICENSE,sha256=wsIvxF9Q9GC9vA_s79zTWP3BkXJdfUNRmALlU8GbW1s,1074
|
90
|
+
commonmeta_py-0.132.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|