eba-xbridge 1.5.0rc5__py3-none-any.whl → 1.5.0rc6__py3-none-any.whl

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: eba-xbridge
3
- Version: 1.5.0rc5
3
+ Version: 1.5.0rc6
4
4
  Summary: XBRL-XML to XBRL-CSV converter for EBA Taxonomy (version 4.1)
5
5
  License: Apache 2.0
6
6
  License-File: LICENSE
@@ -154,6 +154,55 @@ Customize the conversion with additional parameters:
154
154
  strict_validation=False, # Emit warnings instead of errors for orphaned facts
155
155
  )
156
156
 
157
+ Python API - Handling Warnings
158
+ ------------------------------
159
+
160
+ XBridge emits structured warnings that can be filtered or turned into errors from your code.
161
+ The most common ones are:
162
+
163
+ * ``IdentifierPrefixWarning``: Unknown entity identifier prefix; XBridge falls back to ``rs``.
164
+ * ``FilingIndicatorWarning``: Filing indicator inconsistencies; some facts are excluded.
165
+
166
+ To capture these warnings when using ``convert_instance``:
167
+
168
+ .. code-block:: python
169
+
170
+ import warnings
171
+ from xbridge.api import convert_instance
172
+ from xbridge.exceptions import XbridgeWarning, FilingIndicatorWarning
173
+
174
+ input_path = "path/to/instance.xbrl"
175
+ output_path = "path/to/output"
176
+
177
+ with warnings.catch_warnings(record=True) as caught:
178
+ # Ensure all xbridge warnings are captured
179
+ warnings.simplefilter("always", XbridgeWarning)
180
+
181
+ zip_path = convert_instance(
182
+ instance_path=input_path,
183
+ output_path=output_path,
184
+ validate_filing_indicators=True,
185
+ strict_validation=False, # Warnings instead of errors for orphaned facts
186
+ )
187
+
188
+ filing_warnings = [
189
+ w for w in caught if issubclass(w.category, FilingIndicatorWarning)
190
+ ]
191
+ for w in filing_warnings:
192
+ print(f"Filing indicator warning: {w.message}")
193
+
194
+ To treat all XBridge warnings as errors:
195
+
196
+ .. code-block:: python
197
+
198
+ import warnings
199
+ from xbridge.api import convert_instance
200
+ from xbridge.exceptions import XbridgeWarning
201
+
202
+ with warnings.catch_warnings():
203
+ warnings.simplefilter("error", XbridgeWarning)
204
+ convert_instance("path/to/instance.xbrl", "path/to/output")
205
+
157
206
  Loading an Instance
158
207
  -------------------
159
208
 
@@ -1,8 +1,9 @@
1
- xbridge/__init__.py,sha256=S6yAyrnTJ19Trbh-OrGVDIwi5lh88C2pCxDncQyX1VY,68
1
+ xbridge/__init__.py,sha256=OJUYuCUHGkgWxo9tS6dntxUteSpYkfqo5Weaad1X56M,68
2
2
  xbridge/__main__.py,sha256=trtFEv7TRJgrLL84leIapPvgC_iVTj05qLHRRS1Olts,2219
3
3
  xbridge/api.py,sha256=NCBz7VRJWE3gID6ndgL4Awoxw0w1yMIIf_OTLRuZyyQ,1559
4
- xbridge/converter.py,sha256=vWlIaqQ2eA8Y5b1jqnqTIcOxeubJEu1itggMrFxYnS8,24077
5
- xbridge/instance.py,sha256=GA5D2p3IobCp5W4-x1dibgOQFuy_W4R8LmZnQ4H900g,32292
4
+ xbridge/converter.py,sha256=BgmJY06yuB9aLVhcf8OvVZphbQYP3w66RKi3ViZ3LDA,24571
5
+ xbridge/exceptions.py,sha256=C9fjO8nUGVL376qiyQ1vAozafCWP4OfIbuujpXtNPeQ,1002
6
+ xbridge/instance.py,sha256=8cCGKxgtmdP9-1c2Nz4TdhsXw5dtRrykwp9rAXD4-Ew,32863
6
7
  xbridge/modules/ae_ae_4.2.json,sha256=AdFvwZqX0KVP3jF1iHeQc5QSnSMvvT3GvoA2G1AgXis,460165
