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

Switch from attrs to pydantic #9

Merged
merged 3 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ classifiers = [

[tool.poetry.dependencies]
python = ">= 3.8.0"
attrs = ">= 22.2"
httpx = ">= 0.24"
pydantic = ">= 2"

[tool.poetry.group.dev.dependencies]
nox = "^2023.4.22"
Expand Down
2 changes: 1 addition & 1 deletion src/brreg/enhetsregisteret/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get_enhet(self, organisasjonsnummer: str) -> Optional[Enhet]:
if res.status_code in (404, 410):
return None
res.raise_for_status()
return Enhet.from_json(res.json())
return Enhet.model_validate_json(res.content)
except httpx.HTTPError as exc:
raise BrregRestError(
str(exc),
Expand Down
254 changes: 93 additions & 161 deletions src/brreg/enhetsregisteret/_types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import datetime as dt
from typing import Any, Dict, List, Optional
from typing import List, Optional

import attr
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel

__all__ = [
"Adresse",
Expand All @@ -12,115 +13,89 @@
]


@attr.s(auto_attribs=True)
class InstitusjonellSektorkode:
class InstitusjonellSektorkode(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)

#: Sektorkoden
kode: str
kode: Optional[str] = None

#: Tekstlig beskrivelse av sektorkoden
beskrivelse: str
beskrivelse: Optional[str] = None


def __str__(self) -> str:
return f"{self.beskrivelse} ({self.kode})"
class Adresse(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)

@classmethod
def from_json(
cls,
json: Optional[Dict[str, Any]],
) -> Optional["InstitusjonellSektorkode"]:
if not json:
return None
#: Adresse
adresse: List[Optional[str]] = Field(default_factory=list)

return cls(kode=json["kode"], beskrivelse=json["beskrivelse"])
#: Postnummer
postnummer: Optional[str] = None

#: Poststed
poststed: Optional[str] = None

@attr.s(auto_attribs=True)
class Adresse:
#: Land
land: str
#: Kommunenummer
kommunenummer: Optional[str] = None

#: Kommune
kommune: Optional[str] = None

#: Landkode
landkode: str
landkode: Optional[str] = None

#: Postnummer
postnummer: str
#: Land
land: Optional[str] = None

#: Poststed
poststed: str

#: Adresse
adresse: List[Optional[str]]
class Naeringskode(BaseModel):
"""Næringskode.

#: Kommune
kommune: str
Organisasjonsform er virksomhetens formelle organisering og gir
retningslinjer overfor blant annet ansvarsforhold, skatt, revisjonsplikt,
rettigheter og plikter.
"""

model_config = ConfigDict(alias_generator=to_camel)

#: Kommunenummer
kommunenummer: str

def __str__(self) -> str:
return ", ".join(line for line in self.adresse if line)

@classmethod
def from_json(
cls,
json: Optional[Dict[str, Any]],
) -> Optional["Adresse"]:
if not json:
return None

return cls(
land=json["land"],
landkode=json["landkode"],
postnummer=json["postnummer"],
poststed=json["poststed"],
adresse=json["adresse"],
kommune=json["kommune"],
kommunenummer=json["kommunenummer"],
)


@attr.s(auto_attribs=True)
class Naeringskode:
#: Næringskoden
kode: str
kode: Optional[str] = None

#: Tekstlig beskrivelse av næringskoden
beskrivelse: str
beskrivelse: Optional[str] = None

def __str__(self) -> str:
return f"{self.beskrivelse} ({self.kode})"
#: Beskriver om dette er en hjelpeenhetskode
hjelpeenhetskode: Optional[bool] = None

@classmethod
def from_json(
cls,
json: Optional[Dict[str, Any]],
) -> Optional["Naeringskode"]:
if not json:
return None
return cls(kode=json["kode"], beskrivelse=json["beskrivelse"])

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)

@attr.s(auto_attribs=True)
class Organisasjonsform:
#: Organisasjonsformen
kode: str

#: Tekstlig beskrivelse av organisasjonsformen
beskrivelse: str

def __str__(self) -> str:
return f"{self.beskrivelse} ({self.kode})"
#: Dato når organisasjonsformen evt. ble ugyldig
utgaatt: Optional[dt.date] = None


class Enhet(BaseModel):
"""Enhet på øverste nivå i registreringsstrukturen i Enhetsregisteret.

@classmethod
def from_json(
cls,
json: Dict[str, Any],
) -> "Organisasjonsform":
return cls(kode=json["kode"], beskrivelse=json["beskrivelse"])
Eksempelvis enkeltpersonforetak, foreninger, selskap, sameier og andre som
er registrert i Enhetsregisteret. Identifiseres med organisasjonsnummer.
"""

model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)

@attr.s(auto_attribs=True)
class Enhet:
#: Organisasjonsnummer
organisasjonsnummer: str

Expand All @@ -131,114 +106,71 @@ class Enhet:
organisasjonsform: Organisasjonsform

#: Hjemmeside
hjemmeside: Optional[str]
hjemmeside: Optional[str] = None

#: Enhetens postadresse
postadresse: Optional[Adresse] = None

#: Registreringsdato i Enhetsregisteret
registreringsdato_enhetsregisteret: Optional[dt.date]
registreringsdato_enhetsregisteret: Optional[dt.date] = None

