python-easyverein 2.1.2__tar.gz → 2.3.0__tar.gz

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.
Files changed (47) hide show
  1. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/PKG-INFO +21 -6
  2. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/README.md +6 -3
  3. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/__init__.py +1 -1
  4. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/api.py +2 -0
  5. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/core/client.py +32 -0
  6. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/core/types.py +3 -1
  7. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/__init__.py +8 -1
  8. python_easyverein-2.3.0/easyverein/models/billing_account.py +65 -0
  9. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/booking.py +6 -4
  10. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/contact_details.py +1 -1
  11. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/custom_field.py +6 -13
  12. python_easyverein-2.3.0/easyverein/models/custom_field_select_option.py +60 -0
  13. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/invoice.py +2 -2
  14. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/invoice_item.py +4 -3
  15. python_easyverein-2.3.0/easyverein/models/lsb_dosb_sport.py +12 -0
  16. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/member.py +43 -16
  17. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/member_custom_field.py +3 -1
  18. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/member_group.py +6 -1
  19. python_easyverein-2.3.0/easyverein/modules/billing_account.py +27 -0
  20. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/booking.py +2 -1
  21. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/contact_details.py +2 -1
  22. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/custom_field.py +4 -0
  23. python_easyverein-2.3.0/easyverein/modules/custom_field_select_option.py +31 -0
  24. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/invoice.py +2 -1
  25. python_easyverein-2.3.0/easyverein/modules/member.py +49 -0
  26. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/member_custom_field.py +31 -4
  27. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/mixins/crud.py +68 -0
  28. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/pyproject.toml +14 -12
  29. python_easyverein-2.1.2/easyverein/modules/member.py +0 -27
  30. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/LICENSE +0 -0
  31. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/core/__init__.py +0 -0
  32. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/core/exceptions.py +0 -0
  33. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/core/protocol.py +0 -0
  34. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/core/responses.py +0 -0
  35. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/core/validators.py +0 -0
  36. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/base.py +0 -0
  37. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/member_member_group.py +0 -0
  38. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/mixins/__init__.py +0 -0
  39. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/mixins/empty_strings_mixin.py +0 -0
  40. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/models/mixins/required_attributes.py +0 -0
  41. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/__init__.py +0 -0
  42. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/invoice_item.py +0 -0
  43. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/member_group.py +0 -0
  44. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/member_member_group.py +0 -0
  45. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/mixins/__init__.py +0 -0
  46. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/mixins/helper.py +0 -0
  47. {python_easyverein-2.1.2 → python_easyverein-2.3.0}/easyverein/modules/mixins/recycle_bin.py +0 -0
@@ -1,12 +1,24 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: python-easyverein
3
- Version: 2.1.2
3
+ Version: 2.3.0
4
4
  Summary: Python library to interact with the EasyVerein API
5
+ License: Copyright 2023 Daniel Herrmann
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
+ License-File: LICENSE
5
13
  Author: Daniel Herrmann
6
14
  Author-email: daniel.herrmann1@gmail.com
7
- Requires-Python: >=3.11,<4.0
15
+ Requires-Python: >=3.11,<4
16
+ Classifier: License :: Other/Proprietary License
8
17
  Classifier: Programming Language :: Python :: 3
9
18
  Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
10
22
  Requires-Dist: email-validator (>=2,<3)
11
23
  Requires-Dist: pydantic (>=2,<3)
12
24
  Requires-Dist: requests (>=2,<3)
@@ -44,11 +56,14 @@ Not all endpoints offered by the EasyVerein API are supported. For now, only the
44
56
  When saying CRUD, it means the library supports various methods to **C**reate, **R**ead, **U**pdate and
45
57
  **D**elete objects. See the API reference for details on supported CRUD operations.
46
58
 
47
- * `contact-details`: CRUD, Soft-Delete
59
+ * `booking`: CRUD, Bulk Create and Update, Soft-Delete
60
+ * `billing-account`: CRUD, Soft-Delete
61
+ * `contact-details`: CRUD, Bulk Create and Update, Soft-Delete
48
62
  * `custom-fields`: CRUD, Soft-Delete
49
- * `invoice`: CRUD, Soft-Delete, plus some convenience methods
63
+ * `custom-fields/<id>/select-options`: CRUD
64
+ * `invoice`: CRUD, Bulk Create and Update, Soft-Delete, plus some convenience methods
50
65
  * `invoice-item`: CRUD
51
- * `member`: CRUD, Soft-Delete
66
+ * `member`: CRUD, Bulk Create and Update, Soft-Delete
52
67
  * `member-groups`: CRUD, Soft-Delete
53
68
  * `member/<id>/custom-fields`: CRUD, plus some convenience methods
54
69
  * `member/<id>/member-groups`: CRUD, plus some convenience methods
@@ -30,11 +30,14 @@ Not all endpoints offered by the EasyVerein API are supported. For now, only the
30
30
  When saying CRUD, it means the library supports various methods to **C**reate, **R**ead, **U**pdate and
31
31
  **D**elete objects. See the API reference for details on supported CRUD operations.
32
32
 
33
- * `contact-details`: CRUD, Soft-Delete
33
+ * `booking`: CRUD, Bulk Create and Update, Soft-Delete
34
+ * `billing-account`: CRUD, Soft-Delete
35
+ * `contact-details`: CRUD, Bulk Create and Update, Soft-Delete
34
36
  * `custom-fields`: CRUD, Soft-Delete
35
- * `invoice`: CRUD, Soft-Delete, plus some convenience methods
37
+ * `custom-fields/<id>/select-options`: CRUD
38
+ * `invoice`: CRUD, Bulk Create and Update, Soft-Delete, plus some convenience methods
36
39
  * `invoice-item`: CRUD
37
- * `member`: CRUD, Soft-Delete
40
+ * `member`: CRUD, Bulk Create and Update, Soft-Delete
38
41
  * `member-groups`: CRUD, Soft-Delete
39
42
  * `member/<id>/custom-fields`: CRUD, plus some convenience methods
40
43
  * `member/<id>/member-groups`: CRUD, plus some convenience methods
@@ -2,7 +2,7 @@
2
2
  Middleware for FastAPI that supports authenticating users against Keycloak