7
8
  xbridge/modules/ae_con_cir-680-2014_2017-04-04.json,sha256=4n0t9dKJNU8Nb5QHpssrDs8ZLwzI-Mw75ax-ar9pLu0,363273
8
9
  xbridge/modules/ae_con_cir-680-2014_2018-03-31.json,sha256=aVWeLLs20p39kQQUthUzqrxBGKTycqhgX9WLk1rVlNw,363538
@@ -383,8 +384,8 @@ xbridge/modules/sepa_ipr_pay_4.2.json,sha256=JLJvR02LOAJy6SWPRuhV1TT02oXQhsG83FB
383
384
  xbridge/modules.py,sha256=bTvBXtp3w4Gad2DpEQE7Hb-UfuUQLlRl8gywRstQtpU,22399
384
385
  xbridge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
385
386
  xbridge/taxonomy_loader.py,sha256=K0lnJVryvkKsaoK3fMis-L2JpmwLO6z3Ruq3yj9FxDY,9317
386
- eba_xbridge-1.5.0rc5.dist-info/METADATA,sha256=88P20ZbVDK8cMGG0-rs1W7OihOdBrmOxujMvoeB3RQM,10430
387
- eba_xbridge-1.5.0rc5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
388
- eba_xbridge-1.5.0rc5.dist-info/entry_points.txt,sha256=FATct4icSewM04cegjhybtm7xcQWhaSahL-DTtuFdZw,49
389
- eba_xbridge-1.5.0rc5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
390
- eba_xbridge-1.5.0rc5.dist-info/RECORD,,
387
+ eba_xbridge-1.5.0rc6.dist-info/METADATA,sha256=1lyxX7bu8C3L0XVF1yEjEUMSRiyIgEL-ncJFr41uzw8,12053
388
+ eba_xbridge-1.5.0rc6.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
389
+ eba_xbridge-1.5.0rc6.dist-info/entry_points.txt,sha256=FATct4icSewM04cegjhybtm7xcQWhaSahL-DTtuFdZw,49
390
+ eba_xbridge-1.5.0rc6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
391
+ eba_xbridge-1.5.0rc6.dist-info/RECORD,,
xbridge/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
  Init file for eba-xbridge library