#: Hvorvidt enheten er registrert i MVA-registeret
registrert_i_mvaregisteret: Optional[bool]
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]
naeringskode1: Optional[Naeringskode] = None

#: Næringskode 2
naeringskode2: Optional[Naeringskode] = None

#: Næringskode 3
naeringskode3: Optional[Naeringskode] = None

#: Antall ansatte
antall_ansatte: Optional[int]
antall_ansatte: Optional[int] = None

#: Organisasjonsnummeret til overordnet enhet i offentlig sektor
overordnet_enhet: Optional[str] = None

#: Forretningsadresse
forretningsadresse: Optional[Adresse]
forretningsadresse: Optional[Adresse] = None

#: Stiftelsesdato
stiftelsesdato: Optional[dt.date]
stiftelsesdato: Optional[dt.date] = None

#: Sektorkode
institusjonell_sektorkode: Optional[InstitusjonellSektorkode]
institusjonell_sektorkode: Optional[InstitusjonellSektorkode] = None

#: Hvorvidt enheten er registrert i Foretaksregisteret
registrert_i_foretaksregisteret: Optional[bool]
registrert_i_foretaksregisteret: Optional[bool] = None

#: Hvorvidt enheten er registrert i Stiftelsesregisteret
registrert_i_stiftelsesregisteret: Optional[bool]
registrert_i_stiftelsesregisteret: Optional[bool] = None

#: Hvorvidt enheten er registrert i Frivillighetsregisteret
registrert_i_frivillighetsregisteret: Optional[bool]
registrert_i_frivillighetsregisteret: Optional[bool] = None

#: År for siste innsendte årsregnskap
siste_innsendte_aarsregnskap: Optional[int]
siste_innsendte_aarsregnskap: Optional[int] = None

#: Hvorvidt enheten er konkurs
konkurs: Optional[bool]
konkurs: Optional[bool] = None

#: Hvorvidt enheten er under avvikling
under_avvikling: Optional[bool]
under_avvikling: Optional[bool] = None

#: Hvorvidt enheten er under tvangsavvikling eller tvangsoppløsning
under_tvangsavvikling_eller_tvangsopplosning: Optional[bool]
under_tvangsavvikling_eller_tvangsopplosning: Optional[bool] = None

#: Målform
maalform: Optional[str]

#: Dato enheten ble slettet
slettedato: Optional[dt.date]

def __str__(self) -> str:
return f"{self.navn} ({self.organisasjonsnummer})"

@classmethod
def from_json(
cls,
json: Dict[str, Any],
) -> Optional["Enhet"]:
if not json:
return None

return cls(
organisasjonsnummer=json["organisasjonsnummer"],
navn=json["navn"],
organisasjonsform=Organisasjonsform.from_json(json["organisasjonsform"]),
hjemmeside=json.get("hjemmeside"),
registreringsdato_enhetsregisteret=parse_date(
json.get("registreringsdatoEnhetsregisteret")
),
registrert_i_mvaregisteret=json.get("registrertIMvaregisteret"),
naeringskode1=Naeringskode.from_json(json.get("naeringskode1")),
antall_ansatte=json.get("antallAnsatte"),
forretningsadresse=Adresse.from_json(json.get("forretningsadresse")),
stiftelsesdato=parse_date(json.get("stiftelsesdato")),
institusjonell_sektorkode=InstitusjonellSektorkode.from_json(
json.get("institusjonellSektorkode")
),
registrert_i_foretaksregisteret=json.get("registrertIForetaksregisteret"),
registrert_i_stiftelsesregisteret=json.get(
"registrertIStiftelsesregisteret"
),
registrert_i_frivillighetsregisteret=json.get(
"registrertIFrivillighetsregisteret"
),
siste_innsendte_aarsregnskap=parse_int(
json.get("sisteInnsendteAarsregnskap")
),
konkurs=json.get("konkurs"),
under_avvikling=json.get("underAvvikling"),
under_tvangsavvikling_eller_tvangsopplosning=json.get(
"underTvangsavviklingEllerTvangsopplosning"
),
maalform=json.get("maalform"),
slettedato=parse_date(json.get("slettedato")),
)


def parse_date(date_string: Optional[str]) -> Optional[dt.date]:
if date_string is None:
return None
return (
dt.datetime.strptime(date_string, "%Y-%m-%d")
.replace(tzinfo=dt.timezone.utc)
.date()
)


def parse_int(int_string: Optional[str]) -> Optional[int]:
if int_string is None:
return None
return int(int_string)
maalform: Optional[str] = None

#: Nedleggelsesdato for underenheten
nedleggelsesdato: Optional[dt.date] = None

#: Dato under-/enheten ble slettet
slettedato: Optional[dt.date] = None
4 changes: 3 additions & 1 deletion tests/test_enhetsregisteret.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ def test_get_enhet_when_deleted(
assert org.organisasjonsnummer == "815597222"
assert org.navn == "SLETTET ENHET AS"
assert org.organisasjonsform == enhetsregisteret.Organisasjonsform(
kode="UTBG", beskrivelse="Frivillig registrert utleiebygg"
kode="UTBG",
beskrivelse="Frivillig registrert utleiebygg",
utgaatt=date(2017, 7, 17),
)
assert org.slettedato == date(2017, 10, 20)

Expand Down