Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lookup of underenhet by org number #11

Merged
merged 4 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ The following examples takes you through typical use cases with the
:mod:`brreg` library.


Organization details by organization number
===========================================
Querying Enhetsregisteret
=========================

To get details about an organization ("enhet") given its organization number:
To query Enhetsregisteret, you need to create a :class:`brreg.enhetsregisteret.Client` instance:

>>> from brreg.enhetsregisteret import Client
>>> client = Client()
>>>

The client instance will ensure that the HTTP connection is reused across requests.

To get details about an organization ("enhet") given its organization number:

>>> enhet = client.get_enhet('915501680')
>>> enhet.organisasjonsnummer
'915501680'
Expand All @@ -21,5 +27,16 @@ To get details about an organization ("enhet") given its organization number:
>>> enhet.organisasjonsform
Organisasjonsform(kode='ASA', beskrivelse='Allmennaksjeselskap')
>>> enhet.forretningsadresse
Adresse(land='Norge', landkode='NO', postnummer='0181', poststed='OSLO', adresse=['Torggata 7'], kommune='OSLO', kommunenummer='0301')
Adresse(adresse=['Torggata 7'], postnummer='0181', poststed='OSLO', kommunenummer='0301', kommune='OSLO', landkode='NO', land='Norge')
>>>

To get details of a suborganization ("underenhet") given its organization number:

>>> underenhet = client.get_underenhet('915659683')
>>> underenhet.organisasjonsnummer
'915659683'
>>> underenhet.antall_ansatte
91
>>> underenhet.beliggenhetsadresse
Adresse(adresse=['Torggata 7'], postnummer='0181', poststed='OSLO', kommunenummer='0301', kommune='OSLO', landkode='NO', land='Norge')
>>>
2 changes: 2 additions & 0 deletions src/brreg/enhetsregisteret/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
InstitusjonellSektorkode,
Naeringskode,
Organisasjonsform,
Underenhet,
)

__all__ = [
Expand All @@ -21,4 +22,5 @@
"InstitusjonellSektorkode",
"Naeringskode",
"Organisasjonsform",
"Underenhet",
]
59 changes: 38 additions & 21 deletions src/brreg/enhetsregisteret/_client.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Optional
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, Generator, Optional

import httpx

from brreg import BrregError, BrregRestError
from brreg.enhetsregisteret._types import Enhet
from brreg.enhetsregisteret._types import Enhet, Underenhet

if TYPE_CHECKING:
from types import TracebackType


@contextmanager
def error_handler() -> Generator[None, Any, None]:
try:
yield
except httpx.HTTPError as exc:
response: httpx.Response | None = getattr(exc, "response", None)
raise BrregRestError(
str(exc),
method=(exc.request.method if exc.request else None),
url=(str(exc.request.url) if exc.request else None),
status_code=(response.status_code if response else None),
) from exc
except Exception as exc:
raise BrregError(exc) from exc


class Client:
_client: httpx.Client

Expand All @@ -37,16 +54,8 @@ def close(self) -> None:
self._client.close()

