commonmeta-py 0.131__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 CHANGED
@@ -10,7 +10,7 @@ commonmeta-py is a Python library to convert scholarly metadata
10
10
  """
11
11
 
12
12
  __title__ = "commonmeta-py"
13
- __version__ = "0.131"
13
+ __version__ = "0.132"
14
14
  __author__ = "Martin Fenner"
15
15
  __license__ = "MIT"
16
16
 
@@ -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
@@ -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
- ) -> bytes:
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
- input = write_crossref_xml_list(metalist)
398
- if not input:
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(input, str):
404
- input = input.encode("utf-8")
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(input), "application/xml"),
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
- resp = requests.post(post_url, data=multipart_data, headers=headers, timeout=10)
421
- resp.raise_for_status()
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
- response = parse_xml(resp.content)
425
- status = py_.get(response, "html.body.h2")
426
- if status != "SUCCESS":
427
- # Handle error response
428
- message = py_.get(response, "html.body.p")
429
- logger.error(f"Crossref API error: {message}")
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
- record["uuid"] = py_.get(item, "identifiers.0.identifier")
443
- record = update_legacy_record(record, legacy_key=legacy_key, field="doi")
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[str]:
745
+ def get_publication_date(obj, media_type: Optional[str] = None) -> Optional[Dict]:
733
746
  """get publication date"""
734
- pub_date = date_parse(py_.get(obj, "date.published"))
735
- if pub_date is None:
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[dict]:
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": doi_from_url(ref.get("id", None)),
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) -> list:
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 relation.get("type", None) in [
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 relation.get("type", None) in [
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
- f = furl(relation.get("id", None))
883
- if validate_doi(relation.get("id", None)):
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(relation.get("id", None))
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(relation.get("id", None)) == "URL":
918
+ elif validate_url(relation_id) == "URL":
893
919
  identifier_type = "uri"
894
- _id = relation.get("id", None)
920
+ _id = relation_id
895
921
  else:
896
922
  identifier_type = "other"
897
- _id = relation.get("id", None)
923
+ _id = relation_id
898
924
 
899
925
  return {
900
926
  group: compact(
901
927
  {
902
- "@relationship-type": py_.lower_first(relation.get("type"))
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.131
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=aQTPtjtKTK5Fit8pxN4Vi_zEQ84Kj9UYiA29eo_fb50,2118
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=ImsNtmLl2N2lWflQo78ATL_2XrxAism3n4HJdGfTFFg,34075
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.131.dist-info/METADATA,sha256=KjyqcBzab5P6Um8uq6fcI5bxEuNPtdfPKoP_cKrZlR4,7656
87
- commonmeta_py-0.131.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
88
- commonmeta_py-0.131.dist-info/entry_points.txt,sha256=U4w4BoRuS3rN5t5Y-uYSyOeU5Lh_VRVMS9OIDzIgw4w,50
89
- commonmeta_py-0.131.dist-info/licenses/LICENSE,sha256=wsIvxF9Q9GC9vA_s79zTWP3BkXJdfUNRmALlU8GbW1s,1074
90
- commonmeta_py-0.131.dist-info/RECORD,,
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,,