3
3
  """
4
4
 
5
- __version__ = "2.1.2"
5
+ __version__ = "2.3.0"
6
6
 
7
7
  # Export EasyVerein API directly
8
8
  from .api import EasyvereinAPI # noqa: F401
@@ -7,6 +7,7 @@ from typing import Callable, cast
7
7
 
8
8
  from .core.client import EasyvereinClient
9
9
  from .core.responses import BearerToken
10
+ from .modules.billing_account import BillingAccountMixin
10
11
  from .modules.booking import BookingMixin
11
12
  from .modules.contact_details import ContactDetailsMixin
12
13
  from .modules.custom_field import CustomFieldMixin
@@ -59,6 +60,7 @@ class EasyvereinAPI:
59
60
 
60
61
  # Add methods
61
62
  self.booking = BookingMixin(self.c, self.logger)
63
+ self.billing_account = BillingAccountMixin(self.c, self.logger)
62
64
  self.contact_details = ContactDetailsMixin(self.c, self.logger)
63
65
  self.custom_field = CustomFieldMixin(self.c, self.logger)
64
66
  self.invoice = InvoiceMixin(self.c, self.logger)
@@ -204,6 +204,38 @@ class EasyvereinClient:
204
204
  expected_status_code=status_code,
205
205
  )
206
206
 
207
+ def bulk_create(self, url, data: list[BaseModel], status_code: int = 201) -> ResponseSchema:
208
+ """
209
+ Method to create multiple objects in the API
210
+ """
211
+ return self._handle_response(
212
+ self._do_request(
213
+ "post",
214
+ url,
215
+ data={"entries": [d.model_dump(exclude_none=True, exclude_unset=True, by_alias=True) for d in data]},
216
+ ),
217
+ expected_status_code=status_code,
218
+ )
219
+
220
+ def bulk_update(
221
+ self, url, data: list[BaseModel], status_code: int = 200, exclude_none: bool = True
222
+ ) -> ResponseSchema:
223
+ """
224
+ Method to update multiple objects in the API
225
+ """
226
+ return self._handle_response(
227
+ self._do_request(
228
+ "patch",
229
+ url,
230
+ data={
231
+ "entries": [
232
+ d.model_dump(exclude_none=exclude_none, exclude_unset=True, by_alias=True) for d in data
233
+ ]
234
+ },
235
+ ),
236
+ expected_status_code=status_code,
237
+ )
238
+
207
239
  def upload(
208
240
  self,
209
241
  url: str,
@@ -4,7 +4,7 @@ Custom types used for model validation
4
4
 
5
5
  import datetime
6
6
  import json
7
- from typing import Annotated
7
+ from typing import Annotated, Literal
8
8
 
9
9
  from pydantic import Field, PlainSerializer, PlainValidator, UrlConstraints
10
10
  from pydantic_core import Url
@@ -40,3 +40,5 @@ FilterStrList = Annotated[
40
40
  list[str],
41
41
  PlainSerializer(lambda x: ",".join(x), return_type=str),
42
42
  ]
43
+
44
+ Sphere = Literal[1, 2, 3, 4, 9]
@@ -12,6 +12,12 @@ from .custom_field import (
12
12
  CustomFieldFilter,
13
13
  CustomFieldUpdate,
14
14
  )
15
+ from .custom_field_select_option import (
16
+ CustomFieldSelectOption,
17
+ CustomFieldSelectOptionCreate,
18
+ CustomFieldSelectOptionFilter,
19
+ CustomFieldSelectOptionUpdate,
20
+ )
15
21
  from .invoice import Invoice, InvoiceCreate, InvoiceFilter, InvoiceUpdate
16
22
  from .invoice_item import (
17
23
  InvoiceItem,
@@ -19,7 +25,7 @@ from .invoice_item import (
19
25
  InvoiceItemFilter,
20
26
  InvoiceItemUpdate,
21
27
  )
22
- from .member import Member, MemberCreate, MemberFilter, MemberUpdate
28
+ from .member import Member, MemberCreate, MemberFilter, MemberSetDosb, MemberSetLsb, MemberUpdate
23
29
  from .member_custom_field import (
24
30
  MemberCustomField,
25
31
  MemberCustomFieldCreate,
@@ -37,6 +43,7 @@ from .member_member_group import (
37
43
  Booking.model_rebuild()
38
44
  ContactDetails.model_rebuild()
39
45
  CustomField.model_rebuild()
46
+ CustomFieldSelectOption.model_rebuild()
40
47
  Invoice.model_rebuild()
41
48
  InvoiceItem.model_rebuild()
42
49
  Member.model_rebuild()
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, PositiveInt
4
+
5
+ from ..core.types import EasyVereinReference, FilterIntList, FilterStrList, PositiveIntWithZero, Sphere
6
+ from .base import EasyVereinBase
7
+ from .mixins.empty_strings_mixin import EmptyStringsToNone
8
+ from .mixins.required_attributes import required_mixin
9
+
10
+
11
+ class BillingAccountBase(EasyVereinBase):
12
+ """
13
+ | Representative Model Class | Update Model Class | Create Model Class |
14
+ | --- | --- | --- |
15
+ | `BillingAccount` | `BillingAccountUpdate` | `BillingAccountCreate` |
16
+ """
17
+
18
+ name: str | None = None
19
+ excludeInEur: bool | None = None
20
+ number: PositiveInt | None = None
21
+ defaultSphere: Sphere | None = None
22
+
23
+
24
+ class BillingAccount(BillingAccountBase, EmptyStringsToNone):
25
+ """
26
+ Pydantic model representing an BillingAccount
27
+ """
28
+
29
+ skr: str | None = None
30
+ accountingPlans: list[EasyVereinReference] | None = None
31
+ numberLength: PositiveIntWithZero
32
+ linkedBookings: PositiveIntWithZero
33
+
34
+
35
+ class BillingAccountCreate(
36
+ BillingAccountBase,
37
+ required_mixin(["name", "number"]), # type: ignore
38
+ ):
39
+ """
40
+ Pydantic model representing a BillingAccount
41
+ """
42
+
43
+
44
+ class BillingAccountUpdate(BillingAccountBase):
45
+ """
46
+ Pydantic model used to patch an BillingAccount
47
+ """
48
+
49
+
50
+ class BillingAccountFilter(BaseModel):
51
+ """
52
+ Pydantic model used to filter billing accounts
53
+ """
54
+
55
+ id__in: FilterIntList | None = None
56
+ name: str | None = None
57
+ skr: str | None = None
58
+ skr__in: FilterStrList | None = None
59
+ number__gte: PositiveInt | None = None
60
+ number__lte: PositiveInt | None = None
61
+ deleted: bool | None = None
62
+ accountingPlan__isnull: bool | None = None
63
+ showOwnBillingAccounts: bool | None = None
64
+ ordering: str | None = None
65
+ search: str | None = None
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
- from ..core.types import DateTime, EasyVereinReference, FilterIntList
9
+ from ..core.types import DateTime, EasyVereinReference, FilterIntList, Sphere
10
10
  from .base import EasyVereinBase
11
11
  from .mixins.empty_strings_mixin import EmptyStringsToNone
12
12
  from .mixins.required_attributes import required_mixin
@@ -22,8 +22,7 @@ class BookingBase(EasyVereinBase):
22
22
  amount: float | None = None
23
23
  # TODO: Add reference to BankAccount once implemented
24
24
  bankAccount: EasyVereinReference | None = None
25
- # TODO: Add reference to BillingAccount once implemented
26
- billingAccount: EasyVereinReference | None = None
25
+ billingAccount: BillingAccount | EasyVereinReference | None = None
27
26
  description: str | None = None
28
27
  date: DateTime | None = None
29
28
  receiver: str | None = None
@@ -34,7 +33,7 @@ class BookingBase(EasyVereinBase):
34
33
  counterpartBic: str | None = None
35
34
  twingleDonation: bool | None = None
36
35
  bookingProject: str | None = None
37
- sphere: int | None = None
36
+ sphere: Sphere | None = None
38
37
  relatedInvoice: list[EasyVereinReference] | None = None
39
38
 
40
39
 
@@ -98,3 +97,6 @@ class BookingFilter(BaseModel):
98
97
  relatedInvoice__isnull: bool | None = None
99
98
  relatedInvoice: int | None = None
100
99
  search: str | None = None
100
+
101
+
102
+ from .billing_account import BillingAccount # noqa: E402
@@ -148,7 +148,7 @@ class ContactDetailsFilter(BaseModel):
148
148
 
149
149
  id__in: FilterIntList | None = None
150
150
  country: str | None = None
151
- isCompany: bool = Field(default=None, serialization_alias="_isCompany")
151
+ isCompany: bool | None = Field(default=None, serialization_alias="_isCompany")
152
152
  preferredCommunicationWay: str | None = None
153
153
  contactDetailsGroups: str | None = None
154
154
  contactDetailsGroups__not: str | None = None
@@ -1,20 +1,10 @@
1
- """
2
- Member related models
3
- """
4
-
5
1
  from __future__ import annotations
6
2
 
7
3
  from typing import Literal
8
4
 
9
5
  from pydantic import BaseModel, Field
10
6
 
11
- from ..core.types import (
12
- EasyVereinReference,
13
- FilterIntList,
14
- HexColor,
15
- OptionsField,
16
- PositiveIntWithZero,
17
- )
7
+ from ..core.types import EasyVereinReference, FilterIntList, HexColor, PositiveIntWithZero
18
8
  from .base import EasyVereinBase
19
9
  from .mixins.empty_strings_mixin import EmptyStringsToNone
20
10
  from .mixins.required_attributes import required_mixin
@@ -67,7 +57,7 @@ class CustomFieldBase(EasyVereinBase):
67
57
  It is not even possible to set other fields except the ones mentioned before in the portal,
68
58
  so not sure what the other values are meant for.
69
59
  """