3
3
  """
4
4
 
5
- __version__ = "1.5.0rc5"
5
+ __version__ = "1.5.0rc6"
xbridge/converter.py CHANGED
@@ -15,6 +15,7 @@ from zipfile import ZipFile
15
15
 
16
16
  import pandas as pd
17
17
 
18
+ from xbridge.exceptions import DecimalValueError, FilingIndicatorWarning
18
19
  from xbridge.instance import CsvInstance, Instance, XmlInstance
19
20
  from xbridge.modules import Module, Table
20
21
 
@@ -376,31 +377,23 @@ class Converter:
376
377
 
377
378
  data_type = row["data_type"][1:]
378
379
  decimals = row["decimals"]
380
+ normalized_decimals = self._normalize_decimals_value(decimals)
379
381
 
380
382
  if data_type not in self._decimals_parameters:
381
- self._decimals_parameters[data_type] = (
382
- int(decimals) if decimals not in {"INF", "#none"} else decimals
383
- )
383
+ self._decimals_parameters[data_type] = normalized_decimals
384
384
  else:
385
- # If new value is a special value, skip it (prefer numeric values)
386
- if decimals in {"INF", "#none"}:
387
- pass
388
- # If new value is numeric
389
- else:
390
- try:
391
- decimals = int(decimals)
392
- except ValueError:
393
- raise ValueError(
394
- f"Invalid decimals value: {decimals}, "
395
- "should be integer, 'INF' or '#none'"
396
- )
397
-
398
- # If existing value is special, replace with numeric
399
- if (
400
- self._decimals_parameters[data_type] in {"INF", "#none"}
401
- or decimals < self._decimals_parameters[data_type]
402
- ):
403
- self._decimals_parameters[data_type] = decimals
385
+ # Skip special values when we already have an entry,
386
+ # as numeric values take precedence.
387
+ if normalized_decimals in {"INF", "#none"}:
388
+ continue
389
+
390
+ existing_value = self._decimals_parameters[data_type]
391
+ if existing_value in {"INF", "#none"} or (
392
+ isinstance(existing_value, int)
393
+ and isinstance(normalized_decimals, int)
394
+ and normalized_decimals < existing_value
395
+ ):
396
+ self._decimals_parameters[data_type] = normalized_decimals
404
397
 
405
398
  drop_columns = merge_cols + ["data_type", "decimals"]
406
399
  else:
@@ -422,6 +415,23 @@ class Converter:
422
415
 
423
416
  return table_df
424
417
 
418
+ def _normalize_decimals_value(self, decimals: Any) -> Union[int, str]:
419
+ """Return a validated decimals value or raise a DecimalValueError."""
420
+ candidate = decimals
421
+ if isinstance(candidate, str):
422
+ candidate = candidate.strip()
423
+
424
+ if candidate in {"INF", "#none"}:
425
+ return candidate
426
+
427
+ try:
428
+ return int(candidate)
429
+ except (TypeError, ValueError) as exc:
430
+ raise DecimalValueError(
431
+ f"Invalid decimals value: {decimals}, should be integer, 'INF' or '#none'",
432
+ offending_value=decimals,
433
+ ) from exc
434
+
425
435
  def _convert_tables(
426
436
  self,
427
437
  temp_dir_path: Path,
@@ -557,7 +567,11 @@ class Converter:
557
567
  "Consider setting filed=true for the relevant tables "
558
568
  "or removing these facts from the XML."
559
569
  )
560
- warnings.warn(error_msg)
570
+ warnings.warn(
571
+ error_msg,
572
+ category=FilingIndicatorWarning,
573
+ stacklevel=2,
574
+ )
561
575
 
562
576
  def _convert_parameters(self, temp_dir_path: Path) -> None:
563
577
  # Workaround;
xbridge/exceptions.py ADDED
@@ -0,0 +1,33 @@
1
+ """Custom exception types for the xbridge package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+
8
+ class SchemaRefValueError(ValueError):
9
+ """Raised when schemaRef validation fails in an XBRL instance."""
10
+
11
+ def __init__(self, error_message: str, offending_value: Optional[Any] = None) -> None:
12
+ super().__init__(error_message)
13
+ self.offending_value = offending_value
14
+
15
+
16
+ class DecimalValueError(ValueError):
17
+ """Raised when decimals metadata contains unsupported values."""
18
+
19
+ def __init__(self, error_message: str, offending_value: Optional[Any] = None) -> None:
20
+ super().__init__(error_message)
21
+ self.offending_value = offending_value
22
+
23
+
24
+ class XbridgeWarning(Warning):
25
+ """Base warning for the xbridge library."""
26
+
27
+
28
+ class IdentifierPrefixWarning(XbridgeWarning):
29
+ """Unknown identifier prefix; defaulting to 'rs'."""
30
+
31
+
32
+ class FilingIndicatorWarning(XbridgeWarning):
33
+ """Facts orphaned by filing indicators; some are excluded."""
xbridge/instance.py CHANGED
@@ -13,6 +13,8 @@ from zipfile import ZipFile
13
13
  import pandas as pd
14
14
  from lxml import etree
15
15
 
