brreg-wrapper 0.2.0__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brreg-wrapper
3
- Version: 0.2.0
3
+ Version: 0.2.3
4
4
  Summary: A Python wrapper for the Brønnøysund Register Centre (Brreg) API.
5
5
  Author-email: Sondre Dyrnes <sondre@villdyr.no>
6
6
  License-Expression: MIT
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: httpx<1.0,>=0.27
20
20
  Requires-Dist: pydantic<3.0,>=2.0
21
- Requires-Dist: tenacity<9.0,>=8.2
21
+ Requires-Dist: tenacity<10.0,>=8.2
22
22
  Provides-Extra: dev
23
23
  Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
24
24
  Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == "dev"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brreg-wrapper"
3
- version = "0.2.0"
3
+ version = "0.2.3"
4
4
  description = "A Python wrapper for the Brønnøysund Register Centre (Brreg) API."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -21,7 +21,7 @@ classifiers = [
21
21
  dependencies = [
22
22
  "httpx>=0.27,<1.0",
23
23
  "pydantic>=2.0,<3.0",
24
- "tenacity>=8.2,<9.0", # Added for retry logic
24
+ "tenacity>=8.2,<10.0",
25
25
  ]
26
26
 
27
27
  [project.urls]
@@ -196,6 +196,22 @@ class BrregClient:
196
196
  request_params=request_params,
197
197
  )
198
198
 
199
+ def _log_mapped_http_error(self, exc: httpx.HTTPStatusError) -> None:
200
+ """Log a mapped HTTP error at a level matching its severity.
201
+
202
+ 404 is an expected miss (callers often try enhet, then underenhet),
203
+ so it is logged at DEBUG without exception info. Other HTTP errors
204
+ stay at ERROR with traceback.
205
+ """
206
+ message = (
207
+ f"HTTP error {exc.response.status_code} for {exc.request.url}: "
208
+ f"{exc.response.text}"
209
+ )
210
+ if exc.response.status_code == 404:
211
+ self._logger.debug(message)
212
+ else:
213
+ self._logger.error(message, exc_info=True)
214
+
199
215
  async def _request(
200
216
  self,
201
217
  method: str,
@@ -251,11 +267,7 @@ class BrregClient:
251
267
  return response
252
268
  except httpx.HTTPStatusError as exc:
253
269
  error = self._map_http_error(exc)
254
- self._logger.error(
255
- f"HTTP error {exc.response.status_code} for {exc.request.url}: "
256
- f"{exc.response.text}",
257
- exc_info=True,
258
- )
270
+ self._log_mapped_http_error(exc)
259
271
  raise error
260
272
  except httpx.TimeoutException as exc:
261
273
  self._logger.error(f"Request timed out: {exc}", exc_info=True)
@@ -332,11 +344,7 @@ class BrregClient:
332
344
  return response
333
345
  except httpx.HTTPStatusError as exc:
334
346
  error = self._map_http_error(exc)
335
- self._logger.error(
336
- f"HTTP error {exc.response.status_code} for {exc.request.url}: "
337
- f"{exc.response.text}",
338
- exc_info=True,
339
- )
347
+ self._log_mapped_http_error(exc)
340
348
  raise error
341
349
  except httpx.TimeoutException as exc:
342
350
  self._logger.error(f"Request timed out: {exc}", exc_info=True)
@@ -162,6 +162,23 @@ class LegacyPaategning(BaseModel):
162
162
  )
163
163
 
164
164
 
165
+ class HistoriskNavn(BaseModel):
166
+ model_config = ConfigDict(
167
+ extra="forbid",
168
+ )
169
+ navn: str = Field(..., description="Historisk navn")
170
+ fraDato: str = Field(
171
+ ...,
172
+ description="Dato navnet ble gyldig fra",
173
+ examples=["1995-02-22 22:56:00"],
174
+ )
175
+ tilDato: str = Field(
176
+ ...,
177
+ description="Dato navnet var gyldig til",
178
+ examples=["1996-05-22 19:00:08"],
179
+ )
180
+
181
+
165
182
  class Self1(BaseModel):
