commonmeta-py 0.106__py3-none-any.whl → 0.107__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.106"
13
+ __version__ = "0.107"
14
14
  __author__ = "Martin Fenner"
15
15
  __license__ = "MIT"
16
16
 
@@ -31,6 +31,18 @@ from .base_utils import (
31
31
  unwrap,
32
32
  wrap,
33
33
  )
34
+ from .crossref_utils import (
35
+ CrossrefBadRequestError,
36
+ CrossrefError,
37
+ CrossrefForbiddenError,
38
+ CrossrefNoContentError,
39
+ CrossrefNotFoundError,
40
+ CrossrefRequestError,
41
+ CrossrefServerError,
42
+ CrossrefUnauthorizedError,
43
+ HttpError,
44
+ generate_crossref_xml,
45
+ )
34
46
  from .date_utils import (
35
47
  get_date_from_crossref_parts,
36
48
  get_date_from_date_parts,
@@ -231,7 +231,6 @@ def insert_citation_list(metadata, xml):
231
231
 
232
232
  citation_list = etree.SubElement(xml, "citation_list")
233
233
  for i, ref in enumerate(metadata.references):
234
- print(i)
235
234
  if ref.get("id", None) is None:
236
235
  continue
237
236
  citation = etree.SubElement(
@@ -581,3 +580,83 @@ def generate_crossref_xml_list(metalist) -> Optional[str]:
581
580
  doctype='<?xml version="1.0" encoding="UTF-8"?>',
582
581
  pretty_print=True,
583
582
  )
583
+
584
+
585
+ """Errors for the Crossref XML API.
586
+
587
+ Error responses will be converted into an exception from this module.
588
+ """
589
+
590
+
591
+ class HttpError(Exception):
592
+ """Exception raised when a connection problem happens."""
593
+
594
+
595
+ class CrossrefError(Exception):
596
+ """Exception raised when the server returns a known HTTP error code.
597
+
598
+ Known HTTP error codes include:
599
+
600
+ * 204 No Content
601
+ * 400 Bad Request
602
+ * 401 Unauthorized
603
+ * 403 Forbidden
604
+ * 404 Not Found
605
+ * 410 Gone (deleted)
606
+ """
607
+
608
+ @staticmethod
609
+ def factory(err_code, *args):
610
+ """Create exceptions through a Factory based on the HTTP error code."""
611
+ if err_code == 204:
612
+ return CrossrefNoContentError(*args)
613
+ elif err_code == 400:
614
+ return CrossrefBadRequestError(*args)
615
+ elif err_code == 401:
616
+ return CrossrefUnauthorizedError(*args)
617
+ elif err_code == 403:
618
+ return CrossrefForbiddenError(*args)
619
+ elif err_code == 404:
620
+ return CrossrefNotFoundError(*args)
621
+ else:
622
+ return CrossrefServerError(*args)
623
+
624
+
625
+ class CrossrefServerError(CrossrefError):
626
+ """An internal server error happened on the Crossref end. Try later.
627
+
628
+ Base class for all 5XX-related HTTP error codes.
629
+ """
630
+
631
+
632
+ class CrossrefRequestError(CrossrefError):
633
+ """A Crossref request error. You made an invalid request.
634
+
635
+ Base class for all 4XX-related HTTP error codes as well as 204.
636
+ """
637
+
638
+
639
+ class CrossrefNoContentError(CrossrefRequestError):
640
+ """DOI is known to Crossref, but not resolvable.
641
+
642
+ This might be due to handle's latency.
643
+ """
644
+
645
+
646
+ class CrossrefBadRequestError(CrossrefRequestError):
647
+ """Bad request error.
648
+
649
+ Bad requests can include e.g. invalid XML, wrong domain, wrong prefix.
650
+ """
651
+
652
+
653
+ class CrossrefUnauthorizedError(CrossrefRequestError):
654
+ """Bad username or password."""
655
+
656
+
657
+ class CrossrefForbiddenError(CrossrefRequestError):
658
+ """Login problem, record belongs to another party or quota exceeded."""
659
+
660
+
661
+ class CrossrefNotFoundError(CrossrefRequestError):
662
+ """DOI does not exist in the database."""
commonmeta/utils.py CHANGED
@@ -408,14 +408,36 @@ def openalex_api_url(id: str, identifier_type: str, **kwargs) -> str:
408
408
 
409
409
  def openalex_api_query_url(query: dict) -> str:
410
410
  """Return the OpenAlex API query URL"""
411
+ # Define allowed types
412
+ types = [
413
+ "article", "book-chapter", "dataset", "preprint", "dissertation",
414
+ "book", "review", "paratext", "libguides", "letter", "other",
415
+ "reference-entry", "report", "editorial", "peer-review", "erratum",
416
+ "standard", "grant", "supplementary-materials", "retraction",
417
+ ]
418
+
411
419
  url = "https://api.openalex.org/works"
412
420
  f = furl(url)
413
- rows = min(int(query.get("rows", 20)), 1000)
414
- queries = []
421
+
422
+ # Handle pagination and sample parameters
423
+ number = max(1, min(1000, int(query.get("number", query.get("rows", 10)))))
424
+ page = max(1, int(query.get("page", 1)))
425
+
426
+ sample = query.get("sample", False)
427
+ if sample:
428
+ f.args["sample"] = str(number)
429
+ else:
430
+ f.args["per-page"] = str(number)
431
+ f.args["page"] = str(page)
432
+ # Sort results by published date in descending order
433
+ f.args["sort"] = "publication_date:desc"
434
+
435
+ # Build filters
415
436
  filters = []
437
+ queries = []
416
438
  _query = None
417
- _filter = None
418
439
 
440
+ # Handle query parameters
419
441
  if query.get("query", None) is not None:
420
442
  queries += [query.get("query")]
421
443
  for key, value in query.items():
@@ -428,23 +450,60 @@ def openalex_api_query_url(query: dict) -> str:
428
450
  queries += [f"{key}:{value}"]
429
451
  if queries:
430
452
  _query = ",".join(queries)
453
+ f.args["query"] = _query
454
+
455
+ # Member/IDs filter
456
+ ids = query.get("ids", query.get("member", ""))
457
+ if ids:
458
+ filters.append(f"member:{ids}")
459
+
460
+ # Type filter
461
+ type_ = query.get("type_", query.get("type", ""))
462
+ if type_ and type_ in types:
463
+ filters.append(f"type:{type_}")
431
464
 
465
+ # ROR filter
466
+ ror = query.get("ror", "")
467
+ if ror:
468
+ r = validate_ror(ror)
469
+ if r:
470
+ filters.append(f"authorships.institutions.ror:{r}")
471
+
472
+ # ORCID filter
473
+ orcid = query.get("orcid", "")
474
+ if orcid:
475
+ o = validate_orcid(orcid)
476
+ if o:
477
+ filters.append(f"authorships.author.id:{o}")
478
+
479
+ # Year filter
480
+ year = query.get("year", query.get("publication_year", ""))
481
+ if year:
482
+ filters.append(f"publication_year:{year}")
483
+
484
+ # Other filters from the original function
432
485
  for key, value in query.items():
433
486
  if key in [
434
487
  "prefix",
435
- "member",
436
- "type",
437
488
  "has-full-text",
438
- "has-references",
439
- "has-orcid",
440
489
  "has-funder",
441
490
  "has-license",
442
491
  ]:
443
- filters += [f"{key}:{value}"]
444
- if filters:
445
- _filter = ",".join(filters)
492
+ filters.append(f"{key}:{value}")
493
+
494
+ # Boolean filters
495
+ # if query.get("hasORCID", query.get("has-orcid", False)):
496
+ # filters.append("has-orcid:true")
497
+
498
+ # if query.get("hasReferences", query.get("has-references", False)):
499
+ # filters.append("has-references:true")
446
500
 
447
- f.args.update(compact({"rows": rows, "query": _query, "filter": _filter}))
501
+ # if query.get("hasAbstract", query.get("has-abstract", False)):
502
+ # filters.append("has-abstract:true")
503
+
504
+ # Add filters to params if any exist
505
+ if filters:
506
+ f.args["filter"] = ",".join(filters)
448
507
 
449
508
  return f.url
450
509
 
@@ -1,13 +1,18 @@
1
1
  """Citation writer for commonmeta-py"""
2
2
 
3
- import orjson as json
4
3
  import re
5
- from pydash import py_
6
- from citeproc import CitationStylesStyle, CitationStylesBibliography
7
- from citeproc import Citation, CitationItem
8
- from citeproc import formatter
4
+
5
+ import orjson as json
6
+ from citeproc import (
7
+ Citation,
8
+ CitationItem,
9
+ CitationStylesBibliography,
10
+ CitationStylesStyle,
11
+ formatter,
12
+ )
9
13
  from citeproc.source.json import CiteProcJSON
10
14
  from citeproc_styles import get_style_filepath
15
+ from pydash import py_
11
16
 
12
17
 
13
18
  def write_citation(metadata):
@@ -3,6 +3,7 @@
3
3
  import orjson as json
4
4
  import orjsonl
5
5
  import pydash as py_
6
+
6
7
  from ..base_utils import compact
7
8
 
8
9
 
@@ -1,6 +1,7 @@
1
1
  """Crossref XML writer for commonmeta-py"""
2
2
 
3
3
  from typing import Optional
4
+
4
5
  from ..constants import Commonmeta
5
6
  from ..crossref_utils import generate_crossref_xml, generate_crossref_xml_list
6
7
 
@@ -1,13 +1,14 @@
1
1
  """CSL-JSON writer for commonmeta-py"""
2
2
 
3
- import orjson as json
4
3
  from typing import Optional
5
4
 
6
- from ..utils import pages_as_string, to_csl
7
- from ..base_utils import wrap, presence, parse_attributes, compact
5
+ import orjson as json
6
+
7
+ from ..base_utils import compact, parse_attributes, presence, wrap
8
+ from ..constants import CM_TO_CSL_TRANSLATIONS, Commonmeta
8
9
  from ..date_utils import get_date_parts
9
10
  from ..doi_utils import doi_from_url
10
- from ..constants import CM_TO_CSL_TRANSLATIONS, Commonmeta
11
+ from ..utils import pages_as_string, to_csl
11
12
 
12
13
 
13
14
  def write_csl(metadata: Commonmeta) -> Optional[str]:
@@ -1,19 +1,20 @@
1
1
  """DataCite writer for commonmeta-py"""
2
2
 
3
- import orjson as json
4
3
  from typing import Optional, Union
5
4
 
6
- from ..base_utils import wrap, compact
7
- from ..doi_utils import doi_from_url, normalize_doi
5
+ import orjson as json
6
+
7
+ from ..base_utils import compact, wrap
8
8
  from ..constants import (
9
9
  CM_TO_BIB_TRANSLATIONS,
10
- CM_TO_CSL_TRANSLATIONS,
11
10
  CM_TO_CR_TRANSLATIONS,
11
+ CM_TO_CSL_TRANSLATIONS,
12
12
  CM_TO_DC_TRANSLATIONS,
13
13
  CM_TO_RIS_TRANSLATIONS,
14
14
  CM_TO_SO_TRANSLATIONS,
15
15
  Commonmeta,
16
16
  )
17
+ from ..doi_utils import doi_from_url, normalize_doi
17
18
 
18
19
 
19
20
  def write_datacite(metadata: Commonmeta) -> Optional[Union[str, dict]]:
@@ -1,23 +1,24 @@
1
1
  """InvenioRDM writer for commonmeta-py"""
2
2
 
3
- import orjson as json
4
3
  from typing import Optional
5
4
 
6
- from ..base_utils import compact, wrap, parse_attributes, presence
7
- from ..date_utils import get_iso8601_date
8
- from ..doi_utils import doi_from_url, normalize_doi
5
+ import orjson as json
6
+
7
+ from ..base_utils import compact, parse_attributes, presence, wrap
9
8
  from ..constants import (
10
9
  CM_TO_INVENIORDM_TRANSLATIONS,
11
- INVENIORDM_IDENTIFIER_TYPES,
12
10
  CROSSREF_FUNDER_ID_TO_ROR_TRANSLATIONS,
11
+ INVENIORDM_IDENTIFIER_TYPES,
13
12
  )
13
+ from ..date_utils import get_iso8601_date
14
+ from ..doi_utils import doi_from_url, normalize_doi
14
15
  from ..utils import (
16
+ FOS_MAPPINGS,
15
17
  get_language,
16
- validate_orcid,
17
- validate_ror,
18
18
  id_from_url,
19
19
  normalize_url,
20
- FOS_MAPPINGS,
20
+ validate_orcid,
21
+ validate_ror,
21
22
  )
22
23
 
23
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commonmeta-py
3
- Version: 0.106
3
+ Version: 0.107
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
@@ -22,6 +22,7 @@ Requires-Dist: edtf<6,>=5.0.0
22
22
  Requires-Dist: furl<3,>=2.1.3
23
23
  Requires-Dist: jsonschema~=4.21
24
24
  Requires-Dist: lxml>=4.8
25
+ Requires-Dist: marshmallow-utils>=0.10.0
25
26
  Requires-Dist: nameparser<2,>=1.1.1
26
27
  Requires-Dist: nh3<0.3,>=0.2.14
27
28
  Requires-Dist: orjson<4,>=3.9.14
@@ -1,16 +1,16 @@
1
- commonmeta/__init__.py,sha256=4QbwqizFuHpgUA-IbFXoocikH5j9AXSEAHpyKfF3lOk,1933
1
+ commonmeta/__init__.py,sha256=Vz7vv934Mmzs1qxlSSx2bkuMVYtdkFJvvikHMihyzAA,2220
2
2
  commonmeta/api_utils.py,sha256=jkM1d4jgvdZ5k0gkLeg8NDXsizPEesiHNB9sF7P-gFA,2687
3
3
  commonmeta/author_utils.py,sha256=3lYW5s1rOUWNTKs1FP6XLfEUY3yCLOe_3L_VdJTDMp0,8585
4
4
  commonmeta/base_utils.py,sha256=TCaMENjyySStUxFowg6R08Vt5eLkHfW7WjdEHVcAZWo,3752
5
5
  commonmeta/cli.py,sha256=_sMYNzfiEgQ1qx6tZPMbOp1GZ9BBiWe0fYF3jJlamKU,6251
6
6
  commonmeta/constants.py,sha256=NuugDPtaAlYx7d-0WKPHNO-qnjt4rzcj-EbzL2vSi2Q,19259
7
- commonmeta/crossref_utils.py,sha256=mEXNP7LQXcJyEmCqcpWxi_VI3GU1ZD_XvarYjUMxPL0,22284
7
+ commonmeta/crossref_utils.py,sha256=odkcP_Ivk0Fs_BvUzxdPQ4F2098RIsKQT_NbZGQXo4A,24354
8
8
  commonmeta/date_utils.py,sha256=H2cCobX0JREIUOT_cCigGd3MG7prGiQpXk1m4ZNrFwU,6318
9
9
  commonmeta/doi_utils.py,sha256=ZztajfOLtnASk1BbQ1Y2Q4B_xxlnbujn7Opx5a1U5vY,9582
10
10
  commonmeta/metadata.py,sha256=qgqbrvxPEbYn0pKIArhpaGjIeiHeuubc6nF37QgWp2Y,16702
11
11
  commonmeta/schema_utils.py,sha256=yEb8Bt7WqjfQW5y15TV1uaZZdK_k9Ufz6V7Nq7Ywhi0,917
12
12
  commonmeta/translators.py,sha256=CBMK4jrXRmGZiAhCh6wsJjhbDJWbcsda8UvXFXxccAw,1363
13
- commonmeta/utils.py,sha256=APDXdeR2oWyn15vcQPJjPisRHn14hzt1q_21vPxkFCE,48589
13
+ commonmeta/utils.py,sha256=G4BL_YQ-Ug0XwBHU4L83OViRQCiyOqZXg5uJC7WQZ2A,50462
14
14
  commonmeta/readers/__init__.py,sha256=vOf7UsOKNoh_ZCuyexxhAmPMt8wjB-pF_CfpWRaN8pk,45
15
15
  commonmeta/readers/bibtex_reader.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  commonmeta/readers/cff_reader.py,sha256=kIG-OKGQz37oWL6cU3Cz7HQ83DnRCmQ2PKSjIDodSp8,6152
@@ -61,16 +61,16 @@ commonmeta/resources/styles/modern-language-association.csl,sha256=HI2iU4krze1aH
61
61
  commonmeta/resources/styles/vancouver.csl,sha256=lun3_i2oTilgsANk4LjFao2UDPQlGj_hgFgKAWC_DF8,12878
62
62
  commonmeta/writers/__init__.py,sha256=47-snms6xBHkoEXKYV1DBtH1npAtlVtvY29Z4Zr45qI,45
63
63
  commonmeta/writers/bibtex_writer.py,sha256=RjxpFt19N2PjX7cNtvN8kMYTxLWp_KhLYTsQNxYiC-4,4972
64
- commonmeta/writers/citation_writer.py,sha256=7CIC6zuT87dkqTDx-r7xvdbXauAFopNiBvR4CBThr5U,2357
65
- commonmeta/writers/commonmeta_writer.py,sha256=mu-yXQvQs_xdYM4MxbTTxxYB5n0lJA5BqiYfRPV562Q,1870
66
- commonmeta/writers/crossref_xml_writer.py,sha256=ckEOL1q7x8obWLzoMrLuOiDuRKiXHammI1m_GA85BVg,493
67
- commonmeta/writers/csl_writer.py,sha256=_5tmPFLzEIaRgYI9Wz00nPwtfXmBuukCrTVUEFuf83c,2808
68
- commonmeta/writers/datacite_writer.py,sha256=rygkSNUWdF4lvGd4BsjrlBfPHHsSLwkFp-aYLGOWNiU,6305
69
- commonmeta/writers/inveniordm_writer.py,sha256=M_VmBZ_E5x_BHSgMjt2u70c34Eij2ngoaDwYWOrJzkg,11429
64
+ commonmeta/writers/citation_writer.py,sha256=qs_4X3BjrSqHexmJFPvPDTp0mRIqzb0F70_Wuc7S9x0,2343
65
+ commonmeta/writers/commonmeta_writer.py,sha256=erIIcAGBy9cZb6FWSR6P0qzxr1c6iS3oCgKbGAsA94o,1871
66
+ commonmeta/writers/crossref_xml_writer.py,sha256=REORiWPyT2x13Y66IT1RNtOwB9NWutrzNCt_HLeQrsY,494
67
+ commonmeta/writers/csl_writer.py,sha256=IU70HwS77DD-NhQjc4TEGQVSaF-1TgIvyVR9E_zOqy0,2809
68
+ commonmeta/writers/datacite_writer.py,sha256=vijXscY411UMC72WL9VUYJhBtH6ZCPQTk8xtCo1jYtQ,6306
69
+ commonmeta/writers/inveniordm_writer.py,sha256=vGCItVrxkzPgvV-_m-cYnxTSux6mc3eer4JpI-10JhU,11430
70
70
  commonmeta/writers/ris_writer.py,sha256=CXd9MiSHOMLReeGmr1moqteiq4x8AtLNYVS60s-aq8I,2094
71
71
  commonmeta/writers/schema_org_writer.py,sha256=J2Y-krzyhgBZ6n__S_YvdjUtwiCm-RUtpGA_esFZUwg,5742
72
- commonmeta_py-0.106.dist-info/METADATA,sha256=JsAnwb_wOkMWQs7f4oc7VB9Ig6XPOY6b78n6YUqqwDE,7511
73
- commonmeta_py-0.106.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
74
- commonmeta_py-0.106.dist-info/entry_points.txt,sha256=U4w4BoRuS3rN5t5Y-uYSyOeU5Lh_VRVMS9OIDzIgw4w,50
75
- commonmeta_py-0.106.dist-info/licenses/LICENSE,sha256=wsIvxF9Q9GC9vA_s79zTWP3BkXJdfUNRmALlU8GbW1s,1074
76
- commonmeta_py-0.106.dist-info/RECORD,,
72
+ commonmeta_py-0.107.dist-info/METADATA,sha256=1jO_8bgd6pHyUSLCr_LKKRwEYy73kXIgJJaEBowdalw,7552
73
+ commonmeta_py-0.107.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
74
+ commonmeta_py-0.107.dist-info/entry_points.txt,sha256=U4w4BoRuS3rN5t5Y-uYSyOeU5Lh_VRVMS9OIDzIgw4w,50
75
+ commonmeta_py-0.107.dist-info/licenses/LICENSE,sha256=wsIvxF9Q9GC9vA_s79zTWP3BkXJdfUNRmALlU8GbW1s,1074
76
+ commonmeta_py-0.107.dist-info/RECORD,,