16
+ from xbridge.exceptions import IdentifierPrefixWarning, SchemaRefValueError
17
+
16
18
  # Cache namespace → CSV prefix derivations to avoid repeated string work during parse
17
19
  _namespace_prefix_cache: Dict[str, str] = {}
18
20
 
@@ -110,9 +112,7 @@ def _normalize_namespaced_value(
110
112
  return value
111
113
 
112
114
 
113
- def _normalize_metric_value(
114
- value: Optional[str], nsmap: Dict[Optional[str], str]
115
- ) -> Optional[str]:
115
+ def _normalize_metric_value(value: Optional[str], nsmap: Dict[Optional[str], str]) -> Optional[str]:
116
116
  """
117
117
  Normalize a metric namespaced value to the CSV prefix convention.
118
118
  For metrics, we preserve version suffixes (e.g., eba_met_3.5, eba_met_4.0).
@@ -181,6 +181,7 @@ class Instance:
181
181
  self._contexts: Optional[Dict[str, Context]] = None
182
182
  self._facts: Optional[List[Fact]] = None
183
183
  self._facts_list_dict: Optional[List[Dict[str, Any]]] = None
184
+ self._df: Optional[pd.DataFrame] = None
184
185
  self._table_files: Optional[set[Path]] = None
185
186
  self._root_folder: Optional[str] = None
186
187
  self._report_file: Optional[Path] = None
@@ -270,7 +271,7 @@ class Instance:
270
271
  """
271
272
  if self.facts_list_dict is None:
272
273
  return
273
- df = pd.DataFrame.from_dict(self.facts_list_dict) # type: ignore[call-overload]
274
+ df = pd.DataFrame(self.facts_list_dict)
274
275
  df_columns = list(df.columns)
275
276
  ##Workaround
276
277
  # Dropping period an entity columns because in current EBA architecture,
@@ -296,7 +297,9 @@ class Instance:
296
297
  (
297
298
  f"{self._identifier_prefix} is not a known identifier prefix. "
298
299
  "Default 'rs' will be used."
299
- )
300
+ ),
301
+ category=IdentifierPrefixWarning,
302
+ stacklevel=2,
300
303
  )
301
304
  return "rs"
302
305
 
@@ -354,16 +357,17 @@ class Instance:
354
357
  raise AttributeError("XML root not loaded.")
355
358
 
356
359
  contexts: Dict[str, Context] = {}
360
+ namespaces: Dict[str, str] = {key or "": value for key, value in self.namespaces.items()}
357
361
  for context in self.root.findall(
358
362
  "{http://www.xbrl.org/2003/instance}context",
359
- self.namespaces, # type: ignore[arg-type]
363
+ namespaces,
360
364
  ):
361
365
  context_object = Context(context)
362
366
  contexts[context_object.id] = context_object
363
367
 
364
368
  self._contexts = contexts
365
369
 
366
- first_ctx = self.root.find("{http://www.xbrl.org/2003/instance}context", self.namespaces) # type: ignore[arg-type]
370
+ first_ctx = self.root.find("{http://www.xbrl.org/2003/instance}context", namespaces)
367
371
  if first_ctx is not None:
368
372
  entity_elem = first_ctx.find("{http://www.xbrl.org/2003/instance}entity")
369
373
  if entity_elem is not None:
@@ -406,7 +410,8 @@ class Instance:
406
410
  href_attr = "{http://www.w3.org/1999/xlink}href"
407
411
  if href_attr not in child.attrib:
408
412
  continue
409
- value: str = child.attrib[href_attr] # type: ignore[assignment]
413
+ raw_value = child.attrib[href_attr]
414
+ value = str(raw_value)
410
415
  schema_refs.append(value)
411
416
 
412
417
  # Validate that only one schemaRef exists
@@ -414,11 +419,14 @@ class Instance:
414
419
  return # No schema reference found, module_ref will remain None