def get_enhet(self, organisasjonsnummer: str) -> Optional[Enhet]:
"""Get :class:`Enhet` given an organization number.

Returns :class:`None` if Enhet is gone or not found.
Returns :class:`Enhet` if Enhet is found.

Raises :class:`BrregRestError` if a REST error occurs.
Raises :class:`BrregError` if an unhandled exception occurs.
"""
res: Optional[httpx.Response] = None
try:
"""Get :class:`Enhet` given an organization number."""
with error_handler():
res = self._client.get(
f"/enheter/{organisasjonsnummer}",
headers={
Expand All @@ -57,12 +66,20 @@ def get_enhet(self, organisasjonsnummer: str) -> Optional[Enhet]:
return None
res.raise_for_status()
return Enhet.model_validate_json(res.content)
except httpx.HTTPError as exc:
raise BrregRestError(
str(exc),
method=(exc.request.method if exc.request else None),
url=(str(exc.request.url) if exc.request else None),
status_code=(res.status_code if res else None),
) from exc
except Exception as exc:
raise BrregError(exc) from exc

def get_underenhet(self, organisasjonsnummer: str) -> Optional[Underenhet]:
"""Get :class:`Underenhet` given an organization number."""
with error_handler():
res = self._client.get(
f"/underenheter/{organisasjonsnummer}",
headers={
"accept": (
"application/vnd.brreg.enhetsregisteret.underenhet.v2+json;"
"charset=UTF-8"
)
},
)
if res.status_code in (404, 410):
return None
res.raise_for_status()
return Underenhet.model_validate_json(res.content)
71 changes: 71 additions & 0 deletions src/brreg/enhetsregisteret/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,74 @@ class Enhet(BaseModel):

#: 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

#: Underenhetens dato for eierskifte
dato_eierskifte: DateOrNone = None

#: Nedleggelsesdato for underenheten
nedleggelsesdato: DateOrNone = None

#: Dato under-/enheten ble slettet
slettedato: DateOrNone = None
17 changes: 0 additions & 17 deletions tests/conftest.py

This file was deleted.

20 changes: 20 additions & 0 deletions tests/data/underenheter-details-deleted-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"organisasjonsnummer": "987123456",
"navn": "SLETTET UNDERENHET AS",
"organisasjonsform": {
"kode": "AAFY",
"beskrivelse": "Virksomhet til ikke-næringsdrivende person",
"_links": {
"self": {
"href": "http://localhost/enhetsregisteret/api/organisasjonsformer/AAFY"
}
}
},
"slettedato": "2017-10-20",
"nedleggelsesdato": "2017-10-05",
"_links": {
"self": {
"href": "http://localhost/enhetsregisteret/api/underenheter/987123456"
}
}
}
49 changes: 49 additions & 0 deletions tests/data/underenheter-details-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"organisasjonsnummer": "776655441",
"navn": "SESAM STASJON",
"organisasjonsform": {
"kode": "BEDR",
"beskrivelse": "Bedrift",
"_links": {
"self": {
"href": "http://localhost/enhetsregisteret/api/organisasjonsformer/BEDR"
}
}
},
"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,
"harRegistrertAntallAnsatte": true,
"overordnetEnhet": "112233445",
"beliggenhetsadresse": {
"land": "Norge",
"landkode": "NO",
"postnummer": "0122",
"poststed": "OSLO",
"adresse": ["Tyvholmen 1", null, null],
"kommune": "OSLO",
"kommunenummer": "0301"
},
"nedleggelsesdato": "2018-10-20",
"_links": {
"self": {
"href": "http://localhost/enhetsregisteret/api/underenheter/776655441"
},
"overordnetEnhet": {
"href": "http://localhost/enhetsregisteret/api/enheter/112233445"
}
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
from datetime import date
from pathlib import Path

import httpx
import pytest
from pytest_httpx import HTTPXMock

from brreg import BrregRestError, enhetsregisteret

DATA_DIR = Path(__file__).parent.parent / "data"

def test_get_enhet(
httpx_mock: HTTPXMock,
organization_details_response: bytes,
) -> None:

def test_get_enhet(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType]
method="GET",
url="https://data.brreg.no/enhetsregisteret/api/enheter/112233445",
status_code=200,
headers={"content-type": "application/json"},
content=organization_details_response,
content=(DATA_DIR / "enheter-details-response.json").read_bytes(),
)

org = enhetsregisteret.Client().get_enhet("112233445")
Expand Down Expand Up @@ -54,16 +54,13 @@ def test_get_enhet(
assert org.slettedato is None


def test_get_enhet_when_deleted(
httpx_mock: HTTPXMock,
deleted_organization_details_response: bytes,
) -> None:
def test_get_enhet_when_deleted(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType]
method="GET",
url="https://data.brreg.no/enhetsregisteret/api/enheter/123456789",
status_code=200,
headers={"content-type": "application/json"},
content=deleted_organization_details_response,
content=(DATA_DIR / "enheter-details-deleted-response.json").read_bytes(),
)

org = enhetsregisteret.Client().get_enhet("123456789")
Expand All @@ -79,9 +76,7 @@ def test_get_enhet_when_deleted(
assert org.slettedato == date(2017, 10, 20)


def test_get_enhet_when_gone(
httpx_mock: HTTPXMock,
) -> None:
def test_get_enhet_when_gone(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType]
method="GET",
url="https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
Expand All @@ -94,9 +89,7 @@ def test_get_enhet_when_gone(
assert org is None


def test_get_enhet_when_not_found(
httpx_mock: HTTPXMock,
) -> None:
def test_get_enhet_when_not_found(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType]
method="GET",
url="https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
Expand All @@ -109,9 +102,7 @@ def test_get_enhet_when_not_found(
assert org is None


def test_get_enhet_when_http_error(
httpx_mock: HTTPXMock,
) -> None:
def test_get_enhet_when_http_error(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType]
method="GET",
url="https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
Expand All @@ -133,9 +124,7 @@ def test_get_enhet_when_http_error(
assert exc_info.value.status_code == 400


def test_get_organization_by_number_when_http_timeout(
httpx_mock: HTTPXMock,
) -> None:
def test_get_organization_by_number_when_http_timeout(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_exception( # pyright: ignore[reportUnknownMemberType]
httpx.ConnectTimeout("Connection refused"),
)
Expand Down
Loading