Skip to content

Commit

Permalink
Merge pull request #8 from jodal/httpx
Browse files Browse the repository at this point in the history
Switch from requests to httpx
  • Loading branch information
jodal authored Nov 15, 2023
2 parents 4328ed8 + d010da9 commit 9fcb437
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 85 deletions.
8 changes: 5 additions & 3 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ API reference
Exceptions
==========

.. autoclass:: BrregException
.. autoclass:: BrregError
:members:
:undoc-members:


.. autoclass:: BrregRestException
.. autoclass:: BrregRestError
:members:
:undoc-members:

Expand All @@ -22,7 +22,9 @@ Enhetsregisteret

.. automodule:: brreg.enhetsregisteret

.. autofunction:: brreg.enhetsregisteret.get_enhet
.. autoclass:: brreg.enhetsregisteret.Client
:members:
:undoc-members:

.. autoclass:: brreg.enhetsregisteret.Enhet
:members:
Expand Down
5 changes: 3 additions & 2 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ Organization details by organization number

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

>>> from brreg import enhetsregisteret
>>> enhet = enhetsregisteret.get_enhet('915501680')
>>> from brreg.enhetsregisteret import Client
>>> client = Client()
>>> enhet = client.get_enhet('915501680')
>>> enhet.organisasjonsnummer
'915501680'
>>> enhet.navn
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ classifiers = [
[tool.poetry.dependencies]
python = ">= 3.8.0"
attrs = ">= 22.2"
requests = ">= 2.28.1"
httpx = ">= 0.24"

[tool.poetry.group.dev.dependencies]
nox = "^2023.4.22"
Expand All @@ -41,7 +41,7 @@ ruff = "^0.1.4"
coverage = { extras = ["toml"], version = "^7.3.2" }
pytest = "^7.4.2"
pytest-cov = "^4.1.0"
responses = "^0.24.0"
pytest-httpx = "0.22.0"

[tool.poetry.group.typing.dependencies]
types-requests = "^2.31.0.10"
Expand Down
4 changes: 2 additions & 2 deletions src/brreg/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def __init__(
*,
method: Optional[str],
url: Optional[str],
status: Optional[int],
status_code: Optional[int],
) -> None:
super().__init__(f"REST API exception: {msg}")
self.method = method
self.url = url
self.status = status
self.status_code = status_code
4 changes: 2 additions & 2 deletions src/brreg/enhetsregisteret/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
See https://data.brreg.no/enhetsregisteret/api/docs/index.html for API details.
"""

from brreg.enhetsregisteret._client import get_enhet
from brreg.enhetsregisteret._client import Client
from brreg.enhetsregisteret._types import (
Adresse,
Enhet,
Expand All @@ -14,7 +14,7 @@

__all__ = [
# From _client module:
"get_enhet",
"Client",
# From _types module:
"Adresse",
"Enhet",
Expand Down
79 changes: 53 additions & 26 deletions src/brreg/enhetsregisteret/_client.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,65 @@
from typing import Optional
from __future__ import annotations

import requests
from typing import TYPE_CHECKING, Optional

import httpx

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

if TYPE_CHECKING:
from types import TracebackType


from ._types import Enhet
class Client:
client: httpx.Client

BASE_URL = "https://data.brreg.no/enhetsregisteret/api"
def __new__(cls) -> Client:
self = super().__new__(cls)
self.open()
return self

def __enter__(self) -> Client:
return self

def get_enhet(organisasjonsnummer: str) -> Optional[Enhet]:
"""Get :class:`Enhet` given an organization number.
def __exit__(
self,
exc_type: type[BaseException] | None = None,
exc_value: BaseException | None = None,
traceback: TracebackType | None = None,
) -> None:
self.close()

Returns :class:`None` if Enhet is gone or not found
Returns :class:`Enhet` if Enhet is found
def open(self) -> None:
self.client = httpx.Client(
base_url="https://data.brreg.no/enhetsregisteret/api",
)

Raises :class:`BrregRestException` if a REST exception occures
Raises :class:`BrregException` if an unhandled exception occures
"""
try:
res = requests.get(f"{BASE_URL}/enheter/{organisasjonsnummer}")
def close(self) -> None:
self.client.close()

if res.status_code in (404, 410):
return None
def get_enhet(self, organisasjonsnummer: str) -> Optional[Enhet]:
"""Get :class:`Enhet` given an organization number.
res.raise_for_status()
Returns :class:`None` if Enhet is gone or not found.
Returns :class:`Enhet` if Enhet is found.
return Enhet.from_json(res.json())
except requests.RequestException as exc:
raise BrregRestError(
str(exc),
method=(exc.request.method if exc.request else None),
url=(exc.request.url if exc.request else None),
status=getattr(exc.response, "status_code", None),
) from exc
except Exception as exc:
raise BrregError(exc) from exc
Raises :class:`BrregRestError` if a REST error occurs.
Raises :class:`BrregError` if an unhandled exception occurs.
"""
res: Optional[httpx.Response] = None
try:
res = self.client.get(f"/enheter/{organisasjonsnummer}")
if res.status_code in (404, 410):
return None
res.raise_for_status()
return Enhet.from_json(res.json())
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
109 changes: 61 additions & 48 deletions tests/test_enhetsregisteret.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from datetime import date

import httpx
import pytest
import responses
from pytest_httpx import HTTPXMock

from brreg import BrregRestError, enhetsregisteret


@responses.activate
def test_get_enhet(organization_details_response: bytes) -> None:
responses.add(
responses.GET,
"https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
body=organization_details_response,
status=200,
content_type="application/json",
def test_get_enhet(
httpx_mock: HTTPXMock,
organization_details_response: bytes,
) -> None:
httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType]
method="GET",
url="https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
status_code=200,
headers={"content-type": "application/json"},
content=organization_details_response,
)

org = enhetsregisteret.get_enhet("818511752")
org = enhetsregisteret.Client().get_enhet("818511752")

assert org is not None
assert org.organisasjonsnummer == "818511752"
Expand Down Expand Up @@ -50,17 +53,19 @@ def test_get_enhet(organization_details_response: bytes) -> None:
assert org.slettedato is None


@responses.activate
def test_get_enhet_when_deleted(deleted_organization_details_response: bytes) -> None:
responses.add(
responses.GET,
"https://data.brreg.no/enhetsregisteret/api/enheter/815597222",
body=deleted_organization_details_response,
status=200,
content_type="application/json",
def test_get_enhet_when_deleted(
httpx_mock: HTTPXMock,
deleted_organization_details_response: bytes,
) -> None:
httpx_mock.add_response( # pyright: ignore[reportUnknownMemberType]
method="GET",
url="https://data.brreg.no/enhetsregisteret/api/enheter/815597222",
status_code=200,
headers={"content-type": "application/json"},
content=deleted_organization_details_response,
)

org = enhetsregisteret.get_enhet("815597222")
org = enhetsregisteret.Client().get_enhet("815597222")

assert org is not None
assert org.organisasjonsnummer == "815597222"
Expand All @@ -71,45 +76,48 @@ def test_get_enhet_when_deleted(deleted_organization_details_response: bytes) ->
assert org.slettedato == date(2017, 10, 20)


@responses.activate
def test_get_enhet_when_gone() -> None:
responses.add(
responses.GET,
"https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
status=410,
content_type="application/json",
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",
status_code=410,
headers={"content-type": "application/json"},
)

org = enhetsregisteret.get_enhet("818511752")
org = enhetsregisteret.Client().get_enhet("818511752")

assert org is None


@responses.activate
def test_get_enhet_when_not_found() -> None:
responses.add(
responses.GET,
"https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
status=404,
content_type="application/json",
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",
status_code=404,
headers={"content-type": "application/json"},
)

org = enhetsregisteret.get_enhet("818511752")
org = enhetsregisteret.Client().get_enhet("818511752")

assert org is None


@responses.activate
def test_get_enhet_when_http_error() -> None:
responses.add(
responses.GET,
"https://data.brreg.no/enhetsregisteret/api/enheter/818511752",
status=400,
content_type="application/json",
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",
status_code=400,
headers={"content-type": "application/json"},
)

with pytest.raises(BrregRestError) as exc_info:
enhetsregisteret.get_enhet("818511752")
enhetsregisteret.Client().get_enhet("818511752")

assert "REST API exception" in str(exc_info.value)
assert "Bad Request" in str(exc_info.value)
Expand All @@ -119,13 +127,18 @@ def test_get_enhet_when_http_error() -> None:
exc_info.value.url
== "https://data.brreg.no/enhetsregisteret/api/enheter/818511752"
)
assert exc_info.value.status == 400
assert exc_info.value.status_code == 400


@responses.activate
def test_get_organization_by_number_when_http_timeout() -> 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"),
)

with pytest.raises(BrregRestError) as exc_info:
enhetsregisteret.get_enhet("818511752")
enhetsregisteret.Client().get_enhet("818511752")

assert "REST API exception" in str(exc_info.value)
assert "Connection refused" in str(exc_info.value)
Expand All @@ -135,4 +148,4 @@ def test_get_organization_by_number_when_http_timeout() -> None:
exc_info.value.url
== "https://data.brreg.no/enhetsregisteret/api/enheter/818511752"
)
assert exc_info.value.status is None
assert exc_info.value.status_code is None

0 comments on commit 9fcb437

Please sign in to comment.