415
420
 
416
421
  if len(schema_refs) > 1:
417
- raise ValueError(
418
- f"Multiple schemaRef elements found in the XBRL instance. "
419
- f"Only one schemaRef is expected, but {len(schema_refs)} "
420
- f"were found: {schema_refs}. "
421
- f"This may indicate an invalid XBRL-XML file."
422
+ raise SchemaRefValueError(
423
+ (
424
+ "Multiple schemaRef elements found in the XBRL instance. "
425
+ f"Only one schemaRef is expected, but {len(schema_refs)} "
426
+ f"were found: {schema_refs}. "
427
+ "This may indicate an invalid XBRL-XML file."
428
+ ),
429
+ offending_value=schema_refs,
422
430
  )
423
431
 
424
432
  # Process the single schema reference
@@ -427,25 +435,34 @@ class Instance:
427
435
 
428
436
  # Validate href format and extract module code
429
437
  if "/mod/" not in value:
430
- raise ValueError(
431
- f"Invalid href format in schemaRef. Expected href to contain '/mod/' "
432
- f"but got: '{value}'. Please verify the XBRL-XML file contains a "
433
- f"valid schema reference."
438
+ raise SchemaRefValueError(
439
+ (
440
+ "Invalid href format in schemaRef. Expected href to contain '/mod/' "
441
+ f"but got: '{value}'. Please verify the XBRL-XML file contains a "
442
+ "valid schema reference."
443
+ ),
444
+ offending_value=value,
434
445
  )
435
446
 
436
447
  split_parts = value.split("/mod/")
437
448
  if len(split_parts) < 2:
438
- raise ValueError(
439
- f"Invalid href format in schemaRef. Could not extract module name from: '{value}'. "
440
- f"Expected format: 'http://.../mod/[module_name].xsd'"
449
+ raise SchemaRefValueError(
450
+ (
451
+ "Invalid href format in schemaRef. Could not extract module name "
452
+ f"from: '{value}'. Expected format: 'http://.../mod/[module_name].xsd'"
453
+ ),
454
+ offending_value=value,
441
455
  )
442
456
 
443
457
  module_part = split_parts[1]
444
458
  if ".xsd" not in module_part:
445
- raise ValueError(
446
- f"Invalid href format in schemaRef. Expected href to end with '.xsd' "
447
- f"but got: '{value}'. Please verify the XBRL-XML file contains a valid "
448
- f"schema reference."
459
+ raise SchemaRefValueError(
460
+ (
461
+ "Invalid href format in schemaRef. Expected href to end with '.xsd' "
462
+ f"but got: '{value}'. Please verify the XBRL-XML file contains a valid "
463
+ "schema reference."
464
+ ),
465
+ offending_value=value,
449
466
  )
450
467
 
451
468
  xsd_split = module_part.split(".xsd")
@@ -485,7 +502,8 @@ class Instance:
485
502
 
486
503
  units: Dict[str, str] = {}
487
504
  for unit in self.root.findall("{http://www.xbrl.org/2003/instance}unit"):
488
- unit_name: str = unit.attrib["id"] # type: ignore[assignment]
505
+ unit_id = unit.attrib["id"]
506
+ unit_name = str(unit_id)
489
507
  measure = unit.find("{http://www.xbrl.org/2003/instance}measure")
490
508
  if measure is None or measure.text is None:
491
509
  continue
@@ -739,7 +757,7 @@ class Context:
739
757
 
740
758
  def parse(self) -> None:
741
759
  """Parses the XML node with the :obj:`Context <xbridge.xml_instance.Context>`."""
742
- self._id = self.context_xml.attrib["id"] # type: ignore[assignment]
760
+ self._id = str(self.context_xml.attrib["id"])
743
761
 
744
762
  entity_elem = self.context_xml.find("{http://www.xbrl.org/2003/instance}entity")
745
763
  if entity_elem is not None: