opencost 0.1.0__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.
opencost/__init__.py ADDED
@@ -0,0 +1,100 @@
1
+ """Pydantic models for the openCost metadata schema, with XML (de)serialization.
2
+
3
+ openCost (https://github.com/opencost-de/opencost) is a metadata schema for
4
+ the financial side of scholarly publishing: article-level cost data
5
+ (``publication``) and contracts / transformative agreements (``contract``).
6
+
7
+ >>> from opencost import Data, PublicationType, from_xml, to_xml
8
+ >>> xml = to_xml(Data(publication=[...])) # models -> openCost XML
9
+ >>> data = from_xml('<data xmlns="https://opencost.de">…</data>') # XML -> models
10
+ """
11
+
12
+ from ._types import NonEmptyString as NonEmptyString
13
+ from ._types import Currency as Currency
14
+ from ._types import DateFormat as DateFormat
15
+ from ._types import ContractCostType as ContractCostType
16
+ from ._types import PublicationCostType as PublicationCostType
17
+ from ._common import Data as Data
18
+ from ._contract import ContractType as ContractType
19
+ from ._contract import ContractPrimaryIdentifier as ContractPrimaryIdentifier
20
+ from ._contract import ContractPrimaryIdentifierType as ContractPrimaryIdentifierType
21
+ from ._contract import ContractSecondaryIdType as ContractSecondaryIdType
22
+ from ._contract import ContractSecondaryIdTypeEnum as ContractSecondaryIdTypeEnum
23
+ from ._contract import ContractSecondaryIdentifiersType as ContractSecondaryIdentifiersType
24
+ from ._contract import ParticipationType as ParticipationType
25
+ from ._invoice import PublicationInvoiceType as PublicationInvoiceType
26
+ from ._invoice import PublicationAmountPaidType as PublicationAmountPaidType
27
+ from ._invoice import PublicationAmountsPaid as PublicationAmountsPaid
28
+ from ._invoice import AmountInvoice as AmountInvoice
29
+ from ._invoice import Dates as Dates
30
+ from ._invoice import ContractCostDataType as ContractCostDataType
31
+ from ._invoice import ContractAmountPaidType as ContractAmountPaidType
32
+ from ._invoice import ContractAmountsPaid as ContractAmountsPaid
33
+ from ._invoice import ContractInvoiceType as ContractInvoiceType
34
+ from ._invoice import ContractInvoicePeriodType as ContractInvoicePeriodType
35
+ from ._invoice import ContractInvoiceGroupType as ContractInvoiceGroupType
36
+ from ._institution import InstitutionType as InstitutionType
37
+ from ._institution import InstitutionId as InstitutionId
38
+ from ._institution import InstitutionIdType as InstitutionIdType
39
+ from ._institution import InstitutionName as InstitutionName
40
+ from ._institution import InstitutionNameType as InstitutionNameType
41
+ from ._publication import PublicationType as PublicationType
42
+ from ._publication import PublicationPrimaryIdentifier as PublicationPrimaryIdentifier
43
+ from ._publication import PublicationSecondaryIdType as PublicationSecondaryIdType
44
+ from ._publication import PublicationSecondaryIdTypeEnum as PublicationSecondaryIdTypeEnum
45
+ from ._publication import PublicationSecondaryIdentifiers as PublicationSecondaryIdentifiers
46
+ from ._publication import BibliographicInformation as BibliographicInformation
47
+ from ._publication import CoarPublicationType as CoarPublicationType
48
+ from ._publication import PublicationCostDataType as PublicationCostDataType
49
+ from ._publication import PartOfContractType as PartOfContractType
50
+ from ._validators import EitherFieldMixin as EitherFieldMixin
51
+ from ._validators import OpenCostModel as OpenCostModel
52
+ from .xml import NAMESPACE as NAMESPACE
53
+ from .xml import from_xml as from_xml
54
+ from .xml import to_xml as to_xml
55
+
56
+ __all__ = [
57
+ "NonEmptyString",
58
+ "Currency",
59
+ "DateFormat",
60
+ "ContractCostType",
61
+ "PublicationCostType",
62
+ "Data",
63
+ "ContractType",
64
+ "ContractPrimaryIdentifier",
65
+ "ContractPrimaryIdentifierType",
66
+ "ContractSecondaryIdType",
67
+ "ContractSecondaryIdTypeEnum",
68
+ "ContractSecondaryIdentifiersType",
69
+ "ParticipationType",
70
+ "PublicationInvoiceType",
71
+ "PublicationAmountPaidType",
72
+ "PublicationAmountsPaid",
73
+ "AmountInvoice",
74
+ "Dates",
75
+ "ContractCostDataType",
76
+ "ContractAmountPaidType",
77
+ "ContractAmountsPaid",
78
+ "ContractInvoiceType",
79
+ "ContractInvoicePeriodType",
80
+ "ContractInvoiceGroupType",
81
+ "InstitutionType",
82
+ "InstitutionId",
83
+ "InstitutionIdType",
84
+ "InstitutionName",
85
+ "InstitutionNameType",
86
+ "PublicationType",
87
+ "PublicationPrimaryIdentifier",
88
+ "PublicationSecondaryIdType",
89
+ "PublicationSecondaryIdTypeEnum",
90
+ "PublicationSecondaryIdentifiers",
91
+ "BibliographicInformation",
92
+ "CoarPublicationType",
93
+ "PartOfContractType",
94
+ "PublicationCostDataType",
95
+ "EitherFieldMixin",
96
+ "OpenCostModel",
97
+ "NAMESPACE",
98
+ "from_xml",
99
+ "to_xml",
100
+ ]
opencost/_common.py ADDED
@@ -0,0 +1,9 @@
1
+ from ._contract import ContractType
2
+ from ._publication import PublicationType
3
+ from ._validators import EitherFieldMixin
4
+
5
+
6
+ class Data(EitherFieldMixin):
7
+ either_fields = ("publication", "contract")
8
+ publication: list[PublicationType] | None = None
9
+ contract: list[ContractType] | None = None
opencost/_contract.py ADDED
@@ -0,0 +1,103 @@
1
+ # Field descriptions and notes are taken from the openCost documentation
2
+ # (doc/README.md of https://github.com/opencost-de/opencost), GPL-3.0-or-later,
3
+ # vendored as submodule vendor/opencost @ af6d257.
4
+ from enum import Enum
5
+ from typing import Annotated
6
+
7
+ from pydantic import Field
8
+
9
+ from ._institution import InstitutionType
10
+ from ._invoice import ContractCostDataType
11
+ from ._types import DateFormat, NonEmptyString
12
+ from ._validators import OpenCostModel
13
+
14
+
15
+ class ContractPrimaryIdentifierType(Enum):
16
+ """Contract primary identifier scheme (§5.1).
17
+
18
+ Currently only ESAC is accepted.
19
+ """
20
+
21
+ ESAC = "ESAC"
22
+
23
+
24
+ class ContractPrimaryIdentifier(OpenCostModel):
25
+ """Persistent, global identifier for the contract (§5).
26
+
27
+ Currently only an ESAC ID is accepted
28
+ (https://esac-initiative.org/about/transformative-agreements/agreement-registry/).
29
+ """
30
+
31
+ value: NonEmptyString = Field(description="ESAC agreement id of the contract.")
32
+ type: ContractPrimaryIdentifierType = Field(
33
+ description="Identifier scheme; currently only `ESAC`."
34
+ )
35
+
36
+
37
+ class ContractSecondaryIdTypeEnum(Enum):
38
+ """Contract secondary identifier scheme (§6.1.1)."""
39
+
40
+ oai = "oai"
41
+ ezb = "ezb"
42
+ local = "local"
43
+
44
+
45
+ class ContractSecondaryIdType(OpenCostModel):
46
+ """Secondary identifier for the contract (§6.1)."""
47
+
48
+ value: NonEmptyString = Field(description="Identifier value.")
49
+ type: ContractSecondaryIdTypeEnum = Field(description="Identifier scheme of `value`.")
50
+
51
+
52
+ class ContractSecondaryIdentifiersType(OpenCostModel):
53
+ """Contains additional, optional identifiers for the contract (§6)."""
54
+
55
+ id: Annotated[
56
+ list[ContractSecondaryIdType],
57
+ Field(min_length=1, description="Additional (persistent) identifiers."),
58
+ ]
59
+
60
+
61
+ class ParticipationType(OpenCostModel):
62
+ """Contains information on the dates an institution joined and left a
63
+ contract (§4)."""
64
+
65
+ to: DateFormat = Field(
66
+ description="The date when the institution left the contract. Not to "
67
+ "be confused with the end date of the agreement itself, which may be "
68
+ "later."
69
+ )
70
+ from_: DateFormat = Field(
71
+ ...,
72
+ alias="from",
73
+ description="The date when the institution joined the contract. Not "
74
+ "to be confused with the start date of the agreement itself, which "
75
+ "may be earlier.",
76
+ )
77
+
78
+
79
+ class ContractType(OpenCostModel):
80
+ """Top-level element, corresponds to a contract for which costs are to be
81
+ recorded (§1).
82
+
83
+ Examples of such contracts are transformative agreements and memberships.
84
+ """
85
+
86
+ contract_name: NonEmptyString = Field(description="A human-readable label for the contract.")
87
+ institution: InstitutionType = Field(
88
+ description="Contains information to identify the institution taking part in the contract."
89
+ )
90
+ participation: ParticipationType = Field(
91
+ description="The dates the institution joined and left the contract."
92
+ )
93
+ primary_identifier: ContractPrimaryIdentifier = Field(
94
+ description="Persistent, global identifier for the contract; "
95
+ "currently only an ESAC ID is accepted."
96
+ )
97
+ secondary_identifiers: ContractSecondaryIdentifiersType | None = Field(
98
+ default=None,
99
+ description="Additional, optional identifiers for the contract.",
100
+ )
101
+ cost_data: ContractCostDataType = Field(
102
+ description="Aggregates payments related to this contract."
103
+ )
@@ -0,0 +1,59 @@
1
+ # Field descriptions and notes are taken from the openCost documentation
2
+ # (doc/README.md of https://github.com/opencost-de/opencost), GPL-3.0-or-later,
3
+ # vendored as submodule vendor/opencost @ af6d257.
4
+ from enum import Enum
5
+
6
+ from pydantic import Field
7
+
8
+ from ._types import NonEmptyString
9
+ from ._validators import EitherFieldMixin, OpenCostModel
10
+
11
+
12
+ class InstitutionIdType(Enum):
13
+ """Identifier scheme an institution can be referenced by."""
14
+
15
+ ror = "ror"
16
+ isni = "isni"
17
+ ringold = "ringold"
18
+
19
+
20
+ class InstitutionId(OpenCostModel):
21
+ """Contains persistent identifiers for an institution (§4.1, §3.1)."""
22
+
23
+ value: NonEmptyString = Field(description="Institution identifier value, e.g. a ROR id.")
24
+ type: InstitutionIdType = Field(description="Identifier scheme of `value`.")
25
+
26
+
27
+ class InstitutionNameType(Enum):
28
+ """Whether the name is given in full or abbreviated."""
29
+
30
+ full = "full"
31
+ short = "short"
32
+
33
+
34
+ class InstitutionName(OpenCostModel):
35
+ """Human-readable institution name (§4.2, §3.2)."""
36
+
37
+ value: NonEmptyString = Field(description="Name of the institution.")
38
+ type: InstitutionNameType = Field(description="`full` or `short` name form.")
39
+
40
+
41
+ class InstitutionType(EitherFieldMixin):
42
+ """Contains information to identify the institution.
43
+
44
+ Either `id` (persistent identifiers) or `name` (human-readable) must be
45
+ given.
46
+ """
47
+
48
+ either_fields = ("name", "id")
49
+ name: list[InstitutionName] | None = Field(
50
+ default=None,
51
+ description=(
52
+ "The names are meant to be human-readable. Either this element or "
53
+ "the institution's `id` is required."
54
+ ),
55
+ )
56
+ id: list[InstitutionId] | None = Field(
57
+ default=None,
58
+ description="Either this element or the institution's `name` is required.",
59
+ )
opencost/_invoice.py ADDED
@@ -0,0 +1,218 @@
1
+ # Field descriptions and notes are taken from the openCost documentation
2
+ # (doc/README.md of https://github.com/opencost-de/opencost), GPL-3.0-or-later,
3
+ # vendored as submodule vendor/opencost @ af6d257.
4
+ from decimal import Decimal
5
+ from typing import Annotated
6
+
7
+ from pydantic import Field
8
+
9
+ from ._types import ContractCostType, Currency, DateFormat, NonEmptyString, PublicationCostType
10
+ from ._validators import EitherFieldMixin, OpenCostModel
11
+
12
+
13
+ class PublicationAmountPaidType(OpenCostModel):
14
+ """A single amount paid by the institution (§7.2.5.1).
15
+
16
+ Usually corresponding to an item on the invoice.
17
+ """
18
+
19
+ currency: Currency = Field(description="ISO 4217 currency code of the `amount_paid`.")
20
+ amount: Decimal = Field(
21
+ description="Amount of the `amount_paid`. Net monetary value. "
22
+ "Negative or zero values are valid to denote reimbursements or "
23
+ "Diamond OA models."
24
+ )
25
+ cost_type: PublicationCostType = Field(
26
+ description="Describes the object/purpose of the payment. If the VAT "
27
+ "cannot be subdivided specifically for the individual cost types, but "
28
+ "only for the total amount of the invoice, a separate entry with the "
29
+ "`cost_type` `vat` is possible here instead of the `vat` field "
30
+ "(7.2.5.1.4)."
31
+ )
32
+ vat: Decimal | None = Field(
33
+ default=None,
34
+ description="Vat of the `amount_paid`. State in the same currency as "
35
+ "`amount`. Must be specified and assigned according to the individual "
36
+ "cost type; if a differentiated specification is not possible, use a "
37
+ "separate entry with `cost_type` `vat` (7.2.5.1.3).",
38
+ )
39
+
40
+
41
+ class PublicationAmountsPaid(OpenCostModel):
42
+ """Contains all itemized amounts paid corresponding to one invoice (§7.2.5)."""
43
+
44
+ amount_paid: Annotated[
45
+ list[PublicationAmountPaidType],
46
+ Field(min_length=1, description="The itemized amounts paid."),
47
+ ]
48
+
49
+
50
+ class AmountInvoice(OpenCostModel):
51
+ """Contains the total price as stated on the invoice (§7.2.4, §7.1.3.4)."""
52
+
53
+ currency: Currency = Field(
54
+ description="ISO 4217 currency code of the total amount as stated on the invoice."
55
+ )
56
+ amount: Decimal = Field(
57
+ description="Total amount as stated on the invoice. Net monetary value."
58
+ )
59
+
60
+
61
+ class Dates(EitherFieldMixin):
62
+ """Contains different payment-related dates (§7.2.3).
63
+
64
+ At least one of `invoice` or `paid` must be given.
65
+ """
66
+
67
+ either_fields = ("invoice", "paid")
68
+
69
+ invoice: DateFormat | None = Field(
70
+ default=None,
71
+ description="Invoice date. Either this element or the date `paid` is required.",
72
+ )
73
+ paid: DateFormat | None = Field(
74
+ default=None,
75
+ description="Date of payment. Either this element or the invoice date is required.",
76
+ )
77
+
78
+
79
+ class PublicationInvoiceType(OpenCostModel):
80
+ """Encapsulates payment information, meant to correspond to a real-world
81
+ invoice (§7.2)."""
82
+
83
+ amount_invoice: AmountInvoice | None = Field(
84
+ default=None,
85
+ description="The total price as stated on the invoice.",
86
+ )
87
+ invoice_number: NonEmptyString | None = Field(
88
+ default=None,
89
+ description="Optional invoice number. As mentioned on the invoice.",
90
+ )
91
+ amounts_paid: PublicationAmountsPaid = Field(
92
+ description="All itemized amounts paid corresponding to this invoice."
93
+ )
94
+ dates: Dates = Field(description="Payment-related dates of this invoice.")
95
+ creditor: NonEmptyString | None = Field(
96
+ default=None,
97
+ description="Payment receiver as mentioned on the invoice. Might not "
98
+ "be the actual publisher, but also e.g. a service provider from "
99
+ "another publisher.",
100
+ )
101
+
102
+
103
+ class ContractAmountPaidType(OpenCostModel):
104
+ """An amount paid by the institution (§7.1.3.5.1).
105
+
106
+ Usually corresponding to an item on the invoice.
107
+ """
108
+
109
+ currency: Currency = Field(description="ISO 4217 currency code of the `amount_paid`.")
110
+ amount: Decimal = Field(
111
+ description="Amount of the `amount_paid`. Net monetary value. "
112
+ "Negative or zero values are valid to denote reimbursements."
113
+ )
114
+ cost_type: ContractCostType = Field(
115
+ description="Describes the object/purpose of the payment. If the VAT "
116
+ "cannot be subdivided specifically for the individual cost types, but "
117
+ "only for the total amount of the invoice, a separate entry with the "
118
+ "`cost_type` `vat` is possible here instead of the `vat` field "
119
+ "(7.1.3.5.1.4)."
120
+ )
121
+ vat: Decimal | None = Field(
122
+ default=None,
123
+ description="Vat of the `amount_paid`. State in the same currency as "
124
+ "`amount`. Must be specified and assigned according to the individual "
125
+ "cost type; if a differentiated specification is not possible, use a "
126
+ "separate entry with `cost_type` `vat` (7.1.3.5.1.3).",
127
+ )
128
+
129
+
130
+ class ContractAmountsPaid(OpenCostModel):
131
+ """Contains all itemized amounts paid corresponding to one invoice of a
132
+ contract (§7.1.3.5)."""
133
+
134
+ amount_paid: Annotated[
135
+ list[ContractAmountPaidType],
136
+ Field(min_length=1, description="The itemized amounts paid."),
137
+ ]
138
+
139
+
140
+ class ContractInvoiceType(OpenCostModel):
141
+ """Encapsulates payment information for a contract, meant to correspond to
142
+ a real-world invoice (§7.1.3)."""
143
+
144
+ amount_invoice: AmountInvoice | None = Field(
145
+ default=None,
146
+ description="The total price as stated on the invoice for the contract.",
147
+ )
148
+ invoice_number: NonEmptyString | None = Field(
149
+ default=None,
150
+ description="Optional invoice number. As mentioned on the invoice.",
151
+ )
152
+ creditor: NonEmptyString | None = Field(
153
+ default=None,
154
+ description="Payment receiver as mentioned on the invoice. Might not "
155
+ "be the actual publisher, but also e.g. a service provider.",
156
+ )
157
+ dates: Dates = Field(description="Payment-related dates of this invoice.")
158
+ amounts_paid: ContractAmountsPaid = Field(
159
+ description="All itemized amounts paid corresponding to this invoice."
160
+ )
161
+
162
+
163
+ class ContractInvoicePeriodType(OpenCostModel):
164
+ """Identifies the time frame an invoice refers to (§7.1.2)."""
165
+
166
+ from_: DateFormat = Field(
167
+ ...,
168
+ alias="from",
169
+ description="Start date of the time frame the invoice refers to. Can "
170
+ "be different to an institution's contract accession "
171
+ "(`participation // from`) if multiple invoices have been issued over "
172
+ "the total contract duration.",
173
+ )
174
+ to: DateFormat = Field(
175
+ description="End date of the time frame the invoice refers to. Can be "
176
+ "different to an institution's contract termination "
177
+ "(`participation // to`) if multiple invoices have been issued over "
178
+ "the total contract duration."
179
+ )
180
+
181
+
182
+ class ContractInvoiceGroupType(OpenCostModel):
183
+ """Contains all invoices that belong to a billing period within a contract
184
+ (§7.1).
185
+
186
+ Such a contract phase is limited by a time period (`invoices_period`) to
187
+ which a respective invoice can be assigned.
188
+ """
189
+
190
+ group_id: NonEmptyString = Field(
191
+ description="An id to uniquely name and identify the `invoice_group`. "
192
+ "Necessary to link not only from an individual publication to the "
193
+ "global contract but also to a specific invoice and contract period: "
194
+ "relates cost data for an `opencost:publication` to this contract via "
195
+ "the `opencost:part_of_contract` element. Generating a `uuid` or "
196
+ "using another type of unique identifier is recommended."
197
+ )
198
+ invoices_period: ContractInvoicePeriodType = Field(
199
+ description="Identifies the time frame the invoices in this group refer to."
200
+ )
201
+ invoice: list[ContractInvoiceType] | None = Field(
202
+ default=None,
203
+ description="The invoices belonging to this billing period.",
204
+ )
205
+
206
+
207
+ class ContractCostDataType(OpenCostModel):
208
+ """Aggregates payments related to a contract (§7, contract).
209
+
210
+ The `invoice` elements can be combined into one or more common
211
+ `invoice_group` elements referring to a shared `invoices_period` of the
212
+ contract concerned.
213
+ """
214
+
215
+ invoice_group: Annotated[
216
+ list[ContractInvoiceGroupType],
217
+ Field(min_length=1, description="The billing periods of the contract."),
218
+ ]