diff --git a/README.md b/README.md index 85ab5d6..159501e 100644 --- a/README.md +++ b/README.md @@ -30,25 +30,49 @@ python3 -m pip install brreg - [Issue tracker](https://github.com/jodal/python-brreg/issues) - [Contributors](https://github.com/jodal/python-brreg/graphs/contributors) -## Development status +## Features -This project was originally developed in 2019 while I worked at Otovo. -Eventually, it was never used there, and at the end of 2023, I got the project -back under my control. +### Enhetsregistret -I intend to brush the project up with modern project tooling and typing hints, -making it a good foundation to build upon, and will release a 1.0 release as -soon as that is done. +The `brreg.enhetsregisteret` part of this library wraps the open +[Enhetsregistret API](https://data.brreg.no/enhetsregisteret/api/docs/index.html). +This is a list of all features this library could implement on top of this API. However, I will not attempt to make the project cover all parts of -Brønnøysundregisterene's API. I am open to requests, so that time is spent -on the parts of the API that there is an actual demand for. +Brønnøysundregisterene's API. I am open to requests, so that time is spent on +the parts of the API that there is an actual demand for. + +- Enheter + - Search -- Implemented + - Get one by organization number -- Implemented + - Get one's roles by organization number -- Planned + - Get all updates since given time -- Request if needed +- Underenheter + - Search -- Implemented + - Get one by organization number -- Implemented + - Get all updates since given time -- Request if needed +- Organisasjonsform + - Get all -- Request if needed +- Rolletype + - Get all -- Request if needed + - Get one -- Request if needed +- Rollegrupetype + - Get all -- Request if needed + - Get one -- Request if needed +- Representant + - Get all -- Request if needed + - Get one -- Request if needed +- Kommuner + - Get all -- Request if needed +- Matrikkelenhet + - Get one by matrikkelnummer -- Request if needed + - Get one by matrikkelenhet-ID -- Request if needed ## License -`python-brreg` is copyright +Copyright 2019 [Otovo ASA](https://www.otovo.com/), 2023 Stein Magnus Jodal and contributors. -`python-brreg` is licensed under the +Licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/docs/api.rst b/docs/api.rst index d3ce78d..695f4e0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -20,11 +20,44 @@ Exceptions Enhetsregisteret ================ -.. automodule:: brreg.enhetsregisteret +.. module:: brreg.enhetsregisteret + +Client +------ .. autoclass:: brreg.enhetsregisteret.Client :members: +Query objects +------------- + +.. autoclass:: brreg.enhetsregisteret.Query + :members: + :exclude-members: model_config, model_fields + +.. autoclass:: brreg.enhetsregisteret.EnhetQuery + :members: + :exclude-members: model_config, model_fields + +.. autoclass:: brreg.enhetsregisteret.UnderenhetQuery + :members: + :exclude-members: model_config, model_fields + +Response objects +---------------- + +.. autoclass:: brreg.enhetsregisteret.Page + :members: + :exclude-members: model_config, model_fields + +.. autoclass:: brreg.enhetsregisteret.EnhetPage + :members: + :exclude-members: model_config, model_fields + +.. autoclass:: brreg.enhetsregisteret.UnderenhetPage + :members: + :exclude-members: model_config, model_fields + .. autoclass:: brreg.enhetsregisteret.Enhet :members: :exclude-members: model_config, model_fields @@ -37,11 +70,11 @@ Enhetsregisteret :members: :exclude-members: model_config, model_fields -.. autoclass:: brreg.enhetsregisteret.InstitusjonellSektorkode +.. autoclass:: brreg.enhetsregisteret.InstitusjonellSektor :members: :exclude-members: model_config, model_fields -.. autoclass:: brreg.enhetsregisteret.Naeringskode +.. autoclass:: brreg.enhetsregisteret.Naering :members: :exclude-members: model_config, model_fields diff --git a/src/brreg/enhetsregisteret/__init__.py b/src/brreg/enhetsregisteret/__init__.py index a97313b..3599be4 100644 --- a/src/brreg/enhetsregisteret/__init__.py +++ b/src/brreg/enhetsregisteret/__init__.py @@ -4,23 +4,52 @@ """ from brreg.enhetsregisteret._client import Client -from brreg.enhetsregisteret._types import ( +from brreg.enhetsregisteret._pagination import EnhetPage, Page, UnderenhetPage +from brreg.enhetsregisteret._queries import EnhetQuery, Query, UnderenhetQuery +from brreg.enhetsregisteret._responses import ( Adresse, Enhet, - InstitusjonellSektorkode, - Naeringskode, + InstitusjonellSektor, + Naering, Organisasjonsform, Underenhet, ) +from brreg.enhetsregisteret._types import ( + Kommunenummer, + KommunenummerValidator, + Organisasjonsnummer, + OrganisasjonsnummerValidator, + Postnummer, + PostnummerValidator, + Sektorkode, + SektorkodeValidator, +) __all__ = [ # From _client module: "Client", - # From _types module: + # From _pagination module: + "EnhetPage", + "Page", + "UnderenhetPage", + # From _queries module: + "EnhetQuery", + "Query", + "UnderenhetQuery", + # From _responses module: "Adresse", "Enhet", - "InstitusjonellSektorkode", - "Naeringskode", + "InstitusjonellSektor", + "Naering", "Organisasjonsform", "Underenhet", + # From _types module: + "Kommunenummer", + "KommunenummerValidator", + "Organisasjonsnummer", + "OrganisasjonsnummerValidator", + "Postnummer", + "PostnummerValidator", + "Sektorkode", + "SektorkodeValidator", ] diff --git a/src/brreg/enhetsregisteret/_client.py b/src/brreg/enhetsregisteret/_client.py index 549f7d4..b8c371b 100644 --- a/src/brreg/enhetsregisteret/_client.py +++ b/src/brreg/enhetsregisteret/_client.py @@ -6,11 +6,18 @@ import httpx from brreg import BrregError, BrregRestError -from brreg.enhetsregisteret._types import Enhet, Underenhet +from brreg.enhetsregisteret._pagination import EnhetPage, UnderenhetPage +from brreg.enhetsregisteret._responses import Enhet, Underenhet +from brreg.enhetsregisteret._types import ( + Organisasjonsnummer, + OrganisasjonsnummerValidator, +) if TYPE_CHECKING: from types import TracebackType + from brreg.enhetsregisteret._queries import EnhetQuery, UnderenhetQuery + class Client: """Client for the Enhetregisteret API. @@ -61,8 +68,12 @@ def close(self) -> None: """ self._client.close() - def get_enhet(self, organisasjonsnummer: str) -> Optional[Enhet]: + def get_enhet( + self, + organisasjonsnummer: Organisasjonsnummer, + ) -> Optional[Enhet]: """Get :class:`Enhet` given an organization number.""" + OrganisasjonsnummerValidator.validate_python(organisasjonsnummer) with error_handler(): res = self._client.get( f"/enheter/{organisasjonsnummer}", @@ -78,8 +89,12 @@ def get_enhet(self, organisasjonsnummer: str) -> Optional[Enhet]: res.raise_for_status() return Enhet.model_validate_json(res.content) - def get_underenhet(self, organisasjonsnummer: str) -> Optional[Underenhet]: + def get_underenhet( + self, + organisasjonsnummer: Organisasjonsnummer, + ) -> Optional[Underenhet]: """Get :class:`Underenhet` given an organization number.""" + OrganisasjonsnummerValidator.validate_python(organisasjonsnummer) with error_handler(): res = self._client.get( f"/underenheter/{organisasjonsnummer}", @@ -95,6 +110,48 @@ def get_underenhet(self, organisasjonsnummer: str) -> Optional[Underenhet]: res.raise_for_status() return Underenhet.model_validate_json(res.content) + def search_enhet( + self, + query: EnhetQuery, + ) -> EnhetPage: + """Search for :class:`Enhet` that matches the given query. + + :param query: The search query. + """ + with error_handler(): + res = self._client.get( + f"/enheter?{query.as_url_query()}", + headers={ + "accept": ( + "application/vnd.brreg.enhetsregisteret.enhet.v2+json;" + "charset=UTF-8" + ) + }, + ) + res.raise_for_status() + return EnhetPage.model_validate_json(res.content) + + def search_underenhet( + self, + query: UnderenhetQuery, + ) -> UnderenhetPage: + """Search for :class:`Underenhet` that matches the given query. + + :param query: The search query. + """ + with error_handler(): + res = self._client.get( + f"/underenheter?{query.as_url_query()}", + headers={ + "accept": ( + "application/vnd.brreg.enhetsregisteret.underenhet.v2+json;" + "charset=UTF-8" + ) + }, + ) + res.raise_for_status() + return UnderenhetPage.model_validate_json(res.content) + @contextmanager def error_handler() -> Generator[None, Any, None]: diff --git a/src/brreg/enhetsregisteret/_pagination.py b/src/brreg/enhetsregisteret/_pagination.py new file mode 100644 index 0000000..8df80a5 --- /dev/null +++ b/src/brreg/enhetsregisteret/_pagination.py @@ -0,0 +1,60 @@ +from typing import Generic, List, TypeVar + +from pydantic import ( + AliasPath, + BaseModel, + Field, +) + +from brreg.enhetsregisteret._responses import Enhet, Underenhet + +__all__ = [ + "EnhetPage", + "UnderenhetPage", +] + + +T = TypeVar("T", bound=BaseModel) + + +class Page(BaseModel, Generic[T]): + """The fields here are available on all page objects.""" + + #: The items on this page. + items: List[T] + + #: The number of elements on this page. + page_size: int = Field( + validation_alias=AliasPath("page", "size"), + ) + + #: The page number, starting at 0. + page_number: int = Field( + validation_alias=AliasPath("page", "number"), + ) + + #: The total number of elements available. + total_elements: int = Field( + validation_alias=AliasPath("page", "totalElements"), + ) + + #: The total number of pages available. + total_pages: int = Field( + validation_alias=AliasPath("page", "totalPages"), + ) + + +class EnhetPage(Page[Enhet]): + """Response type for enhet search.""" + + items: List[Enhet] = Field( + validation_alias=AliasPath("_embedded", "enheter"), + ) + + +class UnderenhetPage(Page[Underenhet]): + """Response type for underenhet search.""" + + items: List[Underenhet] = Field( + validation_alias=AliasPath("_embedded", "underenheter"), + ) diff --git a/src/brreg/enhetsregisteret/_queries.py b/src/brreg/enhetsregisteret/_queries.py new file mode 100644 index 0000000..6399d29 --- /dev/null +++ b/src/brreg/enhetsregisteret/_queries.py @@ -0,0 +1,382 @@ +import datetime as dt +from typing import Literal, Optional +from urllib.parse import urlencode + +from pydantic import ( + BaseModel, + Field, + PositiveInt, +) + +from brreg.enhetsregisteret._types import ( + CommaList, + Kommunenummer, + Naeringskode, + Organisasjonsnummer, + Postnummer, + Sektorkode, +) + +__all__ = [ + "EnhetQuery", + "UnderenhetQuery", +] + + +class Query(BaseModel): + """The fields here are available on all queries.""" + + #: Sortering av resultatsett + sort: Optional[Literal["ASC", "DESC"]] = None + + #: Sidestørrelse + size: Optional[PositiveInt] = None + + #: Sidenummer + page: Optional[PositiveInt] = None + + def as_url_query(self) -> str: + params = self.model_dump( + mode="json", + by_alias=True, + exclude_defaults=True, + ) + for key, value in params.items(): + if isinstance(value, bool): + params[key] = str(value).lower() + return urlencode(params) + + +class EnhetQuery(Query): + """The query type for enhet search.""" + + #: Enhetens navn + navn: Optional[str] = None + + #: Organisasjonsnummeret til enheten + organisasjonsnummer: CommaList[Organisasjonsnummer] = Field( + default_factory=list, + ) + + #: Organisasjonsnummeret til enhetens overordnede enhet + overordnet_enhet: Optional[Organisasjonsnummer] = Field( + default=None, + serialization_alias="overordnetEnhet", + ) + + #: Minste antall ansatte hos enheten + fra_antall_ansatte: Optional[PositiveInt] = Field( + default=None, + serialization_alias="fraAntallAnsatte", + ) + + #: Største antall ansatte hos enheten + til_antall_ansatte: Optional[PositiveInt] = Field( + default=None, + serialization_alias="tilAntallAnsatte", + ) + + #: Hvorvidt enheten er registrert som konkurs + konkurs: Optional[bool] = None + + #: Hvorvidt enheten er registrert i Mva-registeret + registrert_i_mvaregisteret: Optional[bool] = Field( + default=None, + serialization_alias="registrertIMvaregisteret", + ) + + #: Hvorvidt enheten er registrert i Foretaksregisteret + registert_i_foretaksregisteret: Optional[bool] = Field( + default=None, + serialization_alias="registrertIForetaksregisteret", + ) + + #: Hvorvidt enheten er registrert i Stiftelsesregisteret + registrert_i_stiftelsesregisteret: Optional[bool] = Field( + default=None, + serialization_alias="registrertIStiftelsesregisteret", + ) + + #: Hvorvidt enheten er registrert i Frivillighetsregisteret + registrert_i_frivillighetsregisteret: Optional[bool] = Field( + default=None, + serialization_alias="registrertIFrivillighetsregisteret", + ) + + #: Frivillig registrert i Merverdiavgiftsregisteret + frivillig_registrert_i_mvaregisteret: CommaList[str] = Field( + default_factory=list, + serialization_alias="frivilligRegistrertIMvaregisteret", + ) + + #: Hvorvidt enheten er registrert som under tvangsavvikling eller + #: tvangsopplosning + under_tvangsavvikling_eller_trangsopplosning: Optional[bool] = None + + #: Hvorvidt enheten er registrert som under avvikling + under_avvikling: Optional[bool] = None + + #: Tidligste registreringsdato i Enhetsregisteret + fra_registreringsdato_enhetsregisteret: Optional[dt.date] = Field( + default=None, + serialization_alias="fraRegistreringsdatoEnhetsregisteret", + ) + + #: Seneste registreringsdato i Enhetsregisteret + til_registreringsdato_enhetsregisteret: Optional[dt.date] = Field( + default=None, + serialization_alias="tilRegistreringsdatoEnhetsregisteret", + ) + + #: Tidligste stiftelsesdato for enheten + fra_stiftelsesdato: Optional[dt.date] = Field( + default=None, + serialization_alias="fraStiftelsesdato", + ) + + #: Seneste stiftelsesdato for enheten + til_stiftelsesdato: Optional[dt.date] = Field( + default=None, + serialization_alias="tilStiftelsesdato", + ) + + #: Enhetens organisasjonsform + organisasjonsform: CommaList[str] = Field( + default_factory=list, + ) + + #: Enhetens hjemmeside + hjemmeside: Optional[str] = None + + #: Enhetens institusjonelle sektorkode + institusjonell_sektorkode: CommaList[Sektorkode] = Field( + default_factory=list, + ) + + #: Kommunenummer til enhetens postadresse + postadresse_kommunenummer: CommaList[Kommunenummer] = Field( + default_factory=list, + serialization_alias="postadresse.kommunenummer", + ) + + #: Postnummeret til enhetens postadresse + postadresse_postnummer: CommaList[Postnummer] = Field( + default_factory=list, + serialization_alias="postadresse.postnummer", + ) + + #: Poststedet til enhetens postadresse + postadresse_poststed: Optional[str] = Field( + default=None, + serialization_alias="postadresse.poststed", + ) + + #: Landkode til enhetens postadresse + postadresse_landkode: CommaList[str] = Field( + default_factory=list, + serialization_alias="postadresse.landkode", + ) + + #: Adresse til enhetens postadresse + postadresse_adresse: Optional[str] = Field( + default=None, + serialization_alias="postadresse.adresse", + ) + + #: Kommunenummer til enhetens forretningsadresse + kommunenummer: CommaList[Kommunenummer] = Field( + default_factory=list, + serialization_alias="kommunenummer", + ) + + #: Postnummeret til enhetens forretningsadresse + forretningsadresse_postnummer: CommaList[Postnummer] = Field( + default_factory=list, + serialization_alias="forretningsadresse.postnummer", + ) + + #: Poststedet til enhetens forretningsadresse + forretningsadresse_poststed: Optional[str] = Field( + default=None, + serialization_alias="forretningsadresse.poststed", + ) + + #: Landkode til enhetens forretningsadresse + forretningsadresse_landkode: CommaList[str] = Field( + default_factory=list, + serialization_alias="forretningsadresse.landkode", + ) + + #: Adresse til enhetens forretningsadresse + forretningsadresse_adresse: Optional[str] = Field( + default=None, + serialization_alias="forretningsadresse.adresse", + ) + + #: Enhetens næringskode + naeringskode: CommaList[Naeringskode] = Field( + default_factory=list, + ) + + #: Årstall for siste innsendte årsregnskap for enheten + siste_innsendte_aarsregnskap: CommaList[str] = Field( + default_factory=list, + serialization_alias="sisteInnsendteAarsregnskap", + ) + + +class UnderenhetQuery(Query): + """The query type for underenhet search.""" + + #: Underenhetens navn + navn: Optional[str] = None + + #: Organisasjonsnummeret til underenheten + organisasjonsnummer: CommaList[Organisasjonsnummer] = Field( + default_factory=list, + ) + + #: Organisasjonsnummeret til underenhetens overordnede enhet + overordnet_enhet: Optional[Organisasjonsnummer] = Field( + default=None, + serialization_alias="overordnetEnhet", + ) + + #: Minste antall ansatte hos underenheten + fra_antall_ansatte: Optional[PositiveInt] = Field( + default=None, + serialization_alias="fraAntallAnsatte", + ) + + #: Største antall ansatte hos underenheten + til_antall_ansatte: Optional[PositiveInt] = Field( + default=None, + serialization_alias="tilAntallAnsatte", + ) + + #: Hvorvidt underenheten er registrert i Mva-registeret + registrert_i_mvaregisteret: Optional[bool] = Field( + default=None, + serialization_alias="registrertIMvaregisteret", + ) + + #: Tidligste registreringsdato i Enhetsregisteret + fra_registreringsdato_enhetsregisteret: Optional[dt.date] = Field( + default=None, + serialization_alias="fraRegistreringsdatoEnhetsregisteret", + ) + + #: Seneste registreringsdato i Enhetsregisteret + til_registreringsdato_enhetsregisteret: Optional[dt.date] = Field( + default=None, + serialization_alias="tilRegistreringsdatoEnhetsregisteret", + ) + + #: Tidligste oppstartsdato for enheten + fra_oppstartsdato: Optional[dt.date] = Field( + default=None, + serialization_alias="fraOppstartsdato", + ) + + #: Seneste oppstartsdato for enheten + til_oppstartsdato: Optional[dt.date] = Field( + default=None, + serialization_alias="tilOppstartsdato", + ) + + #: Tidligste registreringsdato for eierskifte + fra_dato_eierskifte: Optional[dt.date] = Field( + default=None, + serialization_alias="fraDatoEierskifte", + ) + + #: Seneste registreringsdato for eierskifte + til_dato_eierskifte: Optional[dt.date] = Field( + default=None, + serialization_alias="tilDatoEierskifte", + ) + + #: Tidligste nedleggelsesdato for enheten + fra_nedleggelsesdato: Optional[dt.date] = Field( + default=None, + serialization_alias="fraNedleggelsesdato", + ) + + #: Seneste nedleggelsesdato for enheten + til_nedleggelsesdato: Optional[dt.date] = Field( + default=None, + serialization_alias="tilNedleggelsesdato", + ) + + #: Underenhetens organisasjonsform + organisasjonsform: CommaList[str] = Field( + default_factory=list, + ) + + #: Enhetens hjemmeside + hjemmeside: Optional[str] = None + + #: Kommunenummer til underenhetens postadresse + postadresse_kommunenummer: CommaList[Kommunenummer] = Field( + default_factory=list, + serialization_alias="postadresse.kommunenummer", + ) + + #: Postnummeret til underenhetens postadresse + postadresse_postnummer: CommaList[Postnummer] = Field( + default_factory=list, + serialization_alias="postadresse.postnummer", + ) + + #: Poststedet til underenhetens postadresse + postadresse_poststed: Optional[str] = Field( + default=None, + serialization_alias="postadresse.poststed", + ) + + #: Landkode til underenhetens postadresse + postadresse_landkode: CommaList[str] = Field( + default_factory=list, + serialization_alias="postadresse.landkode", + ) + + #: Adresse til underenhetens postadresse + postadresse_adresse: Optional[str] = Field( + default=None, + serialization_alias="postadresse.adresse", + ) + + #: Kommunenummer til enhetens beliggenhetsadresse + kommunenummer: CommaList[Kommunenummer] = Field( + default_factory=list, + serialization_alias="kommunenummer", + ) + + #: Postnummeret til enhetens beliggenhetsadresse + beliggenhetsadresse_postnummer: CommaList[Postnummer] = Field( + default_factory=list, + serialization_alias="beliggenhetsadresse.postnummer", + ) + + #: Poststedet til enhetens beliggenhetsadresse + beliggenhetsadresse_poststed: Optional[str] = Field( + default=None, + serialization_alias="beliggenhetsadresse.poststed", + ) + + #: Landkode til enhetens beliggenhetsadresse + beliggenhetsadresse_landkode: CommaList[str] = Field( + default_factory=list, + serialization_alias="beliggenhetsadresse.landkode", + ) + + #: Adresse til enhetens beliggenhetsadresse + beliggenhetsadresse_adresse: Optional[str] = Field( + default=None, + serialization_alias="beliggenhetsadresse.adresse", + ) + + #: Underenhetens næringskode + naeringskode: CommaList[Naeringskode] = Field( + default_factory=list, + ) diff --git a/src/brreg/enhetsregisteret/_responses.py b/src/brreg/enhetsregisteret/_responses.py new file mode 100644 index 0000000..3013cc3 --- /dev/null +++ b/src/brreg/enhetsregisteret/_responses.py @@ -0,0 +1,266 @@ +from typing import List, Optional + +from pydantic import ( + BaseModel, + ConfigDict, + Field, +) +from pydantic.alias_generators import to_camel + +from brreg.enhetsregisteret._types import DateOrNone + +__all__ = [ + "Adresse", + "Enhet", + "InstitusjonellSektor", + "Naering", + "Organisasjonsform", +] + + +class InstitusjonellSektor(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + #: Sektorkoden + kode: Optional[str] = None + + #: Tekstlig beskrivelse av sektorkoden + beskrivelse: Optional[str] = None + + +class Adresse(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + #: Adresse + adresse: List[Optional[str]] = Field(default_factory=list) + + #: Postnummer + postnummer: Optional[str] = None + + #: Poststed + poststed: Optional[str] = None + + #: Kommunenummer + kommunenummer: Optional[str] = None + + #: Kommune + kommune: Optional[str] = None + + #: Landkode + landkode: Optional[str] = None + + #: Land + land: Optional[str] = None + + +class Naering(BaseModel): + """Næringskode. + + Næringskoden skal vise virksomhetens hovedaktivitet, og den skal primært + dekke statistiske behov for Statistisk sentralbyrå (SSB). + """ + + model_config = ConfigDict(alias_generator=to_camel) + + #: Næringskoden + kode: Optional[str] = None + + #: Tekstlig beskrivelse av næringskoden + beskrivelse: Optional[str] = None + + +class Organisasjonsform(BaseModel): + """Organisasjonsform er virksomhetens formelle organisering. + + Organisasjonsform gir retningslinjer overfor blant annet ansvarsforhold, + skatt, revisjonsplikt, rettigheter og plikter. + """ + + model_config = ConfigDict(alias_generator=to_camel) + + #: Organisasjonsformen + kode: str + + #: Tekstlig beskrivelse av organisasjonsformen + beskrivelse: str + + #: Dato når organisasjonsformen evt. ble ugyldig + utgaatt: DateOrNone = None + + +class Enhet(BaseModel): + """Enhet på øverste nivå i registreringsstrukturen i Enhetsregisteret. + + Eksempelvis enkeltpersonforetak, foreninger, selskap, sameier og andre som + er registrert i Enhetsregisteret. Identifiseres med organisasjonsnummer. + """ + + model_config = ConfigDict(alias_generator=to_camel) + + #: Organisasjonsnummer + organisasjonsnummer: str + + #: Navn + navn: str + + #: Organisasjonsform + organisasjonsform: Organisasjonsform + + #: Hjemmeside + hjemmeside: Optional[str] = None + + #: Enhetens postadresse + postadresse: Optional[Adresse] = None + + #: Registreringsdato i Enhetsregisteret + registreringsdato_enhetsregisteret: DateOrNone = None + + #: Hvorvidt enheten er registrert i MVA-registeret + registrert_i_mvaregisteret: Optional[bool] = None + + #: Enheter som i utgangspunktet ikke er mva-pliktig, kan søke om frivillig + #: registrering i Merverdiavgiftsregisteret + frivillig_mva_registrert_beskrivelser: List[str] = Field(default_factory=list) + + #: Næringskode 1 + naeringskode1: Optional[Naering] = None + + #: Næringskode 2 + naeringskode2: Optional[Naering] = None + + #: Næringskode 3 + naeringskode3: Optional[Naering] = None + + #: Hjelpeenhetskode + hjelpeenhetskode: Optional[Naering] = None + + #: Antall ansatte + antall_ansatte: Optional[int] = None + + #: Angir om enheten har registrert ansatte + har_registrert_antall_ansatte: Optional[bool] = None + + #: Organisasjonsnummeret til overordnet enhet i offentlig sektor + overordnet_enhet: Optional[str] = None + + #: Forretningsadresse + forretningsadresse: Optional[Adresse] = None + + #: Stiftelsesdato + stiftelsesdato: DateOrNone = None + + #: Sektorkode + institusjonell_sektorkode: Optional[InstitusjonellSektor] = None + + #: Hvorvidt enheten er registrert i Foretaksregisteret + registrert_i_foretaksregisteret: Optional[bool] = None + + #: Hvorvidt enheten er registrert i Stiftelsesregisteret + registrert_i_stiftelsesregisteret: Optional[bool] = None + + #: Hvorvidt enheten er registrert i Frivillighetsregisteret + registrert_i_frivillighetsregisteret: Optional[bool] = None + + #: År for siste innsendte årsregnskap + siste_innsendte_aarsregnskap: Optional[int] = None + + #: Hvorvidt enheten er konkurs + konkurs: Optional[bool] = None + + #: Kjennelsesdato for konkursen + konkursdato: DateOrNone = None + + #: Hvorvidt enheten er under avvikling + under_avvikling: Optional[bool] = None + + #: Hvorvidt enheten er under tvangsavvikling eller tvangsoppløsning + under_tvangsavvikling_eller_tvangsopplosning: Optional[bool] = None + + #: Målform + maalform: Optional[str] = None + + #: Enhetens vedtektsdato + vedtektsdato: DateOrNone = None + + #: Enhetens formål + vedtektsfestet_formaal: List[str] = Field(default_factory=list) + + #: Enhetens aktivitet + aktivitet: List[str] = Field(default_factory=list) + + #: Nedleggelsesdato for underenheten + nedleggelsesdato: DateOrNone = None + + #: Dato under-/enheten ble slettet + slettedato: DateOrNone = None + + +class Underenhet(BaseModel): + """Enhet på laveste nivå i registreringsstrukturen i Enhetsregisteret. + + En underenhet kan ikke eksistere alene og har alltid knytning til en + hovedenhet. Identifiseres med organisasjonsnummer. + """ + + model_config = ConfigDict(alias_generator=to_camel) + + #: Underenhetens organisasjonsnummer + organisasjonsnummer: str + + #: Underenhetens navn + navn: str + + #: Underenhetens navn + organisasjonsform: Organisasjonsform + + #: Underenhetens hjemmeside + hjemmeside: Optional[str] = None + + #: Underenhetens postadresse + postadresse: Optional[Adresse] = None + + #: Underenhetens registreringsdato i Enhetsregisteret + registreringsdato_enhetsregisteret: DateOrNone = None + + #: Hvorvidt underenheten er registrert i MVA-registeret + registrert_i_mvaregisteret: Optional[bool] = None + + #: Underenheter som i utgangspunktet ikke er mva-pliktig, kan søke om + #: frivillig registrering i Merverdiavgiftsregisteret + frivillig_mva_registrert_beskrivelser: List[str] = Field(default_factory=list) + + #: Næringskode 1 + naeringskode1: Optional[Naering] = None + + #: Næringskode 2 + naeringskode2: Optional[Naering] = None + + #: Næringskode 3 + naeringskode3: Optional[Naering] = None + + #: Hjelpeenhetskode + hjelpeenhetskode: Optional[Naering] = None + + #: Antall ansatte + antall_ansatte: Optional[int] = None + + #: Angir om enheten har registrert ansatte + har_registrert_antall_ansatte: Optional[bool] = None + + #: Underenhetens overordnede enhet + overordnet_enhet: Optional[str] = None + + #: Underenhetens beliggenhetsadresse + beliggenhetsadresse: Optional[Adresse] = None + + #: Underenhetens oppstartsdato + oppstartsdato: DateOrNone = None + + #: Underenhetens dato for eierskifte + dato_eierskifte: DateOrNone = None + + #: Nedleggelsesdato for underenheten + nedleggelsesdato: DateOrNone = None + + #: Dato under-/enheten ble slettet + slettedato: DateOrNone = None diff --git a/src/brreg/enhetsregisteret/_types.py b/src/brreg/enhetsregisteret/_types.py index b2285be..8fb7d70 100644 --- a/src/brreg/enhetsregisteret/_types.py +++ b/src/brreg/enhetsregisteret/_types.py @@ -1,18 +1,25 @@ import datetime as dt -from typing import List, Optional - -from pydantic import BaseModel, BeforeValidator, ConfigDict, Field -from pydantic.alias_generators import to_camel +from typing import List, Optional, TypeVar + +from pydantic import ( + BeforeValidator, + Field, + PlainSerializer, + TypeAdapter, +) from typing_extensions import Annotated -__all__ = [ - "Adresse", - "Enhet", - "InstitusjonellSektorkode", - "Naeringskode", - "Organisasjonsform", -] +T = TypeVar("T") +# A list that serializes to a comma-separated string. +CommaList = Annotated[ + List[T], + PlainSerializer( + lambda v: ",".join(v), + return_type=str, + when_used="json", + ), +] # Same as `Optional[dt.date]`, except that this version deserializes empty # strings to `None`. @@ -20,251 +27,33 @@ Optional[dt.date], BeforeValidator(lambda v: v if v != "" else None) ] +Kommunenummer = Annotated[ + str, + Field(min_length=4, max_length=4, pattern=r"^\d{4}$"), +] +KommunenummerValidator = TypeAdapter(Kommunenummer) -class InstitusjonellSektorkode(BaseModel): - model_config = ConfigDict(alias_generator=to_camel) - - #: Sektorkoden - kode: Optional[str] = None - - #: Tekstlig beskrivelse av sektorkoden - beskrivelse: Optional[str] = None - - -class Adresse(BaseModel): - model_config = ConfigDict(alias_generator=to_camel) - - #: Adresse - adresse: List[Optional[str]] = Field(default_factory=list) - - #: Postnummer - postnummer: Optional[str] = None - - #: Poststed - poststed: Optional[str] = None - - #: Kommunenummer - kommunenummer: Optional[str] = None - - #: Kommune - kommune: Optional[str] = None - - #: Landkode - landkode: Optional[str] = None - - #: Land - land: Optional[str] = None - - -class Naeringskode(BaseModel): - """Næringskode. - - Organisasjonsform er virksomhetens formelle organisering og gir - retningslinjer overfor blant annet ansvarsforhold, skatt, revisjonsplikt, - rettigheter og plikter. - """ - - model_config = ConfigDict(alias_generator=to_camel) - - #: Næringskoden - kode: Optional[str] = None - - #: Tekstlig beskrivelse av næringskoden - beskrivelse: Optional[str] = None - - -class Organisasjonsform(BaseModel): - """Organisasjonsform er virksomhetens formelle organisering. - - Organisasjonsform gir retningslinjer overfor blant annet ansvarsforhold, - skatt, revisjonsplikt, rettigheter og plikter. - """ - - model_config = ConfigDict(alias_generator=to_camel) - - #: Organisasjonsformen - kode: str - - #: Tekstlig beskrivelse av organisasjonsformen - beskrivelse: str - - #: Dato når organisasjonsformen evt. ble ugyldig - utgaatt: DateOrNone = None - - -class Enhet(BaseModel): - """Enhet på øverste nivå i registreringsstrukturen i Enhetsregisteret. - - Eksempelvis enkeltpersonforetak, foreninger, selskap, sameier og andre som - er registrert i Enhetsregisteret. Identifiseres med organisasjonsnummer. - """ - - model_config = ConfigDict(alias_generator=to_camel) - - #: Organisasjonsnummer - organisasjonsnummer: str - - #: Navn - navn: str - - #: Organisasjonsform - organisasjonsform: Organisasjonsform - - #: Hjemmeside - hjemmeside: Optional[str] = None - - #: Enhetens postadresse - postadresse: Optional[Adresse] = None - - #: Registreringsdato i Enhetsregisteret - registreringsdato_enhetsregisteret: DateOrNone = None - - #: Hvorvidt enheten er registrert i MVA-registeret - registrert_i_mvaregisteret: Optional[bool] = None - - #: Enheter som i utgangspunktet ikke er mva-pliktig, kan søke om frivillig - #: registrering i Merverdiavgiftsregisteret - frivillig_mva_registrert_beskrivelser: List[str] = Field(default_factory=list) - - #: Næringskode 1 - naeringskode1: Optional[Naeringskode] = None - - #: Næringskode 2 - naeringskode2: Optional[Naeringskode] = None - - #: Næringskode 3 - naeringskode3: Optional[Naeringskode] = None - - #: Hjelpeenhetskode - hjelpeenhetskode: Optional[Naeringskode] = None - - #: Antall ansatte - antall_ansatte: Optional[int] = None - - #: Angir om enheten har registrert ansatte - har_registrert_antall_ansatte: Optional[bool] = None - - #: Organisasjonsnummeret til overordnet enhet i offentlig sektor - overordnet_enhet: Optional[str] = None - - #: Forretningsadresse - forretningsadresse: Optional[Adresse] = None - - #: Stiftelsesdato - stiftelsesdato: DateOrNone = None - - #: Sektorkode - institusjonell_sektorkode: Optional[InstitusjonellSektorkode] = None - - #: Hvorvidt enheten er registrert i Foretaksregisteret - registrert_i_foretaksregisteret: Optional[bool] = None - - #: Hvorvidt enheten er registrert i Stiftelsesregisteret - registrert_i_stiftelsesregisteret: Optional[bool] = None - - #: Hvorvidt enheten er registrert i Frivillighetsregisteret - registrert_i_frivillighetsregisteret: Optional[bool] = None - - #: År for siste innsendte årsregnskap - siste_innsendte_aarsregnskap: Optional[int] = None - - #: Hvorvidt enheten er konkurs - konkurs: Optional[bool] = None - - #: Kjennelsesdato for konkursen - konkursdato: DateOrNone = None - - #: Hvorvidt enheten er under avvikling - under_avvikling: Optional[bool] = None - - #: Hvorvidt enheten er under tvangsavvikling eller tvangsoppløsning - under_tvangsavvikling_eller_tvangsopplosning: Optional[bool] = None - - #: Målform - maalform: Optional[str] = None - - #: Enhetens vedtektsdato - vedtektsdato: DateOrNone = None - - #: Enhetens formål - vedtektsfestet_formaal: List[str] = Field(default_factory=list) - - #: Enhetens aktivitet - aktivitet: List[str] = Field(default_factory=list) - - #: Nedleggelsesdato for underenheten - nedleggelsesdato: DateOrNone = None - - #: Dato under-/enheten ble slettet - slettedato: DateOrNone = None - - -class Underenhet(BaseModel): - """Enhet på laveste nivå i registreringsstrukturen i Enhetsregisteret. - - En underenhet kan ikke eksistere alene og har alltid knytning til en - hovedenhet. Identifiseres med organisasjonsnummer. - """ - - model_config = ConfigDict(alias_generator=to_camel) - - #: Underenhetens organisasjonsnummer - organisasjonsnummer: str - - #: Underenhetens navn - navn: str - - #: Underenhetens navn - organisasjonsform: Organisasjonsform - - #: Underenhetens hjemmeside - hjemmeside: Optional[str] = None - - #: Underenhetens postadresse - postadresse: Optional[Adresse] = None - - #: Underenhetens registreringsdato i Enhetsregisteret - registreringsdato_enhetsregisteret: DateOrNone = None - - #: Hvorvidt underenheten er registrert i MVA-registeret - registrert_i_mvaregisteret: Optional[bool] = None - - #: Underenheter som i utgangspunktet ikke er mva-pliktig, kan søke om - #: frivillig registrering i Merverdiavgiftsregisteret - frivillig_mva_registrert_beskrivelser: List[str] = Field(default_factory=list) - - #: Næringskode 1 - naeringskode1: Optional[Naeringskode] = None - - #: Næringskode 2 - naeringskode2: Optional[Naeringskode] = None - - #: Næringskode 3 - naeringskode3: Optional[Naeringskode] = None - - #: Hjelpeenhetskode - hjelpeenhetskode: Optional[Naeringskode] = None - - #: Antall ansatte - antall_ansatte: Optional[int] = None - - #: Angir om enheten har registrert ansatte - har_registrert_antall_ansatte: Optional[bool] = None - - #: Underenhetens overordnede enhet - overordnet_enhet: Optional[str] = None - - #: Underenhetens beliggenhetsadresse - beliggenhetsadresse: Optional[Adresse] = None - - #: Underenhetens oppstartsdato - oppstartsdato: DateOrNone = None +Naeringskode = Annotated[ + str, + Field(min_length=6, max_length=6, pattern=r"^\d{2}\.\d{3}$"), +] +NaeringskodeValidator = TypeAdapter(Naeringskode) - #: Underenhetens dato for eierskifte - dato_eierskifte: DateOrNone = None +Organisasjonsnummer = Annotated[ + str, + BeforeValidator(lambda v: v.replace(" ", "")), + Field(min_length=9, max_length=9, pattern=r"^\d{9}$"), +] +OrganisasjonsnummerValidator = TypeAdapter(Organisasjonsnummer) - #: Nedleggelsesdato for underenheten - nedleggelsesdato: DateOrNone = None +Postnummer = Annotated[ + str, + Field(min_length=4, max_length=4, pattern=r"^\d{4}$"), +] +PostnummerValidator = TypeAdapter(Postnummer) - #: Dato under-/enheten ble slettet - slettedato: DateOrNone = None +Sektorkode = Annotated[ + str, + Field(min_length=4, max_length=4, pattern=r"^\d{4}$"), +] +SektorkodeValidator = TypeAdapter(Sektorkode) diff --git a/tests/data/enheter-search-response.json b/tests/data/enheter-search-response.json new file mode 100644 index 0000000..8b38b26 --- /dev/null +++ b/tests/data/enheter-search-response.json @@ -0,0 +1,77 @@ +{ + "_embedded": { + "enheter": [ + { + "organisasjonsnummer": "112233445", + "navn": "SESAM STASJON", + "organisasjonsform": { + "kode": "AS", + "beskrivelse": "Aksjeselskap", + "_links": { + "self": { + "href": "http://localhost/enhetsregisteret/api/organisasjonsformer/AS" + } + } + }, + "postadresse": { + "land": "Norge", + "landkode": "NO", + "postnummer": "0122", + "poststed": "OSLO", + "adresse": ["c/o reder K. Rusing", "Postboks 1752 Vika", ""], + "kommune": "OSLO", + "kommunenummer": "0301" + }, + "registreringsdatoEnhetsregisteret": "2017-10-20", + "registrertIMvaregisteret": true, + "naeringskode1": { + "kode": "52.292", + "beskrivelse": "Skipsmegling" + }, + "antallAnsatte": 50, + "forretningsadresse": { + "land": "Norge", + "landkode": "NO", + "postnummer": "0101", + "poststed": "OSLO", + "adresse": ["Tyvholmen 1", null, null, ""], + "kommune": "OSLO", + "kommunenummer": "0301" + }, + "stiftelsesdato": "2017-10-20", + "registrertIForetaksregisteret": true, + "registrertIStiftelsesregisteret": false, + "registrertIFrivillighetsregisteret": false, + "konkurs": false, + "underAvvikling": false, + "underTvangsavviklingEllerTvangsopplosning": false, + "maalform": "Bokmål", + "_links": { + "self": { + "href": "http://localhost/enhetsregisteret/api/enheter/112233445" + } + } + } + ] + }, + "_links": { + "first": { + "href": "https://data.brreg.no/enhetsregisteret/api/enheter?navn=Sesam&fraRegistreringsdatoEnhetsregisteret=2017-10-20&tilRegistreringsdatoEnhetsregisteret=2017-10-20&konkurs=false&page=0&size=1" + }, + "self": { + "href": "https://data.brreg.no/enhetsregisteret/api/enheter?navn=Sesam&fraRegistreringsdatoEnhetsregisteret=2017-10-20&tilRegistreringsdatoEnhetsregisteret=2017-10-20&konkurs=false" + }, + "next": { + "href": "https://data.brreg.no/enhetsregisteret/api/enheter?navn=Sesam&fraRegistreringsdatoEnhetsregisteret=2017-10-20&tilRegistreringsdatoEnhetsregisteret=2017-10-20&konkurs=false&page=1&size=1" + }, + "last": { + "href": "https://data.brreg.no/enhetsregisteret/api/enheter?navn=Sesam&fraRegistreringsdatoEnhetsregisteret=2017-10-20&tilRegistreringsdatoEnhetsregisteret=2017-10-20&konkurs=false&page=1&size=1" + } + }, + "page": { + "size": 1, + "totalElements": 1, + "totalPages": 1, + "number": 0 + } +} diff --git a/tests/data/underenheter-search-response.json b/tests/data/underenheter-search-response.json new file mode 100644 index 0000000..e5dd5d7 --- /dev/null +++ b/tests/data/underenheter-search-response.json @@ -0,0 +1,55 @@ +{ + "_embedded": { + "underenheter": [ + { + "organisasjonsnummer": "334455660", + "navn": "TRYLLEBUTIKKEN SESAM", + "organisasjonsform": { + "kode": "BEDR", + "beskrivelse": "Bedrift", + "_links": { + "self": { + "href": "http://localhost/enhetsregisteret/api/organisasjonsformer/BEDR" + } + } + }, + "registreringsdatoEnhetsregisteret": "2017-10-20", + "registrertIMvaregisteret": true, + "naeringskode1": { + "kode": "90.012", + "beskrivelse": "Utøvende kunstnere og underholdsningsvirksomhet innen scenekunst" + }, + "harRegistrertAntallAnsatte": false, + "overordnetEnhet": "443322119", + "beliggenhetsadresse": { + "land": "Norge", + "landkode": "NO", + "postnummer": "2120", + "poststed": "NORD-ODAL", + "adresse": ["Sagstua", null, null], + "kommune": "NORD-ODAL", + "kommunenummer": "0418" + }, + "_links": { + "self": { + "href": "http://localhost/enhetsregisteret/api/underenheter/334455660" + }, + "overordnetEnhet": { + "href": "http://localhost/enhetsregisteret/api/enheter/443322119" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://data.brreg.no/enhetsregisteret/api/underenheter" + } + }, + "page": { + "size": 1, + "totalElements": 1, + "totalPages": 1, + "number": 0 + } +} diff --git a/tests/enhetsregisteret/test_get_enhet.py b/tests/enhetsregisteret/test_get_enhet.py index e7754ca..9edd79c 100644 --- a/tests/enhetsregisteret/test_get_enhet.py +++ b/tests/enhetsregisteret/test_get_enhet.py @@ -27,7 +27,7 @@ def test_get_enhet(httpx_mock: HTTPXMock) -> None: assert org.hjemmeside is None assert org.registreringsdato_enhetsregisteret == date(2017, 10, 20) assert org.registrert_i_mvaregisteret is True - assert org.naeringskode1 == enhetsregisteret.Naeringskode( + assert org.naeringskode1 == enhetsregisteret.Naering( kode="52.292", beskrivelse="Skipsmegling" ) assert org.antall_ansatte == 50 diff --git a/tests/enhetsregisteret/test_get_underenhet.py b/tests/enhetsregisteret/test_get_underenhet.py index dd6fc84..7d4854f 100644 --- a/tests/enhetsregisteret/test_get_underenhet.py +++ b/tests/enhetsregisteret/test_get_underenhet.py @@ -25,7 +25,7 @@ def test_get_underenhet(httpx_mock: HTTPXMock) -> None: assert org.hjemmeside is None assert org.registreringsdato_enhetsregisteret == date(2017, 10, 20) assert org.registrert_i_mvaregisteret is True - assert org.naeringskode1 == enhetsregisteret.Naeringskode( + assert org.naeringskode1 == enhetsregisteret.Naering( kode="52.292", beskrivelse="Skipsmegling" ) assert org.antall_ansatte == 50 diff --git a/tests/enhetsregisteret/test_search_enhet.py b/tests/enhetsregisteret/test_search_enhet.py new file mode 100644 index 0000000..a6f0754 --- /dev/null +++ b/tests/enhetsregisteret/test_search_enhet.py @@ -0,0 +1,93 @@ +from datetime import date +from pathlib import Path + +from pytest_httpx import HTTPXMock + +from brreg import enhetsregisteret + +DATA_DIR = Path(__file__).parent.parent / "data" + + +def test_enhet_query() -> None: + query = enhetsregisteret.EnhetQuery( + navn="Sesam", + konkurs=False, + fra_registreringsdato_enhetsregisteret=date(2017, 10, 20), + til_registreringsdato_enhetsregisteret=date(2017, 10, 20), + ) + + assert query.navn == "Sesam" + assert query.konkurs is False + assert query.fra_registreringsdato_enhetsregisteret == date(2017, 10, 20) + assert query.til_registreringsdato_enhetsregisteret == date(2017, 10, 20) + + assert query.as_url_query() == ( + "navn=Sesam" + "&konkurs=false" + "&fraRegistreringsdatoEnhetsregisteret=2017-10-20" + "&tilRegistreringsdatoEnhetsregisteret=2017-10-20" + ) + + +def test_search_enhet(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType] + method="GET", + url=( + "https://data.brreg.no/enhetsregisteret/api/enheter" + "?navn=Sesam" + "&konkurs=false" + "&fraRegistreringsdatoEnhetsregisteret=2017-10-20" + "&tilRegistreringsdatoEnhetsregisteret=2017-10-20" + ), + status_code=200, + headers={"content-type": "application/json"}, + content=(DATA_DIR / "enheter-search-response.json").read_bytes(), + ) + + page = enhetsregisteret.Client().search_enhet( + enhetsregisteret.EnhetQuery( + navn="Sesam", + fra_registreringsdato_enhetsregisteret=date(2017, 10, 20), + til_registreringsdato_enhetsregisteret=date(2017, 10, 20), + konkurs=False, + ) + ) + + assert page.page_size == 1 + assert page.page_number == 0 + assert page.total_elements == 1 + assert page.total_pages == 1 + + org = page.items[0] + + assert org is not None + assert org.organisasjonsnummer == "112233445" + assert org.navn == "SESAM STASJON" + assert org.hjemmeside is None + assert org.registreringsdato_enhetsregisteret == date(2017, 10, 20) + assert org.registrert_i_mvaregisteret is True + assert org.naeringskode1 == enhetsregisteret.Naering( + kode="52.292", beskrivelse="Skipsmegling" + ) + assert org.antall_ansatte == 50 + assert org.har_registrert_antall_ansatte is None + assert org.forretningsadresse == enhetsregisteret.Adresse( + land="Norge", + landkode="NO", + postnummer="0101", + poststed="OSLO", + adresse=["Tyvholmen 1", None, None, ""], + kommune="OSLO", + kommunenummer="0301", + ) + assert org.stiftelsesdato == date(2017, 10, 20) + assert org.institusjonell_sektorkode is None + assert org.registrert_i_foretaksregisteret is True + assert org.registrert_i_stiftelsesregisteret is False + assert org.registrert_i_frivillighetsregisteret is False + assert org.siste_innsendte_aarsregnskap is None + assert org.konkurs is False + assert org.under_avvikling is False + assert org.under_tvangsavvikling_eller_tvangsopplosning is False + assert org.maalform == "Bokmål" + assert org.slettedato is None diff --git a/tests/enhetsregisteret/test_search_underenhet.py b/tests/enhetsregisteret/test_search_underenhet.py new file mode 100644 index 0000000..b58b73b --- /dev/null +++ b/tests/enhetsregisteret/test_search_underenhet.py @@ -0,0 +1,80 @@ +from datetime import date +from pathlib import Path + +from pytest_httpx import HTTPXMock + +from brreg import enhetsregisteret + +DATA_DIR = Path(__file__).parent.parent / "data" + + +def test_underenhet_query() -> None: + query = enhetsregisteret.UnderenhetQuery( + navn="Sesam", + fra_registreringsdato_enhetsregisteret=date(2015, 1, 1), + naeringskode=["90.012", "90.013"], + ) + + assert query.navn == "Sesam" + assert query.fra_registreringsdato_enhetsregisteret == date(2015, 1, 1) + assert query.naeringskode == ["90.012", "90.013"] + + assert query.as_url_query() == ( + "navn=Sesam" + "&fraRegistreringsdatoEnhetsregisteret=2015-01-01" + "&naeringskode=90.012%2C90.013" + ) + + +def test_search_underenhet(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType] + method="GET", + url=( + "https://data.brreg.no/enhetsregisteret/api/underenheter" + "?navn=Sesam" + "&fraRegistreringsdatoEnhetsregisteret=2015-01-01" + "&naeringskode=90.012" + ), + status_code=200, + headers={"content-type": "application/json"}, + content=(DATA_DIR / "underenheter-search-response.json").read_bytes(), + ) + + page = enhetsregisteret.Client().search_underenhet( + enhetsregisteret.UnderenhetQuery( + navn="Sesam", + fra_registreringsdato_enhetsregisteret=date(2015, 1, 1), + naeringskode=["90.012"], + ) + ) + + assert page.page_size == 1 + assert page.page_number == 0 + assert page.total_elements == 1 + assert page.total_pages == 1 + + org = page.items[0] + + assert org is not None + assert org.organisasjonsnummer == "334455660" + assert org.navn == "TRYLLEBUTIKKEN SESAM" + assert org.hjemmeside is None + assert org.registreringsdato_enhetsregisteret == date(2017, 10, 20) + assert org.registrert_i_mvaregisteret is True + assert org.naeringskode1 == enhetsregisteret.Naering( + kode="90.012", + beskrivelse="Utøvende kunstnere og underholdsningsvirksomhet innen scenekunst", + ) + assert org.antall_ansatte is None + assert org.har_registrert_antall_ansatte is False + assert org.overordnet_enhet == "443322119" + assert org.beliggenhetsadresse == enhetsregisteret.Adresse( + land="Norge", + landkode="NO", + postnummer="2120", + poststed="NORD-ODAL", + adresse=["Sagstua", None, None], + kommune="NORD-ODAL", + kommunenummer="0418", + ) + assert org.slettedato is None diff --git a/tests/enhetsregisteret/test_types.py b/tests/enhetsregisteret/test_types.py new file mode 100644 index 0000000..5b93fec --- /dev/null +++ b/tests/enhetsregisteret/test_types.py @@ -0,0 +1,100 @@ +from typing import Optional + +import pytest + +from brreg.enhetsregisteret import ( + KommunenummerValidator, + OrganisasjonsnummerValidator, + SektorkodeValidator, +) + + +@pytest.mark.parametrize( + ("value", "expected", "error"), + [ + ("1234", "1234", None), + ( + "12345", + None, + "String should have at most 4 characters", + ), + ( + "123", + None, + "String should have at least 4 characters", + ), + ("abcd", None, r"String should match pattern '\^\\d\{4\}\$'"), + ], +) +def test_kommunenummer( + value: str, + expected: Optional[str], + error: Optional[str], +) -> None: + if expected: + assert KommunenummerValidator.validate_python(value) == expected + + if error: + with pytest.raises(ValueError, match=error): + assert KommunenummerValidator.validate_python(value) + + +@pytest.mark.parametrize( + ("value", "expected", "error"), + [ + ("123456789", "123456789", None), + ("123 456 789", "123456789", None), + ( + "1234567890", + None, + "Value should have at most 9 items after validation, not 10", + ), + ( + "12345678", + None, + "Value should have at least 9 items after validation, not 8", + ), + ("aaabbbccc", None, r"String should match pattern '\^\\d\{9\}\$'"), + ], +) +def test_organisasjonsnummer( + value: str, + expected: Optional[str], + error: Optional[str], +) -> None: + if expected: + assert OrganisasjonsnummerValidator.validate_python(value) == expected + + if error: + with pytest.raises(ValueError, match=error): + assert OrganisasjonsnummerValidator.validate_python(value) + + +@pytest.mark.parametrize( + ("value", "expected", "error"), + [ + ("1234", "1234", None), + ( + "12345", + None, + "String should have at most 4 characters", + ), + ( + "123", + None, + "String should have at least 4 characters", + ), + ("abcd", None, r"String should match pattern '\^\\d\{4\}\$'"), + ], +) +def test_sektorkode( + value: str, + expected: Optional[str], + error: Optional[str], +) -> None: + if expected: + assert SektorkodeValidator.validate_python(value) == expected + + if error: + with pytest.raises(ValueError, match=error): + assert SektorkodeValidator.validate_python(value)