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 +2 -0
- arelle/_version.py +2 -2
- arelle/api/Session.py +88 -58
- arelle/plugin/validate/NL/DisclosureSystems.py +14 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +41 -1
- arelle/plugin/validate/NL/ValidationPluginExtension.py +10 -1
- arelle/plugin/validate/NL/resources/config.xml +6 -1
- arelle/plugin/validate/NL/rules/nl_kvk.py +233 -143
- {arelle_release-2.37.21.dist-info → arelle_release-2.37.22.dist-info}/METADATA +1 -1
- {arelle_release-2.37.21.dist-info → arelle_release-2.37.22.dist-info}/RECORD +18 -17
- tests/integration_tests/validation/conformance_suite_configs.py +2 -0
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +4 -22
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024_gaap_other.py +244 -0
- tests/integration_tests/validation/discover_tests.py +2 -2
- {arelle_release-2.37.21.dist-info → arelle_release-2.37.22.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.21.dist-info → arelle_release-2.37.22.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.21.dist-info → arelle_release-2.37.22.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.21.dist-info → arelle_release-2.37.22.dist-info}/top_level.txt +0 -0
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
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
|
-
|
|
35
|
-
self.
|
|
36
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
self._cntlr
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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,
|
|
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
|