70
- selectOptions: OptionsField = None
60
+ selectOptions: list[CustomFieldSelectOption | EasyVereinReference] | None = None
71
61
  description: str | None = Field(default=None, max_length=500)
72
62
  member_show: bool | None = None
73
63
  member_edit: bool | None = None
@@ -111,6 +101,9 @@ class CustomFieldFilter(BaseModel):
111
101
  kind: str | None = None
112
102
  member_edit: bool | None = None
113
103
  member_show: bool | None = None
114
- deletedBy__isnull: bool = Field(default=None, serialization_alias="_deletedBy__isnull")
104
+ deletedBy__isnull: bool | None = Field(default=None, serialization_alias="_deletedBy__isnull")
115
105
  deleted: bool | None = None
116
106
  ordering: str | None = None
107
+
108
+
109
+ from .custom_field_select_option import CustomFieldSelectOption # noqa: E402
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from ..core.types import FilterIntList, FilterStrList, PositiveIntWithZero
6
+ from .base import EasyVereinBase
7
+ from .mixins.empty_strings_mixin import EmptyStringsToNone
8
+ from .mixins.required_attributes import required_mixin
9
+
10
+
11
+ class CustomFieldSelectOptionBase(EasyVereinBase):
12
+ """
13
+ | Representative Model Class | Update Model Class | Create Model Class |
14
+ | --- | --- | --- |
15
+ | `CustomFieldSelectOption` | `CustomFieldSelectOptionUpdate` | `CustomFieldSelectOptionCreate` |
16
+
17
+ Custom field select options are used to define the possible values for a custom field.
18
+ Those are only available for select (s) and multiselect (a) custom fields.
19
+ """
20
+
21
+ value: str
22
+ orderSequence: PositiveIntWithZero | None = None
23
+ availableForAssignment: bool | None = None
24
+
25
+
26
+ class CustomFieldSelectOption(CustomFieldSelectOptionBase, EmptyStringsToNone):
27
+ """
28
+ Pydantic model used to represent a custom field select option
29
+ """
30
+
31
+ pass
32
+
33
+
34
+ class CustomFieldSelectOptionCreate(CustomFieldSelectOptionBase, required_mixin(["value"])): # type: ignore
35
+ """
36
+ Pydantic model for creating a new custom field select option
37
+ """
38
+
39
+
40
+ class CustomFieldSelectOptionUpdate(CustomFieldSelectOptionBase):
41
+ """
42
+ Pydantic model used to update a custom field select option
43
+ """
44
+
45
+ pass
46
+
47
+
48
+ class CustomFieldSelectOptionFilter(BaseModel):
49
+ """
50
+ Pydantic model used to filter custom field select options
51
+ """
52
+
53
+ id__in: FilterIntList | None = None
54
+ value: str | None = None
55
+ value__in: FilterStrList | None = None
56
+ maxSelections__isnull: bool | None = None
57
+ maxSelections: PositiveIntWithZero | None = None
58
+ orderSequence__isnull: bool | None = None
59
+ orderSequence: PositiveIntWithZero | None = None
60
+ actuallyRemoved: bool | None = None
@@ -55,8 +55,7 @@ class InvoiceBase(EasyVereinBase):
55
55
  ]
56
56
  | None
57
57
  ) = None
58
- # TODO: Add reference to BillingAccount once implemented
59
- selectionAcc: EasyVereinReference | None = None
58
+ selectionAcc: BillingAccount | EasyVereinReference | None = None
60
59
  refNumber: str | None = None
61
60
  paymentDifference: float | None = None
62
61
  isDraft: bool | None = None
@@ -148,6 +147,7 @@ class InvoiceFilter(BaseModel):
148
147
  search: str | None = None
149
148
 
150
149
 
150
+ from .billing_account import BillingAccount # noqa: E402
151
151
  from .contact_details import ContactDetails # noqa: E402
152
152
  from .invoice_item import InvoiceItem # noqa: E402
153
153
  from .member import Member # noqa: E402
@@ -8,7 +8,7 @@ from typing import Annotated
8
8
 
9
9
  from pydantic import BaseModel, Field, PositiveInt
10
10
 
11
- from ..core.types import EasyVereinReference, FilterIntList
11
+ from ..core.types import EasyVereinReference, FilterIntList, Sphere
12
12
  from .base import EasyVereinBase
13
13
  from .mixins.empty_strings_mixin import EmptyStringsToNone
14
14
  from .mixins.required_attributes import required_mixin
@@ -35,8 +35,8 @@ class InvoiceItemBase(EasyVereinBase):
35
35
  taxRate: float | None = None
36
36
  gross: bool | None = None
37
37
  taxName: str | None = None
38
- # TODO: Add reference to BillingAccount once implemented
39
- billingAccount: EasyVereinReference | None = None
38
+ sphere: Sphere | None = None
39
+ billingAccount: BillingAccount | EasyVereinReference | None = None
40
40
  costCentre: Annotated[str, Field(max_length=8)] | None = None
41
41
 
42
42
 
@@ -85,4 +85,5 @@ class InvoiceItemFilter(BaseModel):
85
85
  search: str | None = None
86
86
 
87
87
 
88
+ from .billing_account import BillingAccount # noqa: E402
88
89
  from .invoice import Invoice # noqa: E402
@@ -0,0 +1,12 @@
1
+ from .base import EasyVereinBase
2
+ from .mixins.empty_strings_mixin import EmptyStringsToNone
3
+
4
+
5
+ class LsbDosbSport(EasyVereinBase, EmptyStringsToNone):
6
+ """
7
+ Pydantic Model for a LSB/DOSB Sport.
8
+ """
9
+
10
+ title: str | None = None
11
+ sportNumber: str | None = None
12
+ federationNumber: str | None = None
@@ -63,7 +63,6 @@ class MemberBase(EasyVereinBase):
63
63
  paymentIntervallMonths: PositiveInt | Literal[-1] = 1
64
64
  useBalanceForMembershipFee: bool | None = None
65
65
  bulletinBoardNewPostNotification: bool | None = None
66
- integrationDosbGender: Literal["m", "w", "d"] | None = None
67
66
  isApplication: bool | None = Field(default=None, alias="_isApplication")
68
67
  """
69
68
  Alias for `_isApplication` field. See [Pydantic Models](../usage.md#pydantic-models) for details.
@@ -83,8 +82,10 @@ class MemberBase(EasyVereinBase):
83
82
  Alias for `_editableByRelatedMembers` field. See [Pydantic Models](../usage.md#pydantic-models) for details.
84
83
  """
85
84
  sepaMandateFile: AnyHttpURL | str | None = None
86
- # TODO: exact type is not specified in API docs
87
- integrationDosbSport: list | None = None
85
+ integrationLsbSport: list[EasyVereinReference] | list[LsbDosbSport] | None = None
86
+ integrationLsbGender: Literal["m", "w", "d"] | None = None
87
+ integrationDosbSport: list[EasyVereinReference] | list[LsbDosbSport] | None = None
88
+ integrationDosbGender: Literal["m", "w", "d"] | None = None
88
89
  customFields: list[EasyVereinReference] | list[MemberCustomField] | None = None
89
90
  memberGroups: list[EasyVereinReference] | list[MemberMemberGroup] | None = None
90
91
 
@@ -135,9 +136,9 @@ class MemberFilter(BaseModel):
135
136
  contactDetails__country: str | None = None
136
137
  membershipNumber: str | None = None
137
138
  membershipNumber__in: FilterStrList | None = None
138
- deletedBy: int = Field(default=None, serialization_alias="_deletedBy")
139
- deletedBy__ne: int = Field(default=None, serialization_alias="_deletedBy__ne")
140
- deletedBy__isnull: bool = Field(default=None, serialization_alias="_deletedBy__isnull")
139
+ deletedBy: int | None = Field(default=None, serialization_alias="_deletedBy")
140
+ deletedBy__ne: int | None = Field(default=None, serialization_alias="_deletedBy__ne")
141
+ deletedBy__isnull: bool | None = Field(default=None, serialization_alias="_deletedBy__isnull")
141
142
  joinDate: DateTime | None = None
142
143
  joinDate__gte: DateTime | None = None
143
144
  joinDate__lte: DateTime | None = None
@@ -146,18 +147,22 @@ class MemberFilter(BaseModel):
146
147
  resignationDate__gte: DateTime | None = None
147
148
  resignationDate__lte: DateTime | None = None
148
149
  resignationDate__isnull: bool | None = None
149
- isApplication: bool = Field(default=None, serialization_alias="_isApplication")
150
- applicationDate: Date = Field(default=None, serialization_alias="_applicationDate")
151
- applicationDate__gte: Date = Field(default=None, serialization_alias="_applicationDate__gte")
152
- applicationDate__lte: Date = Field(default=None, serialization_alias="_applicationDate__lte")
153
- applicationDate__isnull: bool = Field(default=None, serialization_alias="_applicationDate__isnull")
154
- applicationWasAcceptedAt: Date = Field(default=None, serialization_alias="_applicationWasAcceptedAt")
155
- applicationWasAcceptedAt__gte: Date = Field(default=None, serialization_alias="_applicationWasAcceptedAt__gte")
156
- applicationWasAcceptedAt__lte: Date = Field(default=None, serialization_alias="_applicationWasAcceptedAt__lte")
157
- applicationWasAcceptedAt__isnull: bool = Field(
150
+ isApplication: bool | None = Field(default=None, serialization_alias="_isApplication")
151
+ applicationDate: Date | None = Field(default=None, serialization_alias="_applicationDate")
152
+ applicationDate__gte: Date | None = Field(default=None, serialization_alias="_applicationDate__gte")
153
+ applicationDate__lte: Date | None = Field(default=None, serialization_alias="_applicationDate__lte")
154
+ applicationDate__isnull: bool | None = Field(default=None, serialization_alias="_applicationDate__isnull")
155
+ applicationWasAcceptedAt: Date | None = Field(default=None, serialization_alias="_applicationWasAcceptedAt")
156
+ applicationWasAcceptedAt__gte: Date | None = Field(
157
+ default=None, serialization_alias="_applicationWasAcceptedAt__gte"
158
+ )
159
+ applicationWasAcceptedAt__lte: Date | None = Field(
160
+ default=None, serialization_alias="_applicationWasAcceptedAt__lte"
161
+ )
162
+ applicationWasAcceptedAt__isnull: bool | None = Field(
158
163
  default=None, serialization_alias="_applicationWasAcceptedAt__isnull"
159
164
  )
160
- isChairman: bool = Field(default=None, serialization_alias="_isChairman")
165
+ isChairman: bool | None = Field(default=None, serialization_alias="_isChairman")
161
166
  memberGroups: FilterIntList | None = None
162
167
  """
163
168
  Filter for members that are member of the given group(s)
@@ -185,6 +190,28 @@ class MemberFilter(BaseModel):
185
190
  search: str | None = None
186
191
 
187
192
 
193
+ class MemberSetLsb(BaseModel):
194
+ """
195
+ Pydantic model used to set LSB sports for a member
196
+
197
+ Does not match the documentation, but works (with v2.0 in january 2026 at least)
198
+ """
199
+
200
+ lsbSport: list[str]
201
+
202
+
203
+ class MemberSetDosb(BaseModel):
204
+ """
205
+ Pydantic model used to set DOSB sports for a member
206
+
207
+ Does not match the documentation, but works (with v2.0 in january 2026 at least)
208
+ And for some reason, has another format than the set-lsb endpoint
209
+ """
210
+
211
+ dosb_sport: list[str]
212
+
213
+
188
214
  from .contact_details import ContactDetails # noqa: E402
215
+ from .lsb_dosb_sport import LsbDosbSport # noqa: E402
189
216
  from .member_custom_field import MemberCustomField # noqa: E402
190
217
  from .member_member_group import MemberMemberGroup # noqa: E402
@@ -32,6 +32,7 @@ class MemberCustomFieldBase(EasyVereinBase):
32
32
  customField: EasyVereinReference | CustomField | None = None
33
33
  value: str | None = None
34
34
  requestedValue: str | None = None # Purpose of this field is not documented
35
+ selectedOptions: list[CustomFieldSelectOption | EasyVereinReference] | None = None
35
36
 
36
37
 
37
38
  class MemberCustomField(MemberCustomFieldBase, EmptyStringsToNone):
@@ -42,7 +43,7 @@ class MemberCustomField(MemberCustomFieldBase, EmptyStringsToNone):
42
43
  pass
43
44
 
44
45
 
45
- class MemberCustomFieldCreate(MemberCustomFieldBase, required_mixin(["customField", "value"])): # type: ignore
46
+ class MemberCustomFieldCreate(MemberCustomFieldBase, required_mixin(["customField"])): # type: ignore
46
47
  """
47
48
  Pydantic model for creating a new member
48
49
  """
@@ -76,4 +77,5 @@ class MemberCustomFieldFilter(BaseModel):
76
77
 
77
78
 
78
79
  from .custom_field import CustomField # noqa: E402
80
+ from .custom_field_select_option import CustomFieldSelectOption # noqa: E402
79
81
  from .member import Member # noqa: E402
@@ -13,6 +13,7 @@ from ..core.types import (
13
13
  FilterIntList,
14
14
  HexColor,
15
15
  PositiveIntWithZero,
16
+ Sphere,
16
17
  )
17
18
  from .base import EasyVereinBase
18
19
  from .mixins.empty_strings_mixin import EmptyStringsToNone
@@ -48,7 +49,7 @@ class MemberGroupBase(EasyVereinBase):
48
49
  agePermission: PositiveIntWithZero | None = None
49
50
  nextGroup: EasyVereinReference | None = None
50
51
  taxRate: float | None = None
51
- billingAccount: EasyVereinReference | None = None
52
+ billingAccount: BillingAccount | EasyVereinReference | None = None
52
53
  costCentre: str | None = Field(default=None, max_length=8)
53
54
  isOnlyVisibleToAdmins: bool | None = None
54
55
  user_shares: Literal["n", "a", "d"] | None = None
@@ -130,6 +131,7 @@ class MemberGroupBase(EasyVereinBase):
130
131
  - a: allowed
131
132
  - d: forbidden
132
133
  """
134
+ sphere: Sphere | None = None
133
135
 
134
136
 
135
137
  class MemberGroup(MemberGroupBase, EmptyStringsToNone):
@@ -153,3 +155,6 @@ class MemberGroupFilter(BaseModel):
153
155
  kind: str | None = None
154
156
  deleted: bool | None = None
155
157
  ordering: str | None = None
158
+
159
+
160
+ from .billing_account import BillingAccount # noqa: E402
@@ -0,0 +1,27 @@
1
+ """
2
+ All methods related to billing accounts
3
+ """
4
+
5
+ import logging
6
+
7
+ from ..core.client import EasyvereinClient
8
+ from ..models.billing_account import (
9
+ BillingAccount,
10
+ BillingAccountCreate,
11
+ BillingAccountFilter,
12
+ BillingAccountUpdate,
13
+ )
14
+ from .mixins.crud import CRUDMixin
15
+ from .mixins.recycle_bin import RecycleBinMixin
16
+
17
+
18
+ class BillingAccountMixin(
19
+ CRUDMixin[BillingAccount, BillingAccountCreate, BillingAccountUpdate, BillingAccountFilter],
20
+ RecycleBinMixin[BillingAccount],
21
+ ):
22
+ def __init__(self, client: EasyvereinClient, logger: logging.Logger):
23
+ super().__init__()
24
+ self.endpoint_name = "billing-account"
25
+ self.return_type = BillingAccount
26
+ self.c = client
27
+ self.logger = logger
@@ -11,12 +11,13 @@ from ..models.booking import (
11
11
  BookingFilter,
12
12
  BookingUpdate,
13
13
  )
14
- from .mixins.crud import CRUDMixin
14
+ from .mixins.crud import BulkUpdateCreateMixin, CRUDMixin
15
15
  from .mixins.recycle_bin import RecycleBinMixin
16
16
 
17
17
 
18
18
  class BookingMixin(
19
19
  CRUDMixin[Booking, BookingCreate, BookingUpdate, BookingFilter],
20
+ BulkUpdateCreateMixin[Booking, BookingCreate, BookingUpdate],
20
21
  RecycleBinMixin[Booking],
21
22
  ):
22
23
  def __init__(self, client: EasyvereinClient, logger: logging.Logger):
@@ -7,12 +7,13 @@ import logging
7
7
  from ..core.client import EasyvereinClient
8
8
  from ..models import ContactDetails, ContactDetailsCreate, ContactDetailsUpdate
9
9
  from ..models.contact_details import ContactDetailsFilter
10
- from .mixins.crud import CRUDMixin
10
+ from .mixins.crud import BulkUpdateCreateMixin, CRUDMixin
11
11
  from .mixins.recycle_bin import RecycleBinMixin
12
12
 
13
13
 
14
14
  class ContactDetailsMixin(
15
15
  CRUDMixin[ContactDetails, ContactDetailsCreate, ContactDetailsUpdate, ContactDetailsFilter],
16
+ BulkUpdateCreateMixin[ContactDetails, ContactDetailsCreate, ContactDetailsUpdate],
16
17
  RecycleBinMixin[ContactDetails],
17
18
  ):
18
19
  def __init__(self, client: EasyvereinClient, logger: logging.Logger):
@@ -11,6 +11,7 @@ from ..models.custom_field import (
11
11
  CustomFieldFilter,
12
12
  CustomFieldUpdate,
13
13
  )
14
+ from .custom_field_select_option import CustomFieldSelectOptionMixin
14
15
  from .mixins.crud import CRUDMixin
15
16
  from .mixins.recycle_bin import RecycleBinMixin
16
17
 
@@ -25,3 +26,6 @@ class CustomFieldMixin(
25
26
  self.return_type = CustomField
26
27
  self.c = client
27
28
  self.logger = logger
29
+
30
+ def select_option(self, custom_field_id: int) -> CustomFieldSelectOptionMixin:
31
+ return CustomFieldSelectOptionMixin(self.c, self.logger, custom_field_id)
@@ -0,0 +1,31 @@
1
+ import logging
2
+
3
+ from ..core.client import EasyvereinClient
4
+ from ..models import (
5
+ CustomField,
6
+ CustomFieldSelectOption,
7
+ CustomFieldSelectOptionCreate,
8
+ CustomFieldSelectOptionFilter,
9
+ CustomFieldSelectOptionUpdate,
10
+ )
11
+ from .mixins.crud import CRUDMixin
12
+ from .mixins.helper import get_id
13
+
14
+
15
+ class CustomFieldSelectOptionMixin(
16
+ CRUDMixin[
17
+ CustomFieldSelectOption,
18
+ CustomFieldSelectOptionCreate,
19
+ CustomFieldSelectOptionUpdate,
20
+ CustomFieldSelectOptionFilter,
21
+ ]
22
+ ):
23
+ def __init__(self, client: EasyvereinClient, logger: logging.Logger, custom_field: CustomField | int):
24
+ self.return_type = CustomFieldSelectOption
25
+ self.c = client
26
+ self.logger = logger
27
+ self.custom_field_id = get_id(custom_field)
28
+
29
+ @property
30
+ def endpoint_name(self) -> str:
31
+ return f"custom-field/{self.custom_field_id}/select-options"
@@ -11,12 +11,13 @@ from ..core.client import EasyvereinClient
11
11
  from ..core.exceptions import EasyvereinAPIException
12
12
  from ..models.invoice import Invoice, InvoiceCreate, InvoiceFilter, InvoiceUpdate
13
13
  from ..models.invoice_item import InvoiceItemCreate
14
- from .mixins.crud import CRUDMixin
14
+ from .mixins.crud import BulkUpdateCreateMixin, CRUDMixin
15
15
  from .mixins.recycle_bin import RecycleBinMixin
16
16
 
17
17
 
18
18
  class InvoiceMixin(
19
19
  CRUDMixin[Invoice, InvoiceCreate, InvoiceUpdate, InvoiceFilter],
20
+ BulkUpdateCreateMixin[Invoice, InvoiceCreate, InvoiceUpdate],
20
21
  RecycleBinMixin[Invoice],
21
22
  ):
22
23
  def __init__(self, client: EasyvereinClient, logger: logging.Logger):
@@ -0,0 +1,49 @@
1
+ """
2
+ All methods related to invoices
3
+ """
4
+
5
+ import logging
6
+
7
+ from easyverein.modules.member_custom_field import MemberCustomFieldMixin
8
+ from easyverein.modules.member_member_group import MemberMemberGroupMixin
9
+
10
+ from ..core.client import EasyvereinClient
11
+ from ..models import Member, MemberCreate, MemberFilter, MemberSetLsb, MemberUpdate
12
+ from ..models.member import MemberSetDosb
13
+ from .mixins.crud import BulkUpdateCreateMixin, CRUDMixin
14
+ from .mixins.helper import get_id
15
+ from .mixins.recycle_bin import RecycleBinMixin
16
+
17
+
18
+ class MemberMixin(
19
+ CRUDMixin[Member, MemberCreate, MemberUpdate, MemberFilter],
20
+ BulkUpdateCreateMixin[Member, MemberCreate, MemberUpdate],
21
+ RecycleBinMixin[Member],
22
+ ):
23
+ def __init__(self, client: EasyvereinClient, logger: logging.Logger):
24
+ self.endpoint_name = "member"
25
+ self.c = client
26
+ self.logger = logger
27
+ self.return_type = Member
28
+
29
+ def custom_field(self, member_id: int) -> MemberCustomFieldMixin:
30
+ return MemberCustomFieldMixin(self.c, self.logger, member_id)
31
+
32
+ def member_group(self, member_id: int) -> MemberMemberGroupMixin:
33
+ return MemberMemberGroupMixin(self.c, self.logger, member_id)
34
+
35
+ def set_lsb(self, target: Member | int, data: MemberSetLsb) -> None:
36
+ obj_id = get_id(target)
37
+
38
+ self.logger.info(f"Setting LSB sports for member {obj_id}")
39
+
40
+ url = self.c.get_url(f"/{self.endpoint_name}/{obj_id}/set-lsb")
41
+ self.c.update(url, data, status_code=204)
42
+
43
+ def set_dosb(self, target: Member | int, data: MemberSetDosb) -> None:
44
+ obj_id = get_id(target)
45
+
46
+ self.logger.info(f"Setting DOSB sports for member {obj_id}")
47
+
48
+ url = self.c.get_url(f"/{self.endpoint_name}/{obj_id}/set-dosb")
49
+ self.c.update(url, data, status_code=204)
@@ -7,6 +7,7 @@ import logging
7
7
  from ..core.client import EasyvereinClient
8
8
  from ..models import (
9
9
  CustomField,
10
+ CustomFieldSelectOption,
10
11
  Member,
11
12
  MemberCustomField,
12
13
  MemberCustomFieldCreate,
@@ -30,17 +31,41 @@ class MemberCustomFieldMixin(
30
31
  def endpoint_name(self) -> str:
31
32
  return f"member/{self.member_id}/custom-fields"
32
33
 
33
- def ensure_set(self, custom_field_id: int, value: str) -> MemberCustomField:
34
+ def ensure_set(self, custom_field_id: int, value: str | list[str]) -> MemberCustomField:
34
35
  """
35
36
  Convenience method to set the custom field value on a member, no matter if it was
36
37
  set before already (PATCH) or not.
37
38
 
39
+ For select (s) and multiselect (a) custom fields, you can provide a list of values.
40
+ Instead of using the value, the method will automatically fetch and select the
41
+ options from selectOptions that have been introduced in January 2026.
42
+
38
43
  Regarding rate limiting, be aware that this method requires at least two API calls.
39
44
 
40
45
  Args:
41
46
  custom_field_id (int): Custom field ID that should be set or changed
42
- value (str): New value the specified custom field should be set to
47
+ value (str | list[str]): New value the specified custom field should be set to
43
48
  """
49
+ custom_field_query = "{id,settings_type,selectOptions{id,value}}"
50
+ custom_field = self.c.api_instance.custom_field.get_by_id(custom_field_id, query=custom_field_query)
51
+ use_selected_options = custom_field.settings_type in ("s", "a")
52
+
53
+ if use_selected_options:
54
+ values_list = [value] if isinstance(value, str) else list(value)
55
+ value_to_id: dict[str, int] = {}
56
+ for opt in custom_field.selectOptions or []:
57
+ if isinstance(opt, CustomFieldSelectOption) and opt.id is not None and opt.value:
58
+ value_to_id[opt.value] = opt.id
59
+ option_ids = [value_to_id[v] for v in values_list if v in value_to_id]
60
+ missing = [v for v in values_list if v not in value_to_id]
61
+ if missing:
62
+ raise ValueError(f"No select option(s) with value(s) {missing!r} for custom field {custom_field_id}")
63
+ payload_value = None
64
+ payload_selected_options: list[int] | None = option_ids
65
+ else:
66
+ assert isinstance(value, str), "Value must be a string for non-select custom fields"
67
+ payload_value = value
68
+ payload_selected_options = None
44
69
 
45
70
  # Get all custom fields this member has already set, use max limit available
46
71
  query = "{id,value,customField{id}}"
@@ -58,10 +83,12 @@ class MemberCustomFieldMixin(
58
83
 
59
84
  if existing_custom_field:
60
85
  # It's already there, we need to patch the existing field
61
- patch = MemberCustomFieldUpdate(value=value)
86
+ patch = MemberCustomFieldUpdate(value=payload_value, selectedOptions=payload_selected_options)
62
87
  assert existing_custom_field.id
63
88
  return self.update(existing_custom_field.id, patch)
64
89
  else:
65
90
  # It's not there, we need to create it
66
- create = MemberCustomFieldCreate(value=value, customField=custom_field_id)
91
+ create = MemberCustomFieldCreate(
92
+ customField=custom_field_id, value=payload_value, selectedOptions=payload_selected_options
93
+ )
67
94
  return self.create(create)
@@ -194,3 +194,71 @@ class CRUDMixin(Generic[ModelType, CreateModelType, UpdateModelType, FilterType]
194
194
  self.logger.info(f"Deleting object of type {self.endpoint_name} with id {obj_id} from wastebasket")
195
195
  purge: Callable = getattr(self, "purge")
196
196
  purge(obj_id)
197
+
198
+
199
+ class BulkUpdateCreateMixin(Generic[ModelType, CreateModelType, UpdateModelType]):
200
+ """
201
+ Mixin providing bulk create and update functionality for endpoints that support it.
202
+ Currently only supported for the following endpoints:
203
+ - booking
204
+ - contact-details
205
+ - member
206
+ - invoice
207
+ """
208
+
209
+ def bulk_create(self: EVClientProtocol[ModelType], data: list[CreateModelType]) -> list[bool]:
210
+ """
211
+ Creates multiple objects in a single API request and returns the created objects.
212
+
213
+ **Example**:
214
+
215
+ ```py
216
+ from easyverein import EasyvereinAPI
217
+
218
+ ev_client = EasyvereinAPI("your_api_key")
219
+
220
+ contact_details = ev_client.contact_details.bulk_create([
221
+ ContactDetailsCreate(fistName="example1", lastName="Example1", isCompany=False),
222
+ ContactDetailsCreate(fistName="example2", lastName="Example2", isCompany=False),
223
+ ])
224
+ ```
225
+
226
+ Args:
227
+ data: List of Pydantic models containing the data for the objects to be created.
228
+ """
229
+ self.logger.info(f"Creating object of type {self.endpoint_name}")
230
+
231
+ url = self.c.get_url(f"/{self.endpoint_name}/bulk-create")
232
+ response = self.c.bulk_create(url, data)
233
+ return [r["data"]["success"] for r in response.result] # type: ignore
234
+
235
+ def bulk_update(
236
+ self: EVClientProtocol[ModelType], data: list[UpdateModelType], exclude_none: bool = True
237
+ ) -> list[bool]:
238
+ """
239
+ Updates multiple objects in a single API request and returns the updated objects.
240
+
241
+ Note that the update models must include the `id` of the objects to be updated.
242
+
243
+ **Example**:
244
+
245
+ ```py
246
+ from easyverein import EasyvereinAPI
247
+
248
+ ev_client = EasyvereinAPI("your_api_key")
249
+
250
+ updated_members = ev_client.member.bulk_update([
251
+ MemberUpdate(id=1, membershipNumber="M1"),
252
+ MemberUpdate(id=2, membershipNumber="M2"),
253
+ ])
254
+ ```
255
+
256
+ Args:
257
+ data: List of Pydantic models containing the data to update.
258
+ exclude_none: If True, fields with None values will be excluded from the update.
259
+ """
260
+ self.logger.info(f"Bulk updating objects of type {self.endpoint_name}")
261
+
262
+ url = self.c.get_url(f"/{self.endpoint_name}/bulk-update")
263
+ response = self.c.bulk_update(url, data, exclude_none=exclude_none)
264
+ return [r["data"]["success"] for r in response.result] # type: ignore
@@ -1,26 +1,28 @@
1
- [tool.poetry]
1
+ [project]
2
2
  name = "python-easyverein"
3
- version = "2.1.2"
3
+ version = "2.3.0"
4
4
  description = "Python library to interact with the EasyVerein API"
5
- authors = ["Daniel Herrmann <daniel.herrmann1@gmail.com>"]
5
+ authors = [{ name = "Daniel Herrmann", email = "daniel.herrmann1@gmail.com" }]
6
6
  readme = "README.md"
7
- packages = [{ include = "easyverein" }]
7
+ requires-python = ">=3.11,<4"
8
+ license = { file = "LICENSE" }
9
+ dependencies = ["pydantic>=2,<3", "requests>=2,<3", "email-validator>=2,<3"]
8
10
 
9
- [tool.poetry.dependencies]
10
- python = "^3.11"
11
- pydantic = "^2"
12
- requests = "^2"
13
- email-validator = "^2"
11
+ [tool.poetry]
12
+ packages = [{ include = "easyverein" }]
14
13
 
15
14
 
16
15
  [tool.poetry.group.dev.dependencies]
17
- sphinx = "^9"
16
+ sphinx = [
17
+ { version = "^9,<9.1", markers = "python_version < '3.12'" },
18
+ { version = "^9", markers = "python_version >= '3.12'" },
19
+ ]
18
20
  sphinx-rtd-theme = "^3"
19
21
  ruff = "*"
20
22
  pytest = "^9"
21
23
  pytest-dotenv = "^0"
22
24
  mkdocs-material = "^9"
23
- mkdocstrings = {extras = ["python"], version = "^1"}
25
+ mkdocstrings = { extras = ["python"], version = "^1" }
24
26
  pymdown-extensions = "^10.7"
25
27
  pre-commit = "^4"
26
28
  pytest-cov = "^7"
@@ -60,7 +62,7 @@ ignore = [
60
62
  "UP007", # use-pep604-annotation
61
63
  "E741", # Ambiguous variable name
62
64
  # "UP035", # deprecated-assertion
63
- "PLE1205" # PLE1205 due to custom logger implementation
65
+ "PLE1205", # PLE1205 due to custom logger implementation
64
66
  ]
65
67
 
66
68
  [tool.ruff.lint.mccabe]
@@ -1,27 +0,0 @@
1
- """
2
- All methods related to invoices
3
- """
4
-
5
- import logging
6
-
7
- from easyverein.modules.member_custom_field import MemberCustomFieldMixin
8
- from easyverein.modules.member_member_group import MemberMemberGroupMixin
9
-
10
- from ..core.client import EasyvereinClient
11
- from ..models import Member, MemberCreate, MemberFilter, MemberUpdate
12
- from .mixins.crud import CRUDMixin
13
- from .mixins.recycle_bin import RecycleBinMixin
14
-
15
-
16
- class MemberMixin(CRUDMixin[Member, MemberCreate, MemberUpdate, MemberFilter], RecycleBinMixin[Member]):
17
- def __init__(self, client: EasyvereinClient, logger: logging.Logger):
18
- self.endpoint_name = "member"
19
- self.c = client
20
- self.logger = logger
21
- self.return_type = Member
22
-
23
- def custom_field(self, member_id: int) -> MemberCustomFieldMixin:
24
- return MemberCustomFieldMixin(self.c, self.logger, member_id)
25
-
26
- def member_group(self, member_id: int) -> MemberMemberGroupMixin:
27
- return MemberMemberGroupMixin(self.c, self.logger, member_id)