166
183
  model_config = ConfigDict(
167
184
  extra="forbid",
@@ -572,6 +589,9 @@ class Enhet(BaseModel):
572
589
  mobil: Optional[str] = Field(
573
590
  None, description="Enhetens mobilnummer", examples=["91504800"]
574
591
  )
592
+ historiskeNavn: Optional[Sequence[HistoriskNavn]] = Field(
593
+ None, description="Enhetens historiske navn"
594
+ )
575
595
  field_links: Optional[FieldLinks2] = Field(None, alias="_links")
576
596
 
577
597
 
@@ -1243,6 +1263,9 @@ class Underenhet(BaseModel):
1243
1263
  mobil: Optional[str] = Field(
1244
1264
  None, description="Underenhetens mobilnummer", examples=["91504800"]
1245
1265
  )
1266
+ historiskeNavn: Optional[Sequence[HistoriskNavn]] = Field(
1267
+ None, description="Underenhetens historiske navn"
1268
+ )
1246
1269
  field_links: Optional[FieldLinks13] = Field(None, alias="_links")
1247
1270
 
1248
1271
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brreg-wrapper
3
- Version: 0.2.0
3
+ Version: 0.2.3
4
4
  Summary: A Python wrapper for the Brønnøysund Register Centre (Brreg) API.
5
5
  Author-email: Sondre Dyrnes <sondre@villdyr.no>
6
6
  License-Expression: MIT
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: httpx<1.0,>=0.27
20
20
  Requires-Dist: pydantic<3.0,>=2.0
21
- Requires-Dist: tenacity<9.0,>=8.2
21
+ Requires-Dist: tenacity<10.0,>=8.2
22
22
  Provides-Extra: dev
23
23
  Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
24
24
  Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == "dev"
@@ -1,6 +1,6 @@
1
1
  httpx<1.0,>=0.27
2
2
  pydantic<3.0,>=2.0
3
- tenacity<9.0,>=8.2
3
+ tenacity<10.0,>=8.2
4
4
 
5
5
  [all]
6
6
  h2<5.0,>=4.0
@@ -24,6 +24,7 @@ from brreg_wrapper.models import (
24
24
  Kommuner1, # Added import
25
25
  Page,
26
26
  SlettetEnhet,
27
+ Underenhet,
27
28
  )
28
29
 
29
30
 
@@ -289,7 +290,9 @@ async def test_async_context_manager():
289
290
 
290
291
 
291
292
  @pytest.mark.asyncio
292
- async def test_get_enhet_not_found(httpx_mock: HTTPXMock):
293
+ async def test_get_enhet_not_found(
294
+ httpx_mock: HTTPXMock, caplog: pytest.LogCaptureFixture
295
+ ):
293
296
  """Test handling of a 404 Not Found error when retrieving an entity."""
294
297
  org_nr = "123456789"
295
298
  expected_url = f"{BrregClient.BASE_URL}/enheter/{org_nr}"
@@ -303,10 +306,16 @@ async def test_get_enhet_not_found(httpx_mock: HTTPXMock):
303
306
 
304
307
  client = BrregClient()
305
308
  try:
306
- with pytest.raises(httpx.HTTPStatusError) as exc_info:
307
- await client.get_enhet(org_nr)
309
+ with caplog.at_level("DEBUG", logger="brreg_wrapper.client"):
310
+ with pytest.raises(BrregResourceNotFoundError) as exc_info:
311
+ await client.get_enhet(org_nr)
308
312
 
309
- assert exc_info.value.response.status_code == 404
313
+ assert exc_info.value.status_code == 404
314
+ assert not any(record.levelname == "ERROR" for record in caplog.records)
315
+ assert any(
316
+ record.levelname == "DEBUG" and "HTTP error 404" in record.message
317
+ for record in caplog.records
318
+ )
310
319
 
311
320
  # Verify the request was made
312
321
  request = httpx_mock.get_request()
@@ -316,6 +325,36 @@ async def test_get_enhet_not_found(httpx_mock: HTTPXMock):
316
325
  await client.close()
317
326
 
318
327
 
328
+ @pytest.mark.asyncio
329
+ async def test_get_enhet_server_error_logs_at_error(
330
+ httpx_mock: HTTPXMock, caplog: pytest.LogCaptureFixture
331
+ ):
332
+ """5xx responses should still be logged at ERROR with exception info."""
333
+ org_nr = "123456789"
334
+ expected_url = f"{BrregClient.BASE_URL}/enheter/{org_nr}"
335
+
336
+ httpx_mock.add_response(
337
+ url=expected_url,
338
+ method="GET",
339
+ status_code=500,
340
+ json={"message": "Internal Server Error"},
341
+ )
342
+
343
+ client = BrregClient(max_retries=0)
344
+ try:
345
+ with caplog.at_level("DEBUG", logger="brreg_wrapper.client"):
346
+ with pytest.raises(BrregServerError) as exc_info:
347
+ await client.get_enhet(org_nr)
348
+
349
+ assert exc_info.value.status_code == 500
350
+ error_records = [r for r in caplog.records if r.levelname == "ERROR"]
351
+ assert error_records
352
+ assert any("HTTP error 500" in record.message for record in error_records)
353
+ assert any(record.exc_info is not None for record in error_records)
354
+ finally:
355
+ await client.close()
356
+
357
+
319
358
  @pytest.mark.asyncio
320
359
  async def test_get_enhet_deleted(httpx_mock: HTTPXMock):
321
360
  """Test retrieving a deleted entity."""
@@ -683,3 +722,50 @@ async def test_response_json_property():
683
722
  # Test with no response text
684
723
  error = BrregAPIError(message="Test error", status_code=400)
685
724
  assert error.response_json is None
725
+
726
+
727
+ @pytest.mark.asyncio
728
+ async def test_get_underenhet_with_historiske_navn(httpx_mock: HTTPXMock):
729
+ """Underenhet responses may include historiskeNavn from the live API."""
730
+ org_nr = "971917407"
731
+ mock_response_data = {
732
+ "organisasjonsnummer": org_nr,
733
+ "navn": "BETONMAST INNLANDET AS",
734
+ "organisasjonsform": {
735
+ "kode": "BEDR",
736
+ "beskrivelse": "Underenhet til næringsdrivende og offentlig forvaltning",
737
+ "_links": {
738
+ "self": {"href": f"{BrregClient.BASE_URL}/organisasjonsformer/BEDR"}
739
+ },
740
+ },
741
+ "historiskeNavn": [
742
+ {
743
+ "navn": "TOTEN BYGG OG ANLEGG AS",
744
+ "fraDato": "1996-05-22 19:00:08",
745
+ "tilDato": "2003-01-27 20:36:38",
746
+ }
747
+ ],
748
+ "registrertIMvaregisteret": False,
749
+ "registreringsdatoEnhetsregisteret": "1995-02-22",
750
+ "harRegistrertAntallAnsatte": True,
751
+ "_links": {"self": {"href": f"{BrregClient.BASE_URL}/underenheter/{org_nr}"}},
752
+ }
753
+ expected_url = f"{BrregClient.BASE_URL}/underenheter/{org_nr}"
754
+
755
+ httpx_mock.add_response(
756
+ url=expected_url,
757
+ method="GET",
758
+ json=mock_response_data,
759
+ status_code=200,
760
+ headers={"Content-Type": "application/json"},
761
+ )
762
+
763
+ client = BrregClient()
764
+ try:
765
+ underenhet = await client.get_underenhet(org_nr)
766
+ assert isinstance(underenhet, Underenhet)
767
+ assert underenhet.navn == "BETONMAST INNLANDET AS"
768
+ assert underenhet.historiskeNavn is not None
769
+ assert underenhet.historiskeNavn[0].navn == "TOTEN BYGG OG ANLEGG AS"
770
+ finally:
771
+ await client.close()
File without changes
File without changes
File without changes