From 0cc8277b3295a43b3d5c785c91a5a7daf495fa4e Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Wed, 4 Sep 2024 15:00:11 +0200 Subject: [PATCH] Initial version of the OpenKlant2 client --- src/openklant2/README.md | 27 + src/openklant2/__init__.py | 3 + src/openklant2/_resources/__init__.py | 0 src/openklant2/_resources/base.py | 142 +++++ src/openklant2/_resources/partij.py | 62 +++ src/openklant2/client.py | 16 + src/openklant2/docker-compose.yaml | 36 ++ src/openklant2/exceptions.py | 75 +++ src/openklant2/factories/__init__.py | 11 + src/openklant2/factories/helpers.py | 15 + src/openklant2/factories/partij.py | 94 ++++ src/openklant2/regenerate_vcr_fixtures.sh | 19 + src/openklant2/tests/__init__.py | 0 .../test_create_contactpersoon.yaml | 85 +++ .../test_partij/test_create_organisatie.yaml | 44 ++ .../test_partij/test_create_persoon.yaml | 43 ++ ...est_create_with_bad_request_exception.yaml | 45 ++ .../test_partij/test_list_partijen.yaml | 123 +++++ ...e_partij[betrokkenen.hadKlantcontact].yaml | 80 +++ .../test_retrieve_partij[betrokkenen].yaml | 80 +++ ...st_retrieve_partij[categorieRelaties].yaml | 80 +++ ...est_retrieve_partij[digitaleAdressen].yaml | 80 +++ src/openklant2/tests/conftest.py | 47 ++ src/openklant2/tests/helpers.py | 177 ++++++ src/openklant2/tests/test_partij.py | 223 ++++++++ src/openklant2/types/__init__.py | 21 + src/openklant2/types/common.py | 15 + src/openklant2/types/error.py | 28 + src/openklant2/types/iso_639_2.py | 510 ++++++++++++++++++ src/openklant2/types/pagination.py | 12 + src/openklant2/types/resources/__init__.py | 35 ++ src/openklant2/types/resources/partij.py | 103 ++++ 32 files changed, 2331 insertions(+) create mode 100644 src/openklant2/README.md create mode 100644 src/openklant2/__init__.py create mode 100644 src/openklant2/_resources/__init__.py create mode 100644 src/openklant2/_resources/base.py create mode 100644 src/openklant2/_resources/partij.py create mode 100644 src/openklant2/client.py create mode 100644 src/openklant2/docker-compose.yaml create mode 100644 src/openklant2/exceptions.py create mode 100644 src/openklant2/factories/__init__.py create mode 100644 src/openklant2/factories/helpers.py create mode 100644 src/openklant2/factories/partij.py create mode 100755 src/openklant2/regenerate_vcr_fixtures.sh create mode 100644 src/openklant2/tests/__init__.py create mode 100644 src/openklant2/tests/cassettes/test_partij/test_create_contactpersoon.yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_create_organisatie.yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_create_persoon.yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_create_with_bad_request_exception.yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_list_partijen.yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen.hadKlantcontact].yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen].yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[categorieRelaties].yaml create mode 100644 src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[digitaleAdressen].yaml create mode 100644 src/openklant2/tests/conftest.py create mode 100644 src/openklant2/tests/helpers.py create mode 100644 src/openklant2/tests/test_partij.py create mode 100644 src/openklant2/types/__init__.py create mode 100644 src/openklant2/types/common.py create mode 100644 src/openklant2/types/error.py create mode 100644 src/openklant2/types/iso_639_2.py create mode 100644 src/openklant2/types/pagination.py create mode 100644 src/openklant2/types/resources/__init__.py create mode 100644 src/openklant2/types/resources/partij.py diff --git a/src/openklant2/README.md b/src/openklant2/README.md new file mode 100644 index 0000000000..a7aa0df934 --- /dev/null +++ b/src/openklant2/README.md @@ -0,0 +1,27 @@ +# Open Klant 2 API Client + +This Python package provides a client for interacting with Open Klant 2 services. It simplifies the process of making requests to the API and handling responses. + +## Usage + +```python +from openklant2 import OpenKlant2Client + +client = OpenKlant2Client(api_root="https://openklant.maykin.nl/klantinteracties", token="your_api_token") + +# Get user data +partijen = client.Partij.list() +print(partijen) +``` + +## Testing + +### Re-recording VCR cassettes + +The tests rely on VCR cassettes which are included in the repo. To dynamically create +an OpenKlant service and run the tests against it, run the following command: + +```bash +$ cd src/openklant2 +$ ./regenerate_vcr_fixtures.sh +``` diff --git a/src/openklant2/__init__.py b/src/openklant2/__init__.py new file mode 100644 index 0000000000..db0d6afe5e --- /dev/null +++ b/src/openklant2/__init__.py @@ -0,0 +1,3 @@ +from openklant2.client import OpenKlant2Client + +__all__ = ["OpenKlant2Client"] diff --git a/src/openklant2/_resources/__init__.py b/src/openklant2/_resources/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/openklant2/_resources/base.py b/src/openklant2/_resources/base.py new file mode 100644 index 0000000000..dd37950147 --- /dev/null +++ b/src/openklant2/_resources/base.py @@ -0,0 +1,142 @@ +import json +from typing import Any, Dict, List, Mapping, MutableMapping, TypeGuard, TypeVar, Union + +import pydantic +import requests +from ape_pie import APIClient + +from openklant2.exceptions import ( + BadRequest, + ClientError, + GenericErrorResponse, + NonJSONResponse, + NotFound, +) +from openklant2.types.error import ( + ErrorResponseBodyValidator, + ValidationErrorResponseBodyValidator, +) + +T = TypeVar("T") + +ResourceResponse = MutableMapping[str, Any] + + +JSONPrimitive = Union[str, int, None, float] +JSONValue = Union[JSONPrimitive, "JSONObject", List["JSONValue"]] +JSONObject = Dict[str, JSONValue] + + +class ResourceMixin: + http_client: APIClient + + def __init__(self, http_client: APIClient): + self.http_client = http_client + + def process_response(self, response: requests.Response) -> TypeGuard[JSONValue]: + response_data = None + try: + content_type = response.headers.get("Content-Type", "") + if not content_type.lower().startswith("application/json"): + raise NonJSONResponse(response) + + response_data = response.json() + except (requests.exceptions.JSONDecodeError, json.JSONDecodeError): + raise NonJSONResponse(response) + + match response.status_code: + case code if code >= 200 and code < 300 and response_data: + return response_data + case code if code >= 400 and code <= 500 and response_data: + match code: + case 400: + validator = ValidationErrorResponseBodyValidator + exc_class = BadRequest + case 404: + validator = ErrorResponseBodyValidator + exc_class = NotFound + case _: + validator = ErrorResponseBodyValidator + exc_class = ClientError + + try: + validator.validate_python(response_data) + raise exc_class(response_data) + except pydantic.ValidationError: + raise GenericErrorResponse(response=response) + case _: + raise GenericErrorResponse(response) + + raise + + def _get( + self, + path: str, + headers: Mapping | None = None, + params: Mapping | None = None, + ) -> requests.Response: + + return self.http_client.request("get", path, headers=headers, params=params) + + def _post( + self, + path: str, + headers: Mapping | None = None, + params: Mapping | None = None, + data: Any = None, + ) -> requests.Response: + return self.http_client.request( + "post", path, headers=headers, json=data, params=params + ) + + def _put( + self, + path: str, + headers: Mapping | None = None, + params: Mapping | None = None, + data: Any = None, + ) -> requests.Response: + return self.http_client.request( + "put", path, headers=headers, json=data, params=params + ) + + def _delete( + self, + path: str, + headers: Mapping | None = None, + params: Mapping | None = None, + ) -> requests.Response: + return self.http_client.request( + "delete", + path, + headers=headers, + params=params, + ) + + def _patch( + self, + path: str, + headers: Mapping | None = None, + params: Mapping | None = None, + data: Any = None, + ) -> requests.Response: + return self.http_client.request( + "patch", + path, + headers=headers, + params=params, + json=data, + ) + + def _options( + self, + path: str, + headers: Mapping | None = None, + params: Mapping | None = None, + ) -> requests.Response: + return self.http_client.request( + "delete", + path, + headers=headers, + params=params, + ) diff --git a/src/openklant2/_resources/partij.py b/src/openklant2/_resources/partij.py new file mode 100644 index 0000000000..046f1d96c2 --- /dev/null +++ b/src/openklant2/_resources/partij.py @@ -0,0 +1,62 @@ +import uuid +from typing import Optional, cast + +from ape_pie import APIClient + +from openklant2._resources.base import ResourceMixin +from openklant2.types.pagination import PaginatedResponseBody +from openklant2.types.resources.partij import ( + CreatePartijContactpersoonData, + CreatePartijOrganisatieData, + CreatePartijPersoonData, + Partij, + PartijListParams, + PartijRetrieveParams, +) + + +class PartijResource(ResourceMixin): + http_client: APIClient + base_path: str = "/partijen" + + def list( + self, *, params: Optional[PartijListParams] = None + ) -> PaginatedResponseBody[Partij]: + response = self._get(self.base_path, params=params) + return cast(PaginatedResponseBody[Partij], self.process_response(response)) + + def retrieve( + self, /, uuid: str | uuid.UUID, *, params: Optional[PartijRetrieveParams] = None + ) -> Partij: + response = self._get(f"{self.base_path}/{str(uuid)}", params=params) + return cast(Partij, self.process_response(response)) + + # Partij is polymorphic on "soortPartij", with varying fields for Persoon, Organisatie and ContactPersoon + def create_organisatie(self, *, data: CreatePartijOrganisatieData): + return self._create(data=data) + + def create_persoon( + self, + *, + data: CreatePartijPersoonData, + ) -> Partij: + return cast(Partij, self._create(data=data)) + + def create_contactpersoon( + self, + *, + data: CreatePartijContactpersoonData, + ) -> Partij: + return cast(Partij, self._create(data=data)) + + def _create( + self, + *, + data: ( + CreatePartijPersoonData + | CreatePartijOrganisatieData + | CreatePartijContactpersoonData + ), + ) -> Partij: + response = self._post(self.base_path, data=data) + return cast(Partij, self.process_response(response)) diff --git a/src/openklant2/client.py b/src/openklant2/client.py new file mode 100644 index 0000000000..e1263ad76a --- /dev/null +++ b/src/openklant2/client.py @@ -0,0 +1,16 @@ +from ape_pie import APIClient + +from openklant2._resources.partij import PartijResource + + +class OpenKlant2Client: + http_client: APIClient + partij: PartijResource + + def __init__(self, token: str, api_root: str): + self.http_client = APIClient( + request_kwargs={"headers": {"Authorization": f"Token {token}"}}, + base_url=api_root, + ) + + self.partij = PartijResource(self.http_client) diff --git a/src/openklant2/docker-compose.yaml b/src/openklant2/docker-compose.yaml new file mode 100644 index 0000000000..cfa375a3a7 --- /dev/null +++ b/src/openklant2/docker-compose.yaml @@ -0,0 +1,36 @@ +# This docker-compose is used internally in the testsuite to dynamically spin up +# an OpenKlant2 backend, seed it with known data, and record VCR fixtures. Be +# sure to verify any changes here are consistent with +version: '3' + +services: + db: + image: postgres + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + + web: + image: maykinmedia/open-klant:${OPEN_KLANT_IMAGE_TAG:-latest} + environment: &web-env + - DJANGO_SETTINGS_MODULE=openklant.conf.docker + - IS_HTTPS=no + - DB_NAME=postgres + - DB_USER=postgres + - DB_HOST=db + - ALLOWED_HOSTS=* + - CACHE_DEFAULT=redis:6379/0 + - CACHE_AXES=redis:6379/0 + - SUBPATH=${SUBPATH:-/} + - SECRET_KEY=${SECRET_KEY:-django-insecure-f8s@b*ds4t84-q_2#c0j0506@!l2q6r5_pq5e!vm^_9c*#^66b} + - CELERY_BROKER_URL=redis://redis:6379/0 + - CELERY_RESULT_BACKEND=redis://redis:6379/0 + - DISABLE_2FA=true + ports: + # Use a somewhat arbitrary port to avoid clashes + - 8338:8000 + depends_on: + - db + - redis + + redis: + image: redis diff --git a/src/openklant2/exceptions.py b/src/openklant2/exceptions.py new file mode 100644 index 0000000000..581a41402a --- /dev/null +++ b/src/openklant2/exceptions.py @@ -0,0 +1,75 @@ +import pprint + +from requests import Response + +from openklant2.types.error import ( + ErrorResponseBody, + InvalidParam, + ValidationErrorResponseBody, +) + + +class OpenKlant2Exception(Exception): + pass + + +class GenericErrorResponse(OpenKlant2Exception): + response: Response + + def __init__(self, response: Response): + self.response = response + + +class NonJSONResponse(GenericErrorResponse): + pass + + +class ClientError(OpenKlant2Exception): + + type: str + code: str + title: str + status: int + detail: str + instance: str + + def __init__(self, response: ErrorResponseBody): + self.type = response["type"] + self.code = response["code"] + self.title = response["title"] + self.status = response["status"] + self.detail = response["detail"] + self.instance = response["instance"] + + OpenKlant2Exception.__init__( + self, f'status={self.status} code={self.code} title="{self.title}"' + ) + + +class NotFound(ClientError): + pass + + +class BadRequest(ClientError): + invalidParams: list[InvalidParam] + + def __init__(self, response: ValidationErrorResponseBody): + self.type = response["type"] + self.code = response["code"] + self.title = response["title"] + self.status = response["status"] + self.detail = response["detail"] + self.instance = response["instance"] + self.invalidParams = response["invalidParams"] + + invalid_params_formatted = "" + if self.invalidParams: + invalid_params_formatted = "\nInvalid parameters:\n" + invalid_params_formatted += "\n".join( + pprint.pformat(param) for param in self.invalidParams + ) + + OpenKlant2Exception.__init__( + self, + f'status={self.status} code={self.code} title="{self.title}":{invalid_params_formatted}', + ) diff --git a/src/openklant2/factories/__init__.py b/src/openklant2/factories/__init__.py new file mode 100644 index 0000000000..52b6b81699 --- /dev/null +++ b/src/openklant2/factories/__init__.py @@ -0,0 +1,11 @@ +from .partij import ( + CreatePartijContactPersoonDataFactory, + CreatePartijOrganisatieDataFactory, + CreatePartijPersoonDataFactory, +) + +__all__ = [ + "CreatePartijPersoonDataFactory", + "CreatePartijOrganisatieDataFactory", + "CreatePartijContactPersoonDataFactory", +] diff --git a/src/openklant2/factories/helpers.py b/src/openklant2/factories/helpers.py new file mode 100644 index 0000000000..ef7b617c97 --- /dev/null +++ b/src/openklant2/factories/helpers.py @@ -0,0 +1,15 @@ +import factory +from pydantic import TypeAdapter + + +def validate_against(validator: TypeAdapter): + def decorator(cls: type[factory.Factory]): + @factory.post_generation + def validate(obj, *args, **kwargs): + validator.validate_python(obj) + + setattr(cls, "post_generation_validator", validate) + + return cls + + return decorator diff --git a/src/openklant2/factories/partij.py b/src/openklant2/factories/partij.py new file mode 100644 index 0000000000..8d21e573a1 --- /dev/null +++ b/src/openklant2/factories/partij.py @@ -0,0 +1,94 @@ +import factory +import factory.faker +from factory import fuzzy + +from openklant2.factories.helpers import validate_against +from openklant2.types.iso_639_2 import LanguageCode +from openklant2.types.resources.partij import ( + CreatePartijContactpersoonDataValidator, + CreatePartijOrganisatieDataValidator, + CreatePartijPersoonDataValidator, +) + + +class ForeignKeyRef(factory.Factory): + class Meta: + model = dict + + uuid = factory.Faker("uuid4") + + +class ContactnaamFactory(factory.Factory): + class Meta: + model = dict + + voorletters = factory.Faker("prefix") + voornaam = factory.Faker("first_name") + voorvoegselAchternaam = factory.Faker("prefix") + achternaam = factory.Faker("last_name") + + +class PartijIdentificatieOrganisatieFactory(factory.Factory): + class Meta: + model = dict + + naam = factory.Faker("company") + + +class PartijIdentificatiePersoonFactory(factory.Factory): + class Meta: + model = dict + + contactnaam = factory.SubFactory(ContactnaamFactory) + + +class PartijIdentificatieContactpersoonFactory(factory.Factory): + class Meta: + model = dict + + class Params: + werkte_voor_partij = None + + contactnaam = factory.SubFactory(ContactnaamFactory) + werkteVoorPartij = factory.SubFactory(ForeignKeyRef) + + +class CreatePartijBaseFactory(factory.Factory): + class Meta: + model = dict + abstract = True + + digitaleAdressen = None + voorkeursDigitaalAdres = None + rekeningnummers = None + voorkeursRekeningnummer = None + indicatieGeheimhouding = fuzzy.FuzzyChoice([True, False]) + indicatieActief = fuzzy.FuzzyChoice([True, False]) + voorkeurstaal = fuzzy.FuzzyChoice(LanguageCode.__args__) + + +@validate_against(CreatePartijPersoonDataValidator) +class CreatePartijPersoonDataFactory(CreatePartijBaseFactory): + class Meta: + model = dict + + soortPartij = "persoon" + partijIdentificatie = factory.SubFactory(PartijIdentificatiePersoonFactory) + + +@validate_against(CreatePartijOrganisatieDataValidator) +class CreatePartijOrganisatieDataFactory(CreatePartijBaseFactory): + class Meta: + model = dict + + soortPartij = "organisatie" + partijIdentificatie = factory.SubFactory(PartijIdentificatieOrganisatieFactory) + + +@validate_against(CreatePartijContactpersoonDataValidator) +class CreatePartijContactPersoonDataFactory(CreatePartijBaseFactory): + class Meta: + model = dict + + soortPartij = "contactpersoon" + partijIdentificatie = factory.SubFactory(PartijIdentificatieContactpersoonFactory) diff --git a/src/openklant2/regenerate_vcr_fixtures.sh b/src/openklant2/regenerate_vcr_fixtures.sh new file mode 100755 index 0000000000..83ff9bef93 --- /dev/null +++ b/src/openklant2/regenerate_vcr_fixtures.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +delete_path=$(realpath ./tests/cassettes) + +# Display the full path and ask for confirmation +echo "You are about to recursively delete all VCR cassettes from the following directory:" +echo "$delete_path" +read -p "Are you sure you want to proceed? (y/N): " confirm + +if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then + echo "Deleting directory..." + rm -rf "$delete_path" + echo "Directory deleted." +else + echo "Operation cancelled." + exit 0 +fi + +pytest --with-openklant-service --record-mode=all diff --git a/src/openklant2/tests/__init__.py b/src/openklant2/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/openklant2/tests/cassettes/test_partij/test_create_contactpersoon.yaml b/src/openklant2/tests/cassettes/test_partij/test_create_contactpersoon.yaml new file mode 100644 index 0000000000..77b0e160f0 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_create_contactpersoon.yaml @@ -0,0 +1,85 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "indicatieGeheimhouding": false, "indicatieActief": + true, "voorkeurstaal": "tiv", "soortPartij": "organisatie", "partijIdentificatie": + {"naam": "Test Organisatie"}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '281' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"99126214-2fbe-48ea-ae76-b2164634696d","url":"http://localhost:8338/klantinteracties/api/v1/partijen/99126214-2fbe-48ea-ae76-b2164634696d","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"organisatie","indicatieGeheimhouding":false,"voorkeurstaal":"tiv","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"naam":"Test + Organisatie"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '749' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/99126214-2fbe-48ea-ae76-b2164634696d + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "voorkeurstaal": "nld", "indicatieActief": + true, "indicatieGeheimhouding": false, "soortPartij": "contactpersoon", "partijIdentificatie": + {"contactnaam": null, "werkteVoorPartij": {"uuid": "99126214-2fbe-48ea-ae76-b2164634696d"}}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '347' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"96589692-2a76-4f92-953b-f5d2cd0097b0","url":"http://localhost:8338/klantinteracties/api/v1/partijen/96589692-2a76-4f92-953b-f5d2cd0097b0","nummer":"0000000002","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"contactpersoon","indicatieGeheimhouding":false,"voorkeurstaal":"nld","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"uuid":"26c930e2-66c8-4b49-862c-3a15dc1fc083","werkteVoorPartij":{"uuid":"99126214-2fbe-48ea-ae76-b2164634696d","url":"http://localhost:8338/klantinteracties/api/v1/partijen/99126214-2fbe-48ea-ae76-b2164634696d"},"contactnaam":{"voorletters":"","voornaam":"","voorvoegselAchternaam":"","achternaam":""},"volledigeNaam":""}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '1048' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/96589692-2a76-4f92-953b-f5d2cd0097b0 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_create_organisatie.yaml b/src/openklant2/tests/cassettes/test_partij/test_create_organisatie.yaml new file mode 100644 index 0000000000..c422cbbd33 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_create_organisatie.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "voorkeurstaal": "nld", "indicatieActief": + true, "indicatieGeheimhouding": false, "soortPartij": "organisatie", "partijIdentificatie": + {"naam": "AcmeCorp Ltd"}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '277' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"4d18ca99-c373-4371-a3bf-2016e9c0e071","url":"http://localhost:8338/klantinteracties/api/v1/partijen/4d18ca99-c373-4371-a3bf-2016e9c0e071","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"organisatie","indicatieGeheimhouding":false,"voorkeurstaal":"nld","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"naam":"AcmeCorp + Ltd"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '745' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/4d18ca99-c373-4371-a3bf-2016e9c0e071 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_create_persoon.yaml b/src/openklant2/tests/cassettes/test_partij/test_create_persoon.yaml new file mode 100644 index 0000000000..e1029f9575 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_create_persoon.yaml @@ -0,0 +1,43 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "voorkeurstaal": "nld", "indicatieActief": + true, "indicatieGeheimhouding": false, "soortPartij": "persoon", "partijIdentificatie": + {"contactnaam": null}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '270' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"ee1b792d-8b18-49f3-9b45-5b002265f5f6","url":"http://localhost:8338/klantinteracties/api/v1/partijen/ee1b792d-8b18-49f3-9b45-5b002265f5f6","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"nld","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"","voornaam":"","voorvoegselAchternaam":"","achternaam":""},"volledigeNaam":""}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '828' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/ee1b792d-8b18-49f3-9b45-5b002265f5f6 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_create_with_bad_request_exception.yaml b/src/openklant2/tests/cassettes/test_partij/test_create_with_bad_request_exception.yaml new file mode 100644 index 0000000000..11e08e69d0 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_create_with_bad_request_exception.yaml @@ -0,0 +1,45 @@ +interactions: +- request: + body: '{}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '2' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"type":"http://localhost:8338/ref/fouten/ValidationError/","code":"invalid","title":"Invalid + input.","status":400,"detail":"","instance":"urn:uuid:fc345ba3-1aeb-424e-8464-1855846166f3","invalidParams":[{"name":"digitaleAdressen","code":"required","reason":"Dit + veld is vereist."},{"name":"voorkeursDigitaalAdres","code":"required","reason":"Dit + veld is vereist."},{"name":"rekeningnummers","code":"required","reason":"Dit + veld is vereist."},{"name":"voorkeursRekeningnummer","code":"required","reason":"Dit + veld is vereist."},{"name":"soortPartij","code":"required","reason":"Dit veld + is vereist."},{"name":"indicatieActief","code":"required","reason":"Dit veld + is vereist."}]}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '678' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 400 + message: Bad Request +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_list_partijen.yaml b/src/openklant2/tests/cassettes/test_partij/test_list_partijen.yaml new file mode 100644 index 0000000000..7451112853 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_list_partijen.yaml @@ -0,0 +1,123 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "indicatieGeheimhouding": false, "indicatieActief": + true, "voorkeurstaal": "tiv", "soortPartij": "organisatie", "partijIdentificatie": + {"naam": "Test Organisatie"}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '281' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"153b3d89-8dc9-45a9-a6c2-2ac41c492ab0","url":"http://localhost:8338/klantinteracties/api/v1/partijen/153b3d89-8dc9-45a9-a6c2-2ac41c492ab0","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"organisatie","indicatieGeheimhouding":false,"voorkeurstaal":"tiv","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"naam":"Test + Organisatie"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '749' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/153b3d89-8dc9-45a9-a6c2-2ac41c492ab0 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "indicatieGeheimhouding": false, "indicatieActief": + true, "voorkeurstaal": "crp", "soortPartij": "persoon", "partijIdentificatie": + {"contactnaam": {"voorletters": "Dr.", "voornaam": "Test Persoon", "voorvoegselAchternaam": + "Mrs.", "achternaam": "Gamble"}}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '373' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"ddadd62c-4dc9-4125-b7ad-a04d8e6120bb","url":"http://localhost:8338/klantinteracties/api/v1/partijen/ddadd62c-4dc9-4125-b7ad-a04d8e6120bb","nummer":"0000000002","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/ddadd62c-4dc9-4125-b7ad-a04d8e6120bb + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +- request: + body: null + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + method: GET + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"count":2,"next":null,"previous":null,"results":[{"uuid":"ddadd62c-4dc9-4125-b7ad-a04d8e6120bb","url":"http://localhost:8338/klantinteracties/api/v1/partijen/ddadd62c-4dc9-4125-b7ad-a04d8e6120bb","nummer":"0000000002","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"},"_expand":{}},{"uuid":"153b3d89-8dc9-45a9-a6c2-2ac41c492ab0","url":"http://localhost:8338/klantinteracties/api/v1/partijen/153b3d89-8dc9-45a9-a6c2-2ac41c492ab0","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"organisatie","indicatieGeheimhouding":false,"voorkeurstaal":"tiv","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"naam":"Test + Organisatie"},"_expand":{}}]}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '1705' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 200 + message: OK +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen.hadKlantcontact].yaml b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen.hadKlantcontact].yaml new file mode 100644 index 0000000000..c3c27ad622 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen.hadKlantcontact].yaml @@ -0,0 +1,80 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "indicatieGeheimhouding": false, "indicatieActief": + true, "voorkeurstaal": "crp", "soortPartij": "persoon", "partijIdentificatie": + {"contactnaam": {"voorletters": "Dr.", "voornaam": "Test Persoon", "voorvoegselAchternaam": + "Mrs.", "achternaam": "Gamble"}}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '373' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"13fd248b-a248-461b-910c-d9381f3fc1b4","url":"http://localhost:8338/klantinteracties/api/v1/partijen/13fd248b-a248-461b-910c-d9381f3fc1b4","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/13fd248b-a248-461b-910c-d9381f3fc1b4 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +- request: + body: null + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + method: GET + uri: http://localhost:8338/klantinteracties/api/v1/partijen/13fd248b-a248-461b-910c-d9381f3fc1b4?expand=betrokkenen.hadKlantcontact + response: + body: + string: '{"uuid":"13fd248b-a248-461b-910c-d9381f3fc1b4","url":"http://localhost:8338/klantinteracties/api/v1/partijen/13fd248b-a248-461b-910c-d9381f3fc1b4","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 200 + message: OK +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen].yaml b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen].yaml new file mode 100644 index 0000000000..c26219a427 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[betrokkenen].yaml @@ -0,0 +1,80 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "indicatieGeheimhouding": false, "indicatieActief": + true, "voorkeurstaal": "crp", "soortPartij": "persoon", "partijIdentificatie": + {"contactnaam": {"voorletters": "Dr.", "voornaam": "Test Persoon", "voorvoegselAchternaam": + "Mrs.", "achternaam": "Gamble"}}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '373' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"94d92995-11df-4c04-a0c3-d0ba6c8bb382","url":"http://localhost:8338/klantinteracties/api/v1/partijen/94d92995-11df-4c04-a0c3-d0ba6c8bb382","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/94d92995-11df-4c04-a0c3-d0ba6c8bb382 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +- request: + body: null + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + method: GET + uri: http://localhost:8338/klantinteracties/api/v1/partijen/94d92995-11df-4c04-a0c3-d0ba6c8bb382?expand=betrokkenen + response: + body: + string: '{"uuid":"94d92995-11df-4c04-a0c3-d0ba6c8bb382","url":"http://localhost:8338/klantinteracties/api/v1/partijen/94d92995-11df-4c04-a0c3-d0ba6c8bb382","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 200 + message: OK +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[categorieRelaties].yaml b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[categorieRelaties].yaml new file mode 100644 index 0000000000..56ab9b42a1 --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[categorieRelaties].yaml @@ -0,0 +1,80 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "indicatieGeheimhouding": false, "indicatieActief": + true, "voorkeurstaal": "crp", "soortPartij": "persoon", "partijIdentificatie": + {"contactnaam": {"voorletters": "Dr.", "voornaam": "Test Persoon", "voorvoegselAchternaam": + "Mrs.", "achternaam": "Gamble"}}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '373' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"00269957-d49b-4373-a19d-f7d8336d8265","url":"http://localhost:8338/klantinteracties/api/v1/partijen/00269957-d49b-4373-a19d-f7d8336d8265","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/00269957-d49b-4373-a19d-f7d8336d8265 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +- request: + body: null + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + method: GET + uri: http://localhost:8338/klantinteracties/api/v1/partijen/00269957-d49b-4373-a19d-f7d8336d8265?expand=categorieRelaties + response: + body: + string: '{"uuid":"00269957-d49b-4373-a19d-f7d8336d8265","url":"http://localhost:8338/klantinteracties/api/v1/partijen/00269957-d49b-4373-a19d-f7d8336d8265","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 200 + message: OK +version: 1 diff --git a/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[digitaleAdressen].yaml b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[digitaleAdressen].yaml new file mode 100644 index 0000000000..22d0a7d69b --- /dev/null +++ b/src/openklant2/tests/cassettes/test_partij/test_retrieve_partij[digitaleAdressen].yaml @@ -0,0 +1,80 @@ +interactions: +- request: + body: '{"digitaleAdressen": null, "voorkeursDigitaalAdres": null, "rekeningnummers": + null, "voorkeursRekeningnummer": null, "indicatieGeheimhouding": false, "indicatieActief": + true, "voorkeurstaal": "crp", "soortPartij": "persoon", "partijIdentificatie": + {"contactnaam": {"voorletters": "Dr.", "voornaam": "Test Persoon", "voorvoegselAchternaam": + "Mrs.", "achternaam": "Gamble"}}}' + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + Content-Length: + - '373' + Content-Type: + - application/json + method: POST + uri: http://localhost:8338/klantinteracties/api/v1/partijen + response: + body: + string: '{"uuid":"c6104a02-d92a-489a-a82c-cce0e9f3f058","url":"http://localhost:8338/klantinteracties/api/v1/partijen/c6104a02-d92a-489a-a82c-cce0e9f3f058","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Location: + - http://localhost:8338/klantinteracties/api/v1/partijen/c6104a02-d92a-489a-a82c-cce0e9f3f058 + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 201 + message: Created +- request: + body: null + headers: + Authorization: + - Token b2eb1da9861da88743d72a3fb4344288fe2cba44 + method: GET + uri: http://localhost:8338/klantinteracties/api/v1/partijen/c6104a02-d92a-489a-a82c-cce0e9f3f058?expand=digitaleAdressen + response: + body: + string: '{"uuid":"c6104a02-d92a-489a-a82c-cce0e9f3f058","url":"http://localhost:8338/klantinteracties/api/v1/partijen/c6104a02-d92a-489a-a82c-cce0e9f3f058","nummer":"0000000001","interneNotitie":"","betrokkenen":[],"categorieRelaties":[],"digitaleAdressen":[],"voorkeursDigitaalAdres":null,"vertegenwoordigden":[],"rekeningnummers":[],"voorkeursRekeningnummer":null,"partijIdentificatoren":[],"soortPartij":"persoon","indicatieGeheimhouding":false,"voorkeurstaal":"crp","indicatieActief":true,"bezoekadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"correspondentieadres":{"nummeraanduidingId":"","adresregel1":"","adresregel2":"","adresregel3":"","land":""},"partijIdentificatie":{"contactnaam":{"voorletters":"Dr.","voornaam":"Test + Persoon","voorvoegselAchternaam":"Mrs.","achternaam":"Gamble"},"volledigeNaam":"Test + Persoon Mrs. Gamble"}}' + headers: + API-version: + - 0.0.3 + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Content-Length: + - '877' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Referrer-Policy: + - same-origin + Vary: + - origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 200 + message: OK +version: 1 diff --git a/src/openklant2/tests/conftest.py b/src/openklant2/tests/conftest.py new file mode 100644 index 0000000000..77926d9e65 --- /dev/null +++ b/src/openklant2/tests/conftest.py @@ -0,0 +1,47 @@ +import pytest + +from openklant2.tests.helpers import OpenKlantService + + +def pytest_addoption(parser): + parser.addoption( + "--with-openklant-service", + action="store_true", + default=False, + help="Whether to seed a fresh OpenKlant Docker instance for each test", + ) + + +@pytest.fixture(scope="session", autouse=True) +def openklant_service_singleton(request): + service = OpenKlantService() + if request.config.getoption("--with-openklant-service"): + with service as running_service: + yield running_service + else: + yield service + + +@pytest.fixture() +def client(request, openklant_service_singleton: OpenKlantService): + if request.config.getoption("--with-openklant-service"): + with openklant_service_singleton.clean_state() as client: + yield client + else: + # Use cassettes instead: simply return a client which will cause the responses to be replayed + yield openklant_service_singleton.client_factory() + + +@pytest.fixture(scope="session") +def vcr_config(): + return { + "match_on": [ + "method", + "scheme", + "host", + "port", + "path", + "query", + "body", + ], + } diff --git a/src/openklant2/tests/helpers.py b/src/openklant2/tests/helpers.py new file mode 100644 index 0000000000..4016b8e0b7 --- /dev/null +++ b/src/openklant2/tests/helpers.py @@ -0,0 +1,177 @@ +import json +import logging +import subprocess +import time +from contextlib import contextmanager +from pathlib import Path + +import requests + +from openklant2.client import OpenKlant2Client + +BASE_DIR = Path(__file__).parent.parent.resolve() + +logger = logging.getLogger(__name__) + + +class OpenKlantService: + _in_server_context: bool = False + _django_service_name: str = "web" + _service_url: str = "http://localhost:8338" + _api_base_path: str = "/klantinteracties/api/v1" + _api_token: str = "b2eb1da9861da88743d72a3fb4344288fe2cba44" + _docker_compose_project_name: str = "openklant2-api-test" + _docker_compose_path: Path = BASE_DIR / "docker-compose.yaml" + + def _docker_compose( + self, + *args: str, + check: bool = True, + input: str | None = None, + ): + input_data = {"text": True, "input": input} if input else {} + try: + return subprocess.run( + args=[ + "docker-compose", + "-f", + str(self._docker_compose_path), + "-p", + self._docker_compose_project_name, + *args, + ], + check=check, + capture_output=True, + **input_data, + ) + except subprocess.CalledProcessError as exc: + logger.exception( + "Unable to execute command", + exc_info=True, + extra={"stderr": exc.stderr, "stdout": exc.stdout}, + ) + raise + + def _manage_py( + self, + *args: str, + input: str | None = None, + ): + self._docker_compose( + "run", + "--rm", + self._django_service_name, + "python", + "src/manage.py", + *args, + input=input, + ) + + def _service_teardown(self): + self._docker_compose("kill", check=False) + self._docker_compose("down", "-v") + self._docker_compose("rm", "-f") + + def _service_init(self): + self._docker_compose("up", "-d") + self._wait_for_response() + self._manage_py("migrate") + + def _reset_db_state(self): + self._manage_py("flush", "--no-input") + self._load_fixture_from_json_string(self._generate_token_fixture()) + + def _load_fixture_from_json_string(self, fixture: str): + self._manage_py( + "loaddata", + "--format", + "json", + "-", # i.e. stdin + input=fixture, + ) + + def _generate_token_fixture(self): + return json.dumps( + [ + { + "model": "token.tokenauth", + "pk": 1, + "fields": { + "token": self._api_token, + "contact_person": "Boaty McBoatface", + "email": "boaty@mcboatface.com", + "organization": "", + "last_modified": "2024-08-22T07:43:21.837Z", + "created": "2024-08-22T07:43:21.837Z", + "application": "", + "administration": "", + }, + } + ] + ) + + def _wait_for_response(self, interval=0.5, max_wait=60): + start_time = time.time() + while True: + try: + response = requests.get(self._service_url) + return response + except requests.RequestException: + logger.debug("Exception while checking for liveness", exc_info=True) + elapsed_time = time.time() - start_time + if elapsed_time >= max_wait: + logger.info("Max wait time exceeded.") + raise RuntimeError( + f"Maximum wait for service to be healthy exceeded: {elapsed_time} > {max_wait}" + ) + + time.sleep(interval) + + def __enter__(self): + if self._in_server_context: + raise RuntimeError( + "You cannot have multiple server contexts active at the same time" + ) + + self._in_server_context = True + self._service_teardown() + self._service_init() + + return self + + @property + def api_root(self): + return f"{self._service_url}{self._api_base_path}" + + def client_factory(self): + return OpenKlant2Client( + api_root=self.api_root, + token=self._api_token, + ) + + def clean_state(self): + """Yield a client configured for an ephemeral OpenKlant2 service with a fresh state. + + Usage: + with OpenKlantService() as service: + with service.clean_state() as client: + client. + """ + if not self._in_server_context: + raise RuntimeError( + "You must execute this context within the server context:\n" + "with OpenKlantServer() as server:\n" + " with server.fresh_db() as client:\n" + " # client.do_stuff()" + ) + + @contextmanager + def managed_resource(*args, **kwds): + self._reset_db_state() + yield self.client_factory() + + return managed_resource() + + def __exit__(self, exc_type, exc_value, exc_tb): + self._service_teardown() + self._in_server_context = False diff --git a/src/openklant2/tests/test_partij.py b/src/openklant2/tests/test_partij.py new file mode 100644 index 0000000000..a91f53c7c5 --- /dev/null +++ b/src/openklant2/tests/test_partij.py @@ -0,0 +1,223 @@ +import pytest +from pydantic import TypeAdapter + +from openklant2.exceptions import BadRequest +from openklant2.factories.partij import ( + CreatePartijContactPersoonDataFactory, + CreatePartijOrganisatieDataFactory, + CreatePartijPersoonDataFactory, +) +from openklant2.types import PaginatedResponseBody +from openklant2.types.resources import ( + CreatePartijContactpersoonDataValidator, + CreatePartijOrganisatieDataValidator, + CreatePartijPersoonDataValidator, + Partij, + PartijValidator, +) + + +@pytest.fixture() +def a_partij(client): + data = { + "digitaleAdressen": None, + "voorkeursDigitaalAdres": None, + "rekeningnummers": None, + "voorkeursRekeningnummer": None, + "indicatieGeheimhouding": False, + "indicatieActief": True, + "voorkeurstaal": "crp", + "soortPartij": "persoon", + "partijIdentificatie": { + "contactnaam": { + "voorletters": "Dr.", + "voornaam": "Test Persoon", + "voorvoegselAchternaam": "Mrs.", + "achternaam": "Gamble", + } + }, + } + return client.partij.create_persoon(data=data) + + +@pytest.fixture() +def an_organisatie(client): + data = { + "digitaleAdressen": None, + "voorkeursDigitaalAdres": None, + "rekeningnummers": None, + "voorkeursRekeningnummer": None, + "indicatieGeheimhouding": False, + "indicatieActief": True, + "voorkeurstaal": "tiv", + "soortPartij": "organisatie", + "partijIdentificatie": {"naam": "Test Organisatie"}, + } + return client.partij.create_organisatie(data=data) + + +@pytest.mark.vcr +@pytest.mark.usefixtures("a_partij") +@pytest.mark.usefixtures("an_organisatie") +def test_list_partijen(client): + resp = client.partij.list() + + TypeAdapter(PaginatedResponseBody[Partij]).validate_python(resp) + + +@pytest.mark.vcr +@pytest.mark.parametrize( + "expand", + ( + "betrokkenen", + "betrokkenen.hadKlantcontact", + "categorieRelaties", + "digitaleAdressen", + ), +) +def test_retrieve_partij(client, a_partij, expand): + resp = client.partij.retrieve( + a_partij["uuid"], + params={"expand": [expand]}, + ) + + PartijValidator.validate_python(resp) + + +@pytest.mark.vcr +def test_create_persoon(client) -> None: + data = CreatePartijPersoonDataValidator.validate_python( + { + "soortPartij": "persoon", + "digitaleAdressen": None, + "rekeningnummers": None, + "voorkeursRekeningnummer": None, + "soortPartij": "persoon", + "voorkeurstaal": "nld", + "indicatieActief": True, + "indicatieGeheimhouding": False, + "voorkeursDigitaalAdres": None, + "partijIdentificatie": {"contactnaam": None}, + } + ) + resp = client.partij.create_persoon( + data=data, + ) + + PartijValidator.validate_python(resp) + + +@pytest.mark.vcr +def test_create_contactpersoon(client, an_organisatie): + data = CreatePartijContactpersoonDataValidator.validate_python( + { + "soortPartij": "contactpersoon", + "digitaleAdressen": None, + "rekeningnummers": None, + "voorkeursRekeningnummer": None, + "voorkeurstaal": "nld", + "indicatieActief": True, + "indicatieGeheimhouding": False, + "voorkeursDigitaalAdres": None, + "partijIdentificatie": { + "contactnaam": None, + "werkteVoorPartij": {"uuid": an_organisatie["uuid"]}, + }, + } + ) + resp = client.partij.create_contactpersoon( + data=data, + ) + + PartijValidator.validate_python(resp) + + +@pytest.mark.vcr +def test_create_organisatie(client): + data = CreatePartijOrganisatieDataValidator.validate_python( + { + "soortPartij": "organisatie", + "digitaleAdressen": None, + "rekeningnummers": None, + "voorkeursRekeningnummer": None, + "voorkeurstaal": "nld", + "indicatieActief": True, + "indicatieGeheimhouding": False, + "voorkeursDigitaalAdres": None, + "partijIdentificatie": {"naam": "AcmeCorp Ltd"}, + } + ) + resp = client.partij.create_organisatie( + data=data, + ) + + PartijValidator.validate_python(resp) + + +@pytest.mark.vcr +def test_create_with_bad_request_exception(client): + with pytest.raises(BadRequest) as exc_info: + client.partij.create_organisatie( + data={}, + ) + + got = ( + exc_info.value.status, + exc_info.value.code, + exc_info.value.title, + exc_info.value.invalidParams, + ) + want = ( + 400, + "invalid", + "Invalid input.", + [ + { + "name": "digitaleAdressen", + "code": "required", + "reason": "Dit veld is vereist.", + }, + { + "name": "voorkeursDigitaalAdres", + "code": "required", + "reason": "Dit veld is vereist.", + }, + { + "name": "rekeningnummers", + "code": "required", + "reason": "Dit veld is vereist.", + }, + { + "name": "voorkeursRekeningnummer", + "code": "required", + "reason": "Dit veld is vereist.", + }, + { + "name": "soortPartij", + "code": "required", + "reason": "Dit veld is vereist.", + }, + { + "name": "indicatieActief", + "code": "required", + "reason": "Dit veld is vereist.", + }, + ], + ) + + assert got == want + + +def test_factory_partij_persoon_data(): + data = CreatePartijPersoonDataFactory.build() + CreatePartijPersoonDataValidator.validate_python(data) + + +def test_factory_partij_organisatie_data(): + data = CreatePartijOrganisatieDataFactory.build() + CreatePartijOrganisatieDataValidator.validate_python(data) + + +def test_factory_partij_contactpersoon_data(): + data = CreatePartijContactPersoonDataFactory.build() + CreatePartijContactpersoonDataValidator.validate_python(data) diff --git a/src/openklant2/types/__init__.py b/src/openklant2/types/__init__.py new file mode 100644 index 0000000000..8a645fd03c --- /dev/null +++ b/src/openklant2/types/__init__.py @@ -0,0 +1,21 @@ +from .common import Adres +from .error import ( + ErrorResponseBody, + ErrorResponseBodyValidator, + InvalidParam, + ValidationErrorResponseBody, + ValidationErrorResponseBodyValidator, +) +from .iso_639_2 import LanguageCode +from .pagination import PaginatedResponseBody + +__all__ = [ + "ErrorResponseBodyValidator", + "ValidationErrorResponseBodyValidator", + "Adres", + "LanguageCode", + "PaginatedResponseBody", + "InvalidParam", + "ErrorResponseBody", + "ValidationErrorResponseBody", +] diff --git a/src/openklant2/types/common.py b/src/openklant2/types/common.py new file mode 100644 index 0000000000..b115f1e6d2 --- /dev/null +++ b/src/openklant2/types/common.py @@ -0,0 +1,15 @@ +from typing import NotRequired, Required + +from typing_extensions import TypedDict + + +class Adres(TypedDict): + nummeraanduidingId: Required[str | None] + adresregel1: NotRequired[str] + adresregel2: NotRequired[str] + adresregel3: NotRequired[str] + land: NotRequired[str] + + +class ForeignKeyRef(TypedDict): + uuid: str diff --git a/src/openklant2/types/error.py b/src/openklant2/types/error.py new file mode 100644 index 0000000000..31aebe7e3a --- /dev/null +++ b/src/openklant2/types/error.py @@ -0,0 +1,28 @@ +from typing import Literal + +from pydantic import TypeAdapter +from typing_extensions import TypedDict + + +class InvalidParam(TypedDict): + name: str + code: str + reason: str + + +class ErrorResponseBody(TypedDict): + type: str + code: str + title: str + status: int + detail: str + instance: str + + +class ValidationErrorResponseBody(ErrorResponseBody): + status: Literal[400] + invalidParams: list[InvalidParam] + + +ErrorResponseBodyValidator = TypeAdapter(ErrorResponseBody) +ValidationErrorResponseBodyValidator = TypeAdapter(ValidationErrorResponseBody) diff --git a/src/openklant2/types/iso_639_2.py b/src/openklant2/types/iso_639_2.py new file mode 100644 index 0000000000..0426d70df0 --- /dev/null +++ b/src/openklant2/types/iso_639_2.py @@ -0,0 +1,510 @@ +from typing import Literal + +LanguageCode = Literal[ + "abk", + "ace", + "ach", + "ada", + "ady", + "afa", + "afh", + "afr", + "ain", + "aka", + "akk", + "alb", + "sqi", + "ale", + "alg", + "alt", + "amh", + "ang", + "anp", + "apa", + "ara", + "arc", + "arg", + "arm", + "hye", + "arn", + "arp", + "art", + "arw", + "asm", + "ast", + "ath", + "aus", + "ava", + "ave", + "awa", + "aym", + "aze", + "bad", + "bai", + "bak", + "bal", + "bam", + "ban", + "baq", + "eus", + "bas", + "bat", + "bej", + "bel", + "bem", + "ben", + "ber", + "bho", + "bih", + "bik", + "bin", + "bis", + "bla", + "bnt", + "tib", + "bod", + "bos", + "bra", + "bre", + "btk", + "bua", + "bug", + "bul", + "bur", + "mya", + "byn", + "cad", + "cai", + "car", + "cat", + "cau", + "ceb", + "cel", + "cze", + "ces", + "cha", + "chb", + "che", + "chg", + "chi", + "zho", + "chk", + "chm", + "chn", + "cho", + "chp", + "chr", + "chu", + "chv", + "chy", + "cmc", + "cnr", + "cop", + "cor", + "cos", + "cpe", + "cpf", + "cpp", + "cre", + "crh", + "crp", + "csb", + "cus", + "wel", + "cym", + "dak", + "dan", + "dar", + "day", + "del", + "den", + "ger", + "deu", + "dgr", + "din", + "div", + "doi", + "dra", + "dsb", + "dua", + "dum", + "dut", + "nld", + "dyu", + "dzo", + "efi", + "egy", + "eka", + "gre", + "ell", + "elx", + "eng", + "enm", + "epo", + "est", + "ewe", + "ewo", + "fan", + "fao", + "per", + "fas", + "fat", + "fij", + "fil", + "fin", + "fiu", + "fon", + "fre", + "fra", + "frm", + "fro", + "frr", + "frs", + "fry", + "ful", + "fur", + "gaa", + "gay", + "gba", + "gem", + "geo", + "kat", + "gez", + "gil", + "gla", + "gle", + "glg", + "glv", + "gmh", + "goh", + "gon", + "gor", + "got", + "grb", + "grc", + "grn", + "gsw", + "guj", + "gwi", + "hai", + "hat", + "hau", + "haw", + "heb", + "her", + "hil", + "him", + "hin", + "hit", + "hmn", + "hmo", + "hrv", + "hsb", + "hun", + "hup", + "iba", + "ibo", + "ice", + "isl", + "ido", + "iii", + "ijo", + "iku", + "ile", + "ilo", + "ina", + "inc", + "ind", + "ine", + "inh", + "ipk", + "ira", + "iro", + "ita", + "jav", + "jbo", + "jpn", + "jpr", + "jrb", + "kaa", + "kab", + "kac", + "kal", + "kam", + "kan", + "kar", + "kas", + "kau", + "kaw", + "kaz", + "kbd", + "kha", + "khi", + "khm", + "kho", + "kik", + "kin", + "kir", + "kmb", + "kok", + "kom", + "kon", + "kor", + "kos", + "kpe", + "krc", + "krl", + "kro", + "kru", + "kua", + "kum", + "kur", + "kut", + "lad", + "lah", + "lam", + "lao", + "lat", + "lav", + "lez", + "lim", + "lin", + "lit", + "lol", + "loz", + "ltz", + "lua", + "lub", + "lug", + "lui", + "lun", + "luo", + "lus", + "mac", + "mkd", + "mad", + "mag", + "mah", + "mai", + "mak", + "mal", + "man", + "mao", + "mri", + "map", + "mar", + "mas", + "may", + "msa", + "mdf", + "mdr", + "men", + "mga", + "mic", + "min", + "mis", + "mkh", + "mlg", + "mlt", + "mnc", + "mni", + "mno", + "moh", + "mon", + "mos", + "mul", + "mun", + "mus", + "mwl", + "mwr", + "myn", + "myv", + "nah", + "nai", + "nap", + "nau", + "nav", + "nbl", + "nde", + "ndo", + "nds", + "nep", + "new", + "nia", + "nic", + "niu", + "nno", + "nob", + "nog", + "non", + "nor", + "nqo", + "nso", + "nub", + "nwc", + "nya", + "nym", + "nyn", + "nyo", + "nzi", + "oci", + "oji", + "ori", + "orm", + "osa", + "oss", + "ota", + "oto", + "paa", + "pag", + "pal", + "pam", + "pan", + "pap", + "pau", + "peo", + "phi", + "phn", + "pli", + "pol", + "pon", + "por", + "pra", + "pro", + "pus", + "que", + "raj", + "rap", + "rar", + "roa", + "roh", + "rom", + "rum", + "ron", + "run", + "rup", + "rus", + "sad", + "sag", + "sah", + "sai", + "sal", + "sam", + "san", + "sas", + "sat", + "scn", + "sco", + "sel", + "sem", + "sga", + "sgn", + "shn", + "sid", + "sin", + "sio", + "sit", + "sla", + "slo", + "slk", + "slv", + "sma", + "sme", + "smi", + "smj", + "smn", + "smo", + "sms", + "sna", + "snd", + "snk", + "sog", + "som", + "son", + "sot", + "spa", + "srd", + "srn", + "srp", + "srr", + "ssa", + "ssw", + "suk", + "sun", + "sus", + "sux", + "swa", + "swe", + "syc", + "syr", + "tah", + "tai", + "tam", + "tat", + "tel", + "tem", + "ter", + "tet", + "tgk", + "tgl", + "tha", + "tig", + "tir", + "tiv", + "tkl", + "tlh", + "tli", + "tmh", + "tog", + "ton", + "tpi", + "tsi", + "tsn", + "tso", + "tuk", + "tum", + "tup", + "tur", + "tut", + "tvl", + "twi", + "tyv", + "udm", + "uga", + "uig", + "ukr", + "umb", + "und", + "urd", + "uzb", + "vai", + "ven", + "vie", + "vol", + "vot", + "wak", + "wal", + "war", + "was", + "wen", + "wln", + "wol", + "xal", + "xho", + "yao", + "yap", + "yid", + "yor", + "ypk", + "zap", + "zbl", + "zen", + "zgh", + "zha", + "znd", + "zul", + "zun", + "zxx", + "zza", +] +"""ISO 639-2 alpha-3 language codes.""" diff --git a/src/openklant2/types/pagination.py b/src/openklant2/types/pagination.py new file mode 100644 index 0000000000..f1b761e3e0 --- /dev/null +++ b/src/openklant2/types/pagination.py @@ -0,0 +1,12 @@ +from typing import Generic, Required, TypeVar + +from typing_extensions import TypedDict + +T = TypeVar("T") + + +class PaginatedResponseBody(Generic[T], TypedDict): + count: Required[int] + next: Required[str | None] + previous: Required[str | None] + results: Required[list[T]] diff --git a/src/openklant2/types/resources/__init__.py b/src/openklant2/types/resources/__init__.py new file mode 100644 index 0000000000..4c4aa960f3 --- /dev/null +++ b/src/openklant2/types/resources/__init__.py @@ -0,0 +1,35 @@ +from .partij import ( + Contactnaam, + CreatePartijContactpersoonData, + CreatePartijContactpersoonDataValidator, + CreatePartijDataBase, + CreatePartijOrganisatieData, + CreatePartijOrganisatieDataValidator, + CreatePartijPersoonData, + CreatePartijPersoonDataValidator, + Partij, + PartijIdentificatieContactpersoon, + PartijIdentificatieOrganisatie, + PartijIdentificatiePersoon, + PartijListParams, + PartijRetrieveParams, + PartijValidator, +) + +__all__ = [ + "Contactnaam", + "CreatePartijDataBase", + "PartijIdentificatiePersoon", + "PartijIdentificatieContactpersoon", + "PartijIdentificatieOrganisatie", + "CreatePartijPersoonData", + "CreatePartijContactpersoonData", + "CreatePartijOrganisatieData", + "Partij", + "PartijListParams", + "PartijRetrieveParams", + "PartijValidator", + "CreatePartijPersoonDataValidator", + "CreatePartijOrganisatieDataValidator", + "CreatePartijContactpersoonDataValidator", +] diff --git a/src/openklant2/types/resources/partij.py b/src/openklant2/types/resources/partij.py new file mode 100644 index 0000000000..6a654eee14 --- /dev/null +++ b/src/openklant2/types/resources/partij.py @@ -0,0 +1,103 @@ +from typing import Literal, NotRequired, Required + +from pydantic import TypeAdapter +from typing_extensions import TypedDict + +from openklant2.types.common import Adres, ForeignKeyRef +from openklant2.types.iso_639_2 import LanguageCode + + +class Contactnaam(TypedDict): + voorletters: str + voornaam: str + voorvoegselAchternaam: str + achternaam: str + + +# Note this is polymorphic, concrete types below +class CreatePartijDataBase(TypedDict): + nummer: NotRequired[str] + interneNotitie: NotRequired[str] + digitaleAdressen: Required[list[ForeignKeyRef] | None] + voorkeursDigitaalAdres: Required[ForeignKeyRef | None] + rekeningnummers: Required[list[ForeignKeyRef] | None] + voorkeursRekeningnummer: Required[ForeignKeyRef | None] + voorkeurstaal: LanguageCode + indicatieActief: bool + indicatieGeheimhouding: bool + correspondentieadres: NotRequired[Adres] + bezoekadres: NotRequired[Adres | None] + + +class PartijIdentificatiePersoon(TypedDict): + contactnaam: Required[Contactnaam | None] + + +class PartijIdentificatieContactpersoon(TypedDict): + contactnaam: Required[Contactnaam | None] + werkteVoorPartij: ForeignKeyRef + + +class PartijIdentificatieOrganisatie(TypedDict): + naam: Required[str | None] + + +class CreatePartijPersoonData(CreatePartijDataBase): + soortPartij: Literal["persoon"] + partijIdentificatie: PartijIdentificatiePersoon + + +class CreatePartijContactpersoonData(CreatePartijDataBase): + soortPartij: Literal["contactpersoon"] + partijIdentificatie: PartijIdentificatieContactpersoon + + +class CreatePartijOrganisatieData(CreatePartijDataBase): + soortPartij: Literal["organisatie"] + partijIdentificatie: PartijIdentificatieOrganisatie + + +class Partij(TypedDict): + uuid: Required[str] + nummer: NotRequired[str] + interneNotitie: NotRequired[str] + digitaleAdressen: Required[list[ForeignKeyRef] | None] + voorkeursDigitaalAdres: Required[ForeignKeyRef | None] + rekeningnummers: Required[list[ForeignKeyRef] | None] + voorkeursRekeningnummer: Required[ForeignKeyRef | None] + + voorkeurstaal: LanguageCode + indicatieActief: bool + indicatieGeheimhouding: bool + correspondentieadres: NotRequired[Adres] + bezoekadres: NotRequired[Adres | None] + soortPartij: Literal["organisatie", "persoon", "contactpersoon"] + partijIdentificatie: ( + PartijIdentificatieContactpersoon + | PartijIdentificatiePersoon + | PartijIdentificatieOrganisatie + ) + + +class PartijListParams(TypedDict): + page: NotRequired[int] + vertegenwoordigdePartij__url: NotRequired[str] + + +class PartijRetrieveParams(TypedDict): + expand: NotRequired[ + list[ + Literal[ + "betrokkenen", + "betrokkenen.hadKlantcontact", + "categorieRelaties", + "digitaleAdressen", + ] + ] + ] + + +PartijValidator = TypeAdapter(Partij) +CreatePartijPersoonDataValidator = TypeAdapter(CreatePartijPersoonData) +CreatePartijOrganisatieDataValidator = TypeAdapter(CreatePartijOrganisatieData) +CreatePartijContactpersoonDataValidator = TypeAdapter(CreatePartijContactpersoonData)