arelle-release 2.37.21__py3-none-any.whl → 2.37.22__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.

Potentially problematic release.


This version of arelle-release might be problematic. Click here for more details.

arelle/XbrlConst.py CHANGED
@@ -110,6 +110,8 @@ qnXbrldiExplicitMember = qname("{http://xbrl.org/2006/xbrldi}xbrldi:explicitMemb
110
110
  qnXbrldiTypedMember = qname("{http://xbrl.org/2006/xbrldi}xbrldi:typedMember")
111
111
  xlink = "http://www.w3.org/1999/xlink"
112
112
  qnXlinkArcRole = qname("{http://www.w3.org/1999/xlink}xlink:arcrole")
113
+ qnXlinkFrom = qname("{http://www.w3.org/1999/xlink}xlink:from")
114
+ qnXlinkType = qname("{http://www.w3.org/1999/xlink}xlink:type")
113
115
  xl = "http://www.xbrl.org/2003/XLink"
114
116
  qnXlExtended = qname("{http://www.xbrl.org/2003/XLink}xl:extended")
115
117
  qnXlLocator = qname("{http://www.xbrl.org/2003/XLink}xl:locator")
arelle/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.37.21'
21
- __version_tuple__ = version_tuple = (2, 37, 21)
20
+ __version__ = version = '2.37.22'
21
+ __version_tuple__ = version_tuple = (2, 37, 22)
arelle/api/Session.py CHANGED
@@ -6,6 +6,7 @@ The `arelle.api` module is the supported method for integrating Arelle into othe
6
6
  from __future__ import annotations
7
7
 
8
8
  import logging
9
+ import threading
9
10
  from types import TracebackType
10
11
  from typing import Any, BinaryIO
11
12
 
@@ -14,10 +15,35 @@ from arelle.CntlrCmdLine import CntlrCmdLine, createCntlrAndPreloadPlugins
14
15
  from arelle.ModelXbrl import ModelXbrl
15
16
  from arelle.RuntimeOptions import RuntimeOptions
16
17
 
18
+ _session_lock = threading.Lock()
19
+
17
20
 
18
21
  class Session:
22
+ """
23
+ CRITICAL THREAD SAFETY WARNING:
24
+
25
+ Arelle uses shared global state (PackageManager, PluginManager) which is NOT thread-safe.
26
+ Only ONE Session can run at a time across the entire process.
27
+
28
+ Safe usage:
29
+ - Use one Session at a time per process
30
+ - Use a process pool instead of thread pool for parallelism
31
+
32
+ Unsafe usage:
33
+ - Running multiple Sessions concurrently in any threads
34
+ - Threading.Thread with Session.run()
35
+ """
36
+
19
37
  def __init__(self) -> None:
20
38
  self._cntlr: CntlrCmdLine | None = None
39
+ self._thread_id = threading.get_ident()
40
+
41
+ def _check_thread(self) -> None:
42
+ """Ensure session is only used from the thread that created it."""
43
+ if threading.get_ident() != self._thread_id:
44
+ raise RuntimeError(
45
+ "Session objects cannot be shared between threads. Create a new Session instance in each thread."
46
+ )
21
47
 
22
48
  def __enter__(self) -> Any:
23
49
  return self
@@ -31,9 +57,11 @@ class Session:
31
57
  self.close()
32
58
 
33
59
  def close(self) -> None:
34
- if self._cntlr is not None:
35
- self._cntlr.close()
36
- PluginManager.close()
60
+ with _session_lock:
61
+ self._check_thread()
62
+ if self._cntlr is not None:
63
+ self._cntlr.close()
64
+ PluginManager.close()
37
65
 
38
66
  def get_log_messages(self) -> list[dict[str, Any]]:
39
67
  """
@@ -89,60 +117,62 @@ class Session:
89
117
  :param sourceZipStreamFileName: Optional file name to use for the passed zip stream.
90
118
  :return: True if the run was successful, False otherwise.
91
119
  """
92
- if sourceZipStreamFileName is not None and sourceZipStream is None:
93
- raise ValueError("sourceZipStreamFileName may only be provided if sourceZipStream is not None.")
94
- PackageManager.reset()
95
- PluginManager.reset()
96
- if self._cntlr is None:
97
- # Certain options must be passed into the controller constructor to have the intended effect
98
- self._cntlr = createCntlrAndPreloadPlugins(
99
- uiLang=options.uiLang,
100
- disablePersistentConfig=options.disablePersistentConfig,
101
- arellePluginModules={},
102
- )
103
- else:
104
- # Certain options passed into the controller constructor need to be updated
105
- if self._cntlr.uiLang != options.uiLang:
106
- self._cntlr.setUiLanguage(options.uiLang)
107
- self._cntlr.disablePersistentConfig = options.disablePersistentConfig or False
108
- logRefObjectProperties = True
109
- if options.logRefObjectProperties is not None:
110
- logRefObjectProperties = options.logRefObjectProperties
111
- if options.webserver:
112
- assert sourceZipStream is None, "Source streaming is not supported with webserver"
113
- assert responseZipStream is None, "Response streaming is not supported with webserver"
114
- if not self._cntlr.logger:
115
- self._cntlr.startLogging(
116
- logFileName='logToBuffer',
117
- logFilters=logFilters,
118
- logHandler=logHandler,
119
- logTextMaxLength=options.logTextMaxLength,
120
- logRefObjectProperties=logRefObjectProperties,
121
- logPropagate=options.logPropagate,
120
+ with _session_lock:
121
+ self._check_thread()
122
+ if sourceZipStreamFileName is not None and sourceZipStream is None:
123
+ raise ValueError("sourceZipStreamFileName may only be provided if sourceZipStream is not None.")
124
+ PackageManager.reset()
125
+ PluginManager.reset()
126
+ if self._cntlr is None:
127
+ # Certain options must be passed into the controller constructor to have the intended effect
128
+ self._cntlr = createCntlrAndPreloadPlugins(
129
+ uiLang=options.uiLang,
130
+ disablePersistentConfig=options.disablePersistentConfig,
131
+ arellePluginModules={},
122
132
  )
123
- self._cntlr.postLoggingInit()
124
- from arelle import CntlrWebMain
125
- CntlrWebMain.startWebserver(self._cntlr, options)
126
- return True
127
- else:
128
- if not self._cntlr.logger:
129
- self._cntlr.startLogging(
130
- logFileName=(options.logFile or "logToPrint"),
131
- logFileMode=options.logFileMode,
132
- logFormat=(options.logFormat or "[%(messageCode)s] %(message)s - %(file)s"),
133
- logLevel=(options.logLevel or "DEBUG"),
134
- logFilters=logFilters,
135
- logHandler=logHandler,
136
- logToBuffer=options.logFile == 'logToBuffer',
137
- logTextMaxLength=options.logTextMaxLength, # e.g., used by EDGAR/render to require buffered logging
138
- logRefObjectProperties=logRefObjectProperties,
139
- logXmlMaxAttributeLength=options.logXmlMaxAttributeLength,
140
- logPropagate=options.logPropagate,
133
+ else:
134
+ # Certain options passed into the controller constructor need to be updated
135
+ if self._cntlr.uiLang != options.uiLang:
136
+ self._cntlr.setUiLanguage(options.uiLang)
137
+ self._cntlr.disablePersistentConfig = options.disablePersistentConfig or False
138
+ logRefObjectProperties = True
139
+ if options.logRefObjectProperties is not None:
140
+ logRefObjectProperties = options.logRefObjectProperties
141
+ if options.webserver:
142
+ assert sourceZipStream is None, "Source streaming is not supported with webserver"
143
+ assert responseZipStream is None, "Response streaming is not supported with webserver"
144
+ if not self._cntlr.logger:
145
+ self._cntlr.startLogging(
146
+ logFileName='logToBuffer',
147
+ logFilters=logFilters,
148
+ logHandler=logHandler,
149
+ logTextMaxLength=options.logTextMaxLength,
150
+ logRefObjectProperties=logRefObjectProperties,
151
+ logPropagate=options.logPropagate,
152
+ )
153
+ self._cntlr.postLoggingInit()
154
+ from arelle import CntlrWebMain
155
+ CntlrWebMain.startWebserver(self._cntlr, options)
156
+ return True
157
+ else:
158
+ if not self._cntlr.logger:
159
+ self._cntlr.startLogging(
160
+ logFileName=(options.logFile or "logToPrint"),
161
+ logFileMode=options.logFileMode,
162
+ logFormat=(options.logFormat or "[%(messageCode)s] %(message)s - %(file)s"),
163
+ logLevel=(options.logLevel or "DEBUG"),
164
+ logFilters=logFilters,
165
+ logHandler=logHandler,
166
+ logToBuffer=options.logFile == 'logToBuffer',
167
+ logTextMaxLength=options.logTextMaxLength, # e.g., used by EDGAR/render to require buffered logging
168
+ logRefObjectProperties=logRefObjectProperties,
169
+ logXmlMaxAttributeLength=options.logXmlMaxAttributeLength,
170
+ logPropagate=options.logPropagate,
171
+ )
172
+ self._cntlr.postLoggingInit() # Cntlr options after logging is started
173
+ return self._cntlr.run(
174
+ options,
175
+ sourceZipStream=sourceZipStream,
176
+ responseZipStream=responseZipStream,
177
+ sourceZipStreamFileName=sourceZipStreamFileName,
141
178
  )
142
- self._cntlr.postLoggingInit() # Cntlr options after logging is started
143
- return self._cntlr.run(
144
- options,
145
- sourceZipStream=sourceZipStream,
146
- responseZipStream=responseZipStream,
147
- sourceZipStreamFileName=sourceZipStreamFileName,
148
- )
@@ -3,3 +3,17 @@ DISCLOSURE_SYSTEM_NT17 = 'NT17'
3
3
  DISCLOSURE_SYSTEM_NT18 = 'NT18'
4
4
  DISCLOSURE_SYSTEM_NT19 = 'NT19'
5
5
  DISCLOSURE_SYSTEM_NL_INLINE_2024 = 'NL-INLINE-2024'
6
+ DISCLOSURE_SYSTEM_NL_INLINE_2024_GAAP_OTHER = 'NL-INLINE-2024-GAAP-OTHER'
7
+
8
+ ALL_NL_INLINE_DISCLOSURE_SYSTEMS = [
9
+ DISCLOSURE_SYSTEM_NL_INLINE_2024,
10
+ DISCLOSURE_SYSTEM_NL_INLINE_2024_GAAP_OTHER,
11
+ ]
12
+
13
+ NL_INLINE_GAAP_IFRS_DISCLOSURE_SYSTEMS = [
14
+ DISCLOSURE_SYSTEM_NL_INLINE_2024,
15
+ ]
16
+
17
+ NL_INLINE_GAAP_OTHER_DISCLOSURE_SYSTEMS = [
18
+ DISCLOSURE_SYSTEM_NL_INLINE_2024_GAAP_OTHER,
19
+ ]
@@ -18,7 +18,7 @@ from arelle.ModelDocument import ModelDocument, Type as ModelDocumentType
18
18
  from arelle.ModelDtsObject import ModelConcept
19
19
  from arelle.ModelInstanceObject import ModelContext, ModelFact, ModelInlineFootnote, ModelUnit, ModelInlineFact
20
20
  from arelle.ModelObject import ModelObject
21
- from arelle.ModelValue import QName
21
+ from arelle.ModelValue import QName, qname
22
22
  from arelle.ModelXbrl import ModelXbrl
23
23
  from arelle.typing import assert_type
24
24
  from arelle.utils.PluginData import PluginData
@@ -28,6 +28,7 @@ from arelle.XmlValidate import lexicalPatterns
28
28
  from arelle.XmlValidateConst import VALID
29
29
  from .LinkbaseType import LinkbaseType
30
30
 
31
+ DEFAULT_MEMBER_ROLE_URI = 'https://www.nltaxonomie.nl/kvk/role/axis-defaults'
31
32
  XBRLI_IDENTIFIER_PATTERN = re.compile(r"^(?!00)\d{8}$")
32
33
  XBRLI_IDENTIFIER_SCHEMA = 'http://www.kvk.nl/kvk-id'
33
34
  MAX_REPORT_PACKAGE_SIZE_MBS = 100
@@ -63,6 +64,10 @@ EFFECTIVE_KVK_GAAP_IFRS_ENTRYPOINT_FILES = frozenset((
63
64
  'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-ifrs-ext.xsd',
64
65
  ))
65
66
 
67
+ EFFECTIVE_KVK_GAAP_OTHER_ENTRYPOINT_FILES = frozenset((
68
+ 'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-other-gaap.xsd',
69
+ ))
70
+
66
71
  TAXONOMY_URLS_BY_YEAR = {
67
72
  '2024': {
68
73
  'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd',
@@ -94,6 +99,10 @@ STANDARD_TAXONOMY_URLS = frozenset((
94
99
  'https://www.w3.org/1999/xlink'
95
100
  ))
96
101
 
102
+ QN_DOMAIN_ITEM_TYPES = frozenset((
103
+ qname("{http://www.xbrl.org/dtr/type/2022-03-31}nonnum:domainItemType"),
104
+ ))
105
+
97
106
 
98
107
  @dataclass(frozen=True)
99
108
  class ContextData:
@@ -195,6 +204,12 @@ class PluginValidationDataExtension(PluginData):
195
204
  def __hash__(self) -> int:
196
205
  return id(self)
197
206
 
207
+ def addDomMbrs(self, modelXbrl: ModelXbrl, sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcept]) -> None:
208
+ if isinstance(sourceDomMbr, ModelConcept) and sourceDomMbr not in membersSet:
209
+ membersSet.add(sourceDomMbr)
210
+ for domMbrRel in modelXbrl.relationshipSet(XbrlConst.domainMember, ELR).fromModelObject(sourceDomMbr):
211
+ self.addDomMbrs(modelXbrl, domMbrRel.toModelObject, domMbrRel.consecutiveLinkrole, membersSet)
212
+
198
213
  @lru_cache(1)
199
214
  def contextsByDocument(self, modelXbrl: ModelXbrl) -> dict[str, list[ModelContext]]:
200
215
  contextsByDocument = defaultdict(list)
@@ -384,6 +399,31 @@ class PluginValidationDataExtension(PluginData):
384
399
  _getDocumentsInDts(modelXbrl.modelDocument)
385
400
  return modelDocuments
386
401
 
402
+ def getDomainMembers(self, modelXbrl: ModelXbrl) -> set[ModelConcept]:
403
+ domainMembers = set() # concepts which are dimension domain members
404
+ hcPrimaryItems: set[ModelConcept] = set()
405
+ hcMembers: set[Any] = set()
406
+ for hasHypercubeArcrole in (XbrlConst.all, XbrlConst.notAll):
407
+ hasHypercubeRelationships = modelXbrl.relationshipSet(hasHypercubeArcrole).fromModelObjects()
408
+ for hasHcRels in hasHypercubeRelationships.values():
409
+ for hasHcRel in hasHcRels:
410
+ sourceConcept: ModelConcept = hasHcRel.fromModelObject
411
+ hcPrimaryItems.add(sourceConcept)
412
+ # find associated primary items to source concept
413
+ for domMbrRel in modelXbrl.relationshipSet(XbrlConst.domainMember).fromModelObject(sourceConcept):
414
+ if domMbrRel.consecutiveLinkrole == hasHcRel.linkrole: # only those related to this hc
415
+ self.addDomMbrs(modelXbrl, domMbrRel.toModelObject, domMbrRel.consecutiveLinkrole, hcPrimaryItems)
416
+ hc = hasHcRel.toModelObject
417
+ for hcDimRel in modelXbrl.relationshipSet(XbrlConst.hypercubeDimension, hasHcRel.consecutiveLinkrole).fromModelObject(hc):
418
+ dim = hcDimRel.toModelObject
419
+ if isinstance(dim, ModelConcept):
420
+ for dimDomRel in modelXbrl.relationshipSet(XbrlConst.dimensionDomain, hcDimRel.consecutiveLinkrole).fromModelObject(dim):
421
+ dom = dimDomRel.toModelObject
422
+ if isinstance(dom, ModelConcept):
423
+ self.addDomMbrs(modelXbrl, dom, dimDomRel.consecutiveLinkrole, hcMembers)
424
+ domainMembers.update(hcMembers)
425
+ return domainMembers
426
+
387
427
  def getEligibleForTransformHiddenFacts(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
388
428
  return self.checkHiddenElements(modelXbrl).eligibleForTransformHiddenFacts
389
429
 
@@ -10,7 +10,9 @@ from arelle.ModelXbrl import ModelXbrl
10
10
  from arelle.ValidateXbrl import ValidateXbrl
11
11
  from arelle.typing import TypeGetText
12
12
  from arelle.utils.validate.ValidationPlugin import ValidationPlugin
13
- from .DisclosureSystems import DISCLOSURE_SYSTEM_NT16, DISCLOSURE_SYSTEM_NT17, DISCLOSURE_SYSTEM_NT18, DISCLOSURE_SYSTEM_NT19, DISCLOSURE_SYSTEM_NL_INLINE_2024
13
+ from .DisclosureSystems import (DISCLOSURE_SYSTEM_NT16, DISCLOSURE_SYSTEM_NT17, DISCLOSURE_SYSTEM_NT18,
14
+ DISCLOSURE_SYSTEM_NT19, DISCLOSURE_SYSTEM_NL_INLINE_2024,
15
+ DISCLOSURE_SYSTEM_NL_INLINE_2024_GAAP_OTHER)
14
16
  from .PluginValidationDataExtension import PluginValidationDataExtension
15
17
 
16
18
  _: TypeGetText
@@ -174,6 +176,13 @@ class ValidationPluginExtension(ValidationPlugin):
174
176
  entrypoints = {entrypointRoot + e for e in [
175
177
  'kvk-annual-report-ifrs-ext.xsd',
176
178
  'kvk-annual-report-nlgaap-ext.xsd',
179
+ ]}
180
+ elif disclosureSystem == DISCLOSURE_SYSTEM_NL_INLINE_2024_GAAP_OTHER:
181
+ jenvNamespace = 'https://www.nltaxonomie.nl/bw2-titel9/2024-12-31/bw2-titel9-cor'
182
+ kvkINamespace = None
183
+ nlTypesNamespace = None
184
+ entrypointRoot = 'http://www.nltaxonomie.nl/kvk/2024-12-31/'
185
+ entrypoints = {entrypointRoot + e for e in [
177
186
  'kvk-annual-report-other-gaap.xsd',
178
187
  ]}
179
188
  else:
@@ -5,7 +5,12 @@
5
5
  <!-- see arelle/config/disclosuresystems.xml for full comments -->
6
6
  <DisclosureSystem
7
7
  names="NL-INLINE-2024|nl-inline-2024|kvk-inline-2024-preview"
8
- description="Checks for NL INLINE 2024"
8
+ description="Checks for NL INLINE 2024. For use with NL GAAP and NL IFRS"
9
+ validationType="NL"
10
+ />
11
+ <DisclosureSystem
12
+ names="NL-INLINE-2024-GAAP-OTHER|nl-inline-2024-gaap-other|kvk-inline-2024-gaap-other-preview"
13
+ description="Checks for NL INLINE 2024. For use with other GAAP."
9
14
  validationType="NL"
10
15
  />
11
16
  <DisclosureSystem