elementpath 4.4.0__tar.gz → 4.5.0__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.
Files changed (130) hide show
  1. {elementpath-4.4.0 → elementpath-4.5.0}/CHANGELOG.rst +7 -0
  2. {elementpath-4.4.0 → elementpath-4.5.0}/PKG-INFO +44 -32
  3. {elementpath-4.4.0 → elementpath-4.5.0}/README.rst +42 -30
  4. {elementpath-4.4.0 → elementpath-4.5.0}/doc/conf.py +2 -2
  5. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/__init__.py +1 -1
  6. elementpath-4.5.0/elementpath/_typing.py +27 -0
  7. elementpath-4.5.0/elementpath/aliases.py +43 -0
  8. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/collations.py +3 -3
  9. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/compare.py +10 -9
  10. elementpath-4.5.0/elementpath/datatypes/__init__.py +61 -0
  11. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/atomic_types.py +8 -2
  12. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/binary.py +1 -1
  13. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/datetime.py +16 -9
  14. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/numeric.py +5 -2
  15. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/proxies.py +13 -1
  16. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/qname.py +1 -1
  17. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/string.py +4 -1
  18. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/untyped.py +1 -1
  19. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/datatypes/uri.py +1 -1
  20. elementpath-4.5.0/elementpath/decoder.py +176 -0
  21. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/etree.py +4 -3
  22. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/exceptions.py +9 -7
  23. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/helpers.py +4 -2
  24. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/namespaces.py +34 -29
  25. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/protocols.py +53 -7
  26. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/regex/character_classes.py +2 -1
  27. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/regex/codepoints.py +3 -1
  28. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/regex/patterns.py +2 -2
  29. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/regex/unicode_subsets.py +2 -1
  30. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/schema_proxy.py +11 -15
  31. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/sequence_types.py +10 -10
  32. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/serialization.py +9 -7
  33. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/tdop.py +17 -14
  34. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/tree_builders.py +16 -13
  35. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/validators/__init__.py +1 -1
  36. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath1/_xpath1_axes.py +39 -24
  37. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath1/_xpath1_functions.py +90 -63
  38. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath1/_xpath1_operators.py +173 -174
  39. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath1/xpath1_parser.py +36 -20
  40. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath2/_xpath2_constructors.py +147 -89
  41. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath2/_xpath2_functions.py +316 -225
  42. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath2/_xpath2_operators.py +139 -101
  43. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath2/xpath2_parser.py +22 -27
  44. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath30/_xpath30_functions.py +216 -139
  45. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath30/_xpath30_operators.py +45 -23
  46. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath30/xpath30_helpers.py +4 -3
  47. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath30/xpath30_parser.py +3 -4
  48. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath31/_xpath31_functions.py +193 -142
  49. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath31/_xpath31_operators.py +41 -27
  50. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath31/xpath31_parser.py +3 -3
  51. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath_context.py +96 -59
  52. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath_nodes.py +72 -47
  53. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath_selectors.py +10 -14
  54. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath_tokens.py +230 -235
  55. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath.egg-info/PKG-INFO +44 -32
  56. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath.egg-info/SOURCES.txt +3 -0
  57. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath.egg-info/requires.txt +1 -1
  58. {elementpath-4.4.0 → elementpath-4.5.0}/setup.py +2 -2
  59. {elementpath-4.4.0 → elementpath-4.5.0}/tests/execute_w3c_tests.py +4 -1
  60. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_datatypes.py +5 -4
  61. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_regex.py +3 -3
  62. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_selectors.py +22 -1
  63. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath1_parser.py +1 -9
  64. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath2_constructors.py +4 -2
  65. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath2_functions.py +10 -5
  66. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath2_parser.py +67 -0
  67. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath30.py +21 -0
  68. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath_context.py +1 -4
  69. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath_nodes.py +23 -0
  70. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath_tokens.py +9 -75
  71. {elementpath-4.4.0 → elementpath-4.5.0}/tests/xpath_test_class.py +2 -0
  72. {elementpath-4.4.0 → elementpath-4.5.0}/tox.ini +19 -12
  73. elementpath-4.4.0/elementpath/datatypes/__init__.py +0 -124
  74. {elementpath-4.4.0 → elementpath-4.5.0}/.coveragerc +0 -0
  75. {elementpath-4.4.0 → elementpath-4.5.0}/LICENSE +0 -0
  76. {elementpath-4.4.0 → elementpath-4.5.0}/MANIFEST.in +0 -0
  77. {elementpath-4.4.0 → elementpath-4.5.0}/doc/Makefile +0 -0
  78. {elementpath-4.4.0 → elementpath-4.5.0}/doc/advanced.rst +0 -0
  79. {elementpath-4.4.0 → elementpath-4.5.0}/doc/index.rst +0 -0
  80. {elementpath-4.4.0 → elementpath-4.5.0}/doc/introduction.rst +0 -0
  81. {elementpath-4.4.0 → elementpath-4.5.0}/doc/make.bat +0 -0
  82. {elementpath-4.4.0 → elementpath-4.5.0}/doc/pratt_api.rst +0 -0
  83. {elementpath-4.4.0 → elementpath-4.5.0}/doc/requirements.txt +0 -0
  84. {elementpath-4.4.0 → elementpath-4.5.0}/doc/xpath_api.rst +0 -0
  85. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/py.typed +0 -0
  86. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/regex/__init__.py +0 -0
  87. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/regex/generate_categories.py +0 -0
  88. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/regex/unicode_categories.py +0 -0
  89. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/validators/analyze-string.xsd +0 -0
  90. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/validators/schema-for-json.xsd +0 -0
  91. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath1/__init__.py +0 -0
  92. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath2/__init__.py +0 -0
  93. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath3.py +0 -0
  94. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath30/__init__.py +0 -0
  95. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath30/_translation_maps.py +0 -0
  96. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath/xpath31/__init__.py +0 -0
  97. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath.egg-info/dependency_links.txt +0 -0
  98. {elementpath-4.4.0 → elementpath-4.5.0}/elementpath.egg-info/top_level.txt +0 -0
  99. {elementpath-4.4.0 → elementpath-4.5.0}/mypy.ini +0 -0
  100. {elementpath-4.4.0 → elementpath-4.5.0}/requirements-dev.txt +0 -0
  101. {elementpath-4.4.0 → elementpath-4.5.0}/setup.cfg +0 -0
  102. {elementpath-4.4.0 → elementpath-4.5.0}/tests/__init__.py +0 -0
  103. {elementpath-4.4.0 → elementpath-4.5.0}/tests/memory_profiling.py +0 -0
  104. {elementpath-4.4.0 → elementpath-4.5.0}/tests/mypy_tests/protocols.py +0 -0
  105. {elementpath-4.4.0 → elementpath-4.5.0}/tests/mypy_tests/selectors.py +0 -0
  106. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/analyze-string.xsd +0 -0
  107. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/external_entity.xml +0 -0
  108. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/sample.xml +0 -0
  109. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/schema-for-json.xsd +0 -0
  110. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/unparsed_entity.xml +0 -0
  111. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/unused_external_entity.xml +0 -0
  112. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/unused_unparsed_entity.xml +0 -0
  113. {elementpath-4.4.0 → elementpath-4.5.0}/tests/resources/with_entity.xml +0 -0
  114. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_collations.py +0 -0
  115. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_compare.py +0 -0
  116. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_elementpath.py +0 -0
  117. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_etree.py +0 -0
  118. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_exceptions.py +0 -0
  119. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_helpers.py +0 -0
  120. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_namespaces.py +0 -0
  121. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_package.py +0 -0
  122. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_schema_context.py +0 -0
  123. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_schema_proxy.py +0 -0
  124. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_sequence_types.py +0 -0
  125. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_serialization.py +0 -0
  126. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_tdop_parser.py +0 -0
  127. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_tree_builders.py +0 -0
  128. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_typing.py +0 -0
  129. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_validators.py +0 -0
  130. {elementpath-4.4.0 → elementpath-4.5.0}/tests/test_xpath31.py +0 -0
@@ -2,6 +2,12 @@
2
2
  CHANGELOG
3
3
  *********
4
4
 
5
+ `v4.5.0`_ (2024-09-09)
6
+ ======================
7
+ * Fix and clean node trees iteration methods (issue #72)
8
+ * Fix missing raw string for '[^\r\n]' (pull request #76)
9
+ * Full and more specific type annotations
10
+
5
11
  `v4.4.0`_ (2024-03-11)
6
12
  ======================
7
13
  * Improve stand-alone XPath functions builder (issue #70)
@@ -463,3 +469,4 @@ CHANGELOG
463
469
  .. _v4.2.1: https://github.com/sissaschool/elementpath/compare/v4.2.0...v4.2.1
464
470
  .. _v4.3.0: https://github.com/sissaschool/elementpath/compare/v4.2.1...v4.3.0
465
471
  .. _v4.4.0: https://github.com/sissaschool/elementpath/compare/v4.3.0...v4.4.0
472
+ .. _v4.4.1: https://github.com/sissaschool/elementpath/compare/v4.4.0...v4.5.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: elementpath
3
- Version: 4.4.0
3
+ Version: 4.5.0
4
4
  Summary: XPath 1.0/2.0/3.0/3.1 parsers and selectors for ElementTree and lxml
5
5
  Home-page: https://github.com/sissaschool/elementpath
6
6
  Author: Davide Brunato
@@ -32,7 +32,7 @@ Provides-Extra: dev
32
32
  Requires-Dist: tox; extra == "dev"
33
33
  Requires-Dist: coverage; extra == "dev"
34
34
  Requires-Dist: lxml; extra == "dev"
35
- Requires-Dist: xmlschema>=2.0.0; extra == "dev"
35
+ Requires-Dist: xmlschema>=3.3.2; extra == "dev"
36
36
  Requires-Dist: Sphinx; extra == "dev"
37
37
  Requires-Dist: memory-profiler; extra == "dev"
38
38
  Requires-Dist: memray; extra == "dev"
@@ -76,11 +76,13 @@ You can install the package with *pip* in a Python 3.8+ environment::
76
76
 
77
77
  For using it import the package and apply the selectors on ElementTree nodes:
78
78
 
79
- >>> import elementpath
80
- >>> from xml.etree import ElementTree
81
- >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
82
- >>> elementpath.select(root, '/A/B2/*')
83
- [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
79
+ .. code-block:: pycon
80
+
81
+ >>> import elementpath
82
+ >>> from xml.etree import ElementTree
83
+ >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
84
+ >>> elementpath.select(root, '/A/B2/*')
85
+ [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
84
86
 
85
87
  The *select* API provides the standard XPath result format that is a list or an elementary
86
88
  datatype's value. If you want only to iterate over results you can use the generator function
@@ -89,52 +91,62 @@ datatype's value. If you want only to iterate over results you can use the gener
89
91
  The selectors API works also using XML data trees based on the `lxml.etree <http://lxml.de>`_
90
92
  library:
91
93
 
92
- >>> import elementpath
93
- >>> import lxml.etree as etree
94
- >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
95
- >>> elementpath.select(root, '/A/B2/*')
96
- [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
94
+ .. code-block:: pycon
95
+
96
+ >>> import elementpath
97
+ >>> import lxml.etree as etree
98
+ >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
99
+ >>> elementpath.select(root, '/A/B2/*')
100
+ [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
97
101
 
98
102
  When you need to apply the same XPath expression to several XML data you can also use the
99
103
  *Selector* class, creating an instance and then using it to apply the path on distinct XML
100
104
  data:
101
105
 
102
- >>> import elementpath
103
- >>> import lxml.etree as etree
104
- >>> selector = elementpath.Selector('/A/*/*')
105
- >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
106
- >>> selector.select(root)
107
- [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
108
- >>> root = etree.XML('<A><B1><C0/></B1><B2><C1/><C2/><C3/></B2></A>')
109
- >>> selector.select(root)
110
- [<Element C0 at ...>, <Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
106
+ .. code-block:: pycon
107
+
108
+ >>> import elementpath
109
+ >>> import lxml.etree as etree
110
+ >>> selector = elementpath.Selector('/A/*/*')
111
+ >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
112
+ >>> selector.select(root)
113
+ [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
114
+ >>> root = etree.XML('<A><B1><C0/></B1><B2><C1/><C2/><C3/></B2></A>')
115
+ >>> selector.select(root)
116
+ [<Element C0 at ...>, <Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
111
117
 
112
118
  Public API classes and functions are described into the
113
119
  `elementpath manual on the "Read the Docs" site <http://elementpath.readthedocs.io/en/latest/>`_.
114
120
 
115
121
  For default the XPath 2.0 is used. If you need XPath 1.0 parser provide the *parser* argument:
116
122
 
117
- >>> from elementpath import select, XPath1Parser
118
- >>> from xml.etree import ElementTree
119
- >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
120
- >>> select(root, '/A/B2/*', parser=XPath1Parser)
121
- [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
123
+ .. code-block:: pycon
124
+
125
+ >>> from elementpath import select, XPath1Parser
126
+ >>> from xml.etree import ElementTree
127
+ >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
128
+ >>> select(root, '/A/B2/*', parser=XPath1Parser)
129
+ [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
122
130
 
123
131
  For XPath 3.0/3.1 import the parser from *elementpath.xpath3* subpackage, that is not loaded
124
132
  for default:
125
133
 
126
- >>> from elementpath.xpath3 import XPath3Parser
127
- >>> select(root, 'math:atan(1.0e0)', parser=XPath3Parser)
128
- 0.7853981633974483
134
+ .. code-block:: pycon
135
+
136
+ >>> from elementpath.xpath3 import XPath3Parser
137
+ >>> select(root, 'math:atan(1.0e0)', parser=XPath3Parser)
138
+ 0.7853981633974483
129
139
 
130
140
  Note: *XPath3Parser* is an alias of *XPath31Parser*.
131
141
 
132
142
  If you need only XPath 3.0 you can also use a more specific subpackage,
133
143
  avoiding the loading of XPath 3.1 implementation:
134
144
 
135
- >>> from elementpath.xpath30 import XPath30Parser
136
- >>> select(root, 'math:atan(1.0e0)', parser=XPath30Parser)
137
- 0.7853981633974483
145
+ .. code-block:: pycon
146
+
147
+ >>> from elementpath.xpath30 import XPath30Parser
148
+ >>> select(root, 'math:atan(1.0e0)', parser=XPath30Parser)
149
+ 0.7853981633974483
138
150
 
139
151
 
140
152
  Contributing
@@ -34,11 +34,13 @@ You can install the package with *pip* in a Python 3.8+ environment::
34
34
 
35
35
  For using it import the package and apply the selectors on ElementTree nodes:
36
36
 
37
- >>> import elementpath
38
- >>> from xml.etree import ElementTree
39
- >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
40
- >>> elementpath.select(root, '/A/B2/*')
41
- [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
37
+ .. code-block:: pycon
38
+
39
+ >>> import elementpath
40
+ >>> from xml.etree import ElementTree
41
+ >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
42
+ >>> elementpath.select(root, '/A/B2/*')
43
+ [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
42
44
 
43
45
  The *select* API provides the standard XPath result format that is a list or an elementary
44
46
  datatype's value. If you want only to iterate over results you can use the generator function
@@ -47,52 +49,62 @@ datatype's value. If you want only to iterate over results you can use the gener
47
49
  The selectors API works also using XML data trees based on the `lxml.etree <http://lxml.de>`_
48
50
  library:
49
51
 
50
- >>> import elementpath
51
- >>> import lxml.etree as etree
52
- >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
53
- >>> elementpath.select(root, '/A/B2/*')
54
- [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
52
+ .. code-block:: pycon
53
+
54
+ >>> import elementpath
55
+ >>> import lxml.etree as etree
56
+ >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
57
+ >>> elementpath.select(root, '/A/B2/*')
58
+ [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
55
59
 
56
60
  When you need to apply the same XPath expression to several XML data you can also use the
57
61
  *Selector* class, creating an instance and then using it to apply the path on distinct XML
58
62
  data:
59
63
 
60
- >>> import elementpath
61
- >>> import lxml.etree as etree
62
- >>> selector = elementpath.Selector('/A/*/*')
63
- >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
64
- >>> selector.select(root)
65
- [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
66
- >>> root = etree.XML('<A><B1><C0/></B1><B2><C1/><C2/><C3/></B2></A>')
67
- >>> selector.select(root)
68
- [<Element C0 at ...>, <Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
64
+ .. code-block:: pycon
65
+
66
+ >>> import elementpath
67
+ >>> import lxml.etree as etree
68
+ >>> selector = elementpath.Selector('/A/*/*')
69
+ >>> root = etree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
70
+ >>> selector.select(root)
71
+ [<Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
72
+ >>> root = etree.XML('<A><B1><C0/></B1><B2><C1/><C2/><C3/></B2></A>')
73
+ >>> selector.select(root)
74
+ [<Element C0 at ...>, <Element C1 at ...>, <Element C2 at ...>, <Element C3 at ...>]
69
75
 
70
76
  Public API classes and functions are described into the
71
77
  `elementpath manual on the "Read the Docs" site <http://elementpath.readthedocs.io/en/latest/>`_.
72
78
 
73
79
  For default the XPath 2.0 is used. If you need XPath 1.0 parser provide the *parser* argument:
74
80
 
75
- >>> from elementpath import select, XPath1Parser
76
- >>> from xml.etree import ElementTree
77
- >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
78
- >>> select(root, '/A/B2/*', parser=XPath1Parser)
79
- [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
81
+ .. code-block:: pycon
82
+
83
+ >>> from elementpath import select, XPath1Parser
84
+ >>> from xml.etree import ElementTree
85
+ >>> root = ElementTree.XML('<A><B1/><B2><C1/><C2/><C3/></B2></A>')
86
+ >>> select(root, '/A/B2/*', parser=XPath1Parser)
87
+ [<Element 'C1' at ...>, <Element 'C2' at ...>, <Element 'C3' at ...>]
80
88
 
81
89
  For XPath 3.0/3.1 import the parser from *elementpath.xpath3* subpackage, that is not loaded
82
90
  for default:
83
91
 
84
- >>> from elementpath.xpath3 import XPath3Parser
85
- >>> select(root, 'math:atan(1.0e0)', parser=XPath3Parser)
86
- 0.7853981633974483
92
+ .. code-block:: pycon
93
+
94
+ >>> from elementpath.xpath3 import XPath3Parser
95
+ >>> select(root, 'math:atan(1.0e0)', parser=XPath3Parser)
96
+ 0.7853981633974483
87
97
 
88
98
  Note: *XPath3Parser* is an alias of *XPath31Parser*.
89
99
 
90
100
  If you need only XPath 3.0 you can also use a more specific subpackage,
91
101
  avoiding the loading of XPath 3.1 implementation:
92
102
 
93
- >>> from elementpath.xpath30 import XPath30Parser
94
- >>> select(root, 'math:atan(1.0e0)', parser=XPath30Parser)
95
- 0.7853981633974483
103
+ .. code-block:: pycon
104
+
105
+ >>> from elementpath.xpath30 import XPath30Parser
106
+ >>> select(root, 'math:atan(1.0e0)', parser=XPath30Parser)
107
+ 0.7853981633974483
96
108
 
97
109
 
98
110
  Contributing
@@ -29,9 +29,9 @@ copyright = '2018-2024, SISSA (International School for Advanced Studies)'
29
29
  author = 'Davide Brunato'
30
30
 
31
31
  # The short X.Y version
32
- version = '4.4'
32
+ version = '4.5'
33
33
  # The full version, including alpha/beta/rc tags
34
- release = '4.4.0'
34
+ release = '4.5.0'
35
35
 
36
36
  # -- General configuration ---------------------------------------------------
37
37
 
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # @author Davide Brunato <brunato@sissa.it>
9
9
  #
10
- __version__ = '4.4.0'
10
+ __version__ = '4.5.0'
11
11
  __author__ = "Davide Brunato"
12
12
  __contact__ = "brunato@sissa.it"
13
13
  __copyright__ = "Copyright 2018-2024, SISSA"
@@ -0,0 +1,27 @@
1
+ #
2
+ # Copyright (c), 2024, SISSA (International School for Advanced Studies).
3
+ # All rights reserved.
4
+ # This file is distributed under the terms of the MIT License.
5
+ # See the file 'LICENSE' in the root directory of the present
6
+ # distribution, or http://opensource.org/licenses/MIT.
7
+ #
8
+ # @author Davide Brunato <brunato@sissa.it>
9
+ #
10
+ """
11
+ Version related imports for subscriptable types for type annotations (no builtins).
12
+ """
13
+ import sys
14
+
15
+ if sys.version_info < (3, 9):
16
+ from typing import Callable, Counter, Deque, Iterable, Iterator, \
17
+ Mapping, Match, MutableMapping, MutableSequence, MutableSet, Pattern, Sequence
18
+ else:
19
+ from collections import deque as Deque, Counter # noqa
20
+ from collections.abc import Callable, Iterable, Iterator, Mapping, MutableMapping, \
21
+ MutableSequence, MutableSet, Sequence
22
+ from re import Match, Pattern
23
+
24
+
25
+ __all__ = ['Callable', 'Counter', 'Deque', 'Iterable', 'Iterator', 'Match',
26
+ 'Mapping', 'MutableMapping', 'MutableSequence', 'MutableSet',
27
+ 'Pattern', 'Sequence']
@@ -0,0 +1,43 @@
1
+ #
2
+ # Copyright (c), 2021, SISSA (International School for Advanced Studies).
3
+ # All rights reserved.
4
+ # This file is distributed under the terms of the MIT License.
5
+ # See the file 'LICENSE' in the root directory of the present
6
+ # distribution, or http://opensource.org/licenses/MIT.
7
+ #
8
+ # @author Davide Brunato <brunato@sissa.it>
9
+ #
10
+ """
11
+ Common type hints aliases for elementpath.
12
+ """
13
+ from typing import Any, List, Optional, NoReturn, Tuple, Type, TYPE_CHECKING, TypeVar, Union
14
+
15
+ from elementpath._typing import MutableMapping
16
+
17
+ ##
18
+ # Type aliases
19
+ NamespacesType = MutableMapping[str, str]
20
+ NsmapType = MutableMapping[Optional[str], str] # compatible with the nsmap of lxml Element
21
+ AnyNsmapType = Union[NamespacesType, NsmapType, None] # for composition and function arguments
22
+
23
+ NargsType = Optional[Union[int, Tuple[int, Optional[int]]]]
24
+ ClassCheckType = Union[Type[Any], Tuple[Type[Any], ...]]
25
+
26
+ T = TypeVar('T')
27
+ Emptiable = Union[T, List[NoReturn]]
28
+ SequenceType = Union[T, List[T]]
29
+ InputType = Union[None, T, List[T], Tuple[T, ...]]
30
+
31
+ if TYPE_CHECKING:
32
+ from elementpath.datatypes import AtomicType, ArithmeticType, NumericType
33
+ from elementpath.xpath_nodes import ChildNodeType, ParentNodeType
34
+ from elementpath.tree_builders import RootArgType
35
+ from elementpath.xpath_context import ContextType, FunctionArgType, ItemType, \
36
+ ItemArgType, ValueType
37
+ from elementpath.xpath_tokens import XPathParserType, XPathTokenType
38
+
39
+ __all__ = ['NamespacesType', 'NsmapType', 'AnyNsmapType', 'NargsType',
40
+ 'ClassCheckType', 'Emptiable', 'SequenceType', 'InputType',
41
+ 'AtomicType', 'ArithmeticType', 'NumericType', 'ChildNodeType',
42
+ 'ParentNodeType', 'RootArgType', 'ContextType', 'FunctionArgType',
43
+ 'ItemType', 'ItemArgType', 'ValueType', 'XPathParserType', 'XPathTokenType']
@@ -14,7 +14,7 @@ from types import TracebackType
14
14
  from typing import TYPE_CHECKING, Any, Optional, Tuple, Type, Union
15
15
  from urllib.parse import urljoin, urlsplit
16
16
 
17
- from .exceptions import xpath_error
17
+ from elementpath.exceptions import xpath_error
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from .xpath_tokens import XPathToken
@@ -173,8 +173,8 @@ class CollationManager(context_class_base):
173
173
  def find(self, a: str, b: str) -> int:
174
174
  return self.strxfrm(a).find(self.strxfrm(b))
175
175
 
176
- def startswith(self, a: str, b: str) -> int:
176
+ def startswith(self, a: str, b: str) -> bool:
177
177
  return self.strxfrm(a).startswith(self.strxfrm(b))
178
178
 
179
- def endswith(self, a: str, b: str) -> int:
179
+ def endswith(self, a: str, b: str) -> bool:
180
180
  return self.strxfrm(a).endswith(self.strxfrm(b))
@@ -11,15 +11,16 @@ import math
11
11
  from decimal import Decimal
12
12
  from functools import cmp_to_key
13
13
  from itertools import zip_longest
14
- from typing import Any, Callable, Optional, Iterable, Iterator
15
-
16
- from .protocols import ElementProtocol
17
- from .exceptions import xpath_error
18
- from .datatypes import UntypedAtomic, AnyURI, AbstractQName
19
- from .collations import UNICODE_CODEPOINT_COLLATION, CollationManager
20
- from .xpath_nodes import XPathNode, ElementNode, AttributeNode, NamespaceNode, \
21
- TextNode, CommentNode, ProcessingInstructionNode, DocumentNode
22
- from .xpath_tokens import XPathToken, XPathFunction, XPathMap, XPathArray
14
+ from typing import Any, Optional
15
+
16
+ from elementpath._typing import Callable, Iterable, Iterator
17
+ from elementpath.protocols import ElementProtocol
18
+ from elementpath.exceptions import xpath_error
19
+ from elementpath.datatypes import UntypedAtomic, AnyURI, AbstractQName
20
+ from elementpath.collations import UNICODE_CODEPOINT_COLLATION, CollationManager
21
+ from elementpath.xpath_nodes import XPathNode, ElementNode, AttributeNode, \
22
+ NamespaceNode, TextNode, CommentNode, ProcessingInstructionNode, DocumentNode
23
+ from elementpath.xpath_tokens import XPathToken, XPathFunction, XPathMap, XPathArray
23
24
 
24
25
 
25
26
  def deep_equal(seq1: Iterable[Any],
@@ -0,0 +1,61 @@
1
+ #
2
+ # Copyright (c), 2018-2020, SISSA (International School for Advanced Studies).
3
+ # All rights reserved.
4
+ # This file is distributed under the terms of the MIT License.
5
+ # See the file 'LICENSE' in the root directory of the present
6
+ # distribution, or http://opensource.org/licenses/MIT.
7
+ #
8
+ # @author Davide Brunato <brunato@sissa.it>
9
+ #
10
+ """
11
+ XSD atomic datatypes subpackage. Includes a class for UntypedAtomic data and
12
+ classes for other XSD built-in types. This subpackage raises only built-in
13
+ exceptions in order to be reusable in other packages.
14
+ """
15
+ from decimal import Decimal
16
+ from typing import Union
17
+
18
+ from .atomic_types import xsd10_atomic_types, xsd11_atomic_types, \
19
+ AtomicTypeMeta, AnyAtomicType
20
+ from .untyped import UntypedAtomic
21
+ from .qname import AbstractQName, QName, Notation
22
+ from .numeric import Float10, Float, Integer, Int, NegativeInteger, \
23
+ PositiveInteger, NonNegativeInteger, NonPositiveInteger, Long, \
24
+ Short, Byte, UnsignedByte, UnsignedInt, UnsignedLong, UnsignedShort
25
+ from .string import NormalizedString, XsdToken, Name, NCName, NMToken, Id, \
26
+ Idref, Language, Entity
27
+ from .uri import AnyURI
28
+ from .binary import AbstractBinary, Base64Binary, HexBinary
29
+ from .datetime import AbstractDateTime, DateTime10, DateTime, DateTimeStamp, \
30
+ Date10, Date, GregorianDay, GregorianMonth, GregorianYear, GregorianYear10, \
31
+ GregorianMonthDay, GregorianYearMonth, GregorianYearMonth10, Time, Timezone, \
32
+ Duration, DayTimeDuration, YearMonthDuration, OrderedDateTime
33
+ from .proxies import BooleanProxy, DecimalProxy, DoubleProxy10, DoubleProxy, \
34
+ StringProxy, NumericProxy, ArithmeticProxy
35
+
36
+
37
+ xsd11_atomic_types.update(
38
+ (k, v) for k, v in xsd10_atomic_types.items() if k not in xsd11_atomic_types
39
+ )
40
+
41
+ ###
42
+ # Aliases for type annotations
43
+ AtomicType = Union[str, int, float, Decimal, bool, AnyAtomicType]
44
+ NumericType = Union[int, float, Decimal]
45
+ ArithmeticType = Union[NumericType, AbstractDateTime, Duration, UntypedAtomic]
46
+ DatetimeValueType = AbstractDateTime # keep until v5.0 for backward compatibility
47
+
48
+ __all__ = ['xsd10_atomic_types', 'xsd11_atomic_types',
49
+ 'AtomicTypeMeta', 'AnyAtomicType', 'NumericProxy', 'ArithmeticProxy',
50
+ 'AbstractDateTime', 'DateTime10', 'DateTime', 'DateTimeStamp', 'Date10',
51
+ 'Date', 'Time', 'GregorianDay', 'GregorianMonth', 'GregorianMonthDay',
52
+ 'GregorianYear10', 'GregorianYear', 'GregorianYearMonth10', 'GregorianYearMonth',
53
+ 'Timezone', 'Duration', 'YearMonthDuration', 'DayTimeDuration', 'StringProxy',
54
+ 'NormalizedString', 'XsdToken', 'Language', 'Name', 'NCName', 'Id', 'Idref',
55
+ 'Entity', 'NMToken', 'Base64Binary', 'HexBinary', 'Float10', 'Float',
56
+ 'Integer', 'NonPositiveInteger', 'NegativeInteger', 'Long', 'Int', 'Short',
57
+ 'Byte', 'NonNegativeInteger', 'PositiveInteger', 'UnsignedLong', 'UnsignedInt',
58
+ 'UnsignedShort', 'UnsignedByte', 'AnyURI', 'Notation', 'QName', 'BooleanProxy',
59
+ 'DecimalProxy', 'DoubleProxy10', 'DoubleProxy', 'UntypedAtomic', 'AbstractBinary',
60
+ 'AtomicType', 'DatetimeValueType', 'OrderedDateTime', 'AbstractQName',
61
+ 'NumericType', 'ArithmeticType']
@@ -7,10 +7,12 @@
7
7
  #
8
8
  # @author Davide Brunato <brunato@sissa.it>
9
9
  #
10
- from abc import ABCMeta
11
- from typing import Any, Dict, Optional, Pattern, Tuple, Type
10
+ from abc import ABCMeta, abstractmethod
11
+ from typing import Any, Dict, Optional, Tuple, Type
12
12
  import re
13
13
 
14
+ from elementpath._typing import Pattern
15
+
14
16
  XSD_NAMESPACE = "http://www.w3.org/2001/XMLSchema"
15
17
 
16
18
  ###
@@ -98,3 +100,7 @@ class AtomicTypeMeta(ABCMeta):
98
100
 
99
101
  class AnyAtomicType(metaclass=AtomicTypeMeta):
100
102
  name = 'anyAtomicType'
103
+
104
+ @abstractmethod
105
+ def __init__(self, value: Any) -> None:
106
+ raise NotImplementedError()
@@ -12,7 +12,7 @@ from typing import Any, Callable, Union
12
12
  import re
13
13
  import codecs
14
14
 
15
- from ..helpers import collapse_white_spaces
15
+ from elementpath.helpers import collapse_white_spaces
16
16
  from .atomic_types import AnyAtomicType
17
17
  from .untyped import UntypedAtomic
18
18
 
@@ -14,9 +14,10 @@ import re
14
14
  import datetime
15
15
  from calendar import isleap
16
16
  from decimal import Decimal, Context
17
- from typing import cast, Any, Callable, Dict, Optional, Tuple, Union
17
+ from typing import cast, Any, Dict, Optional, Tuple, Type, TypeVar, Union
18
18
 
19
- from ..helpers import MONTH_DAYS_LEAP, MONTH_DAYS, DAYS_IN_4Y, \
19
+ from elementpath._typing import Callable
20
+ from elementpath.helpers import MONTH_DAYS_LEAP, MONTH_DAYS, DAYS_IN_4Y, \
20
21
  DAYS_IN_100Y, DAYS_IN_400Y, days_from_common_era, adjust_day, \
21
22
  normalized_seconds, months2days, round_number
22
23
  from .atomic_types import AnyAtomicType
@@ -112,6 +113,9 @@ class Timezone(datetime.tzinfo):
112
113
  raise TypeError("fromutc() argument must be a datetime.datetime instance")
113
114
 
114
115
 
116
+ _DT = TypeVar('_DT', bound='AbstractDateTime')
117
+
118
+
115
119
  class AbstractDateTime(AnyAtomicType):
116
120
  """
117
121
  A class for representing XSD date/time objects. It uses and internal datetime.datetime
@@ -260,8 +264,8 @@ class AbstractDateTime(AnyAtomicType):
260
264
  return self._dt.isocalendar()
261
265
 
262
266
  @classmethod
263
- def fromstring(cls, datetime_string: str, tzinfo: Optional[Timezone] = None) \
264
- -> 'AbstractDateTime':
267
+ def fromstring(cls: Type[_DT], datetime_string: str, tzinfo: Optional[Timezone] = None) \
268
+ -> _DT:
265
269
  """
266
270
  Creates an XSD date/time instance from a string formatted value.
267
271
 
@@ -311,13 +315,13 @@ class AbstractDateTime(AnyAtomicType):
311
315
  return cls(tzinfo=tzinfo, **kwargs)
312
316
 
313
317
  @classmethod
314
- def fromdatetime(cls, dt: Union[datetime.datetime, datetime.date, datetime.time],
315
- year: Optional[int] = None) -> 'AbstractDateTime':
318
+ def fromdatetime(cls: Type[_DT], dt: Union[datetime.datetime, datetime.date, datetime.time],
319
+ year: Optional[int] = None) -> _DT:
316
320
  """
317
321
  Creates an XSD date/time instance from a datetime.datetime/date/time instance.
318
322
 
319
323
  :param dt: the datetime, date or time instance that stores the XSD Date/Time value.
320
- :param year: if an year is provided the created instance refers to it and the \
324
+ :param year: if a year is provided the created instance refers to it and the \
321
325
  possibly present *dt.year* part is ignored.
322
326
  :return: an AbstractDateTime concrete subclass instance.
323
327
  """
@@ -331,7 +335,7 @@ class AbstractDateTime(AnyAtomicType):
331
335
  kwargs['year'] = year
332
336
  return cls(**kwargs)
333
337
 
334
- # Python can't compares offset-naive and offset-aware datetimes
338
+ # Python can't compare offset-naive and offset-aware datetimes
335
339
  def _get_operands(self, other: object) -> Tuple[datetime.datetime, datetime.datetime]:
336
340
  if isinstance(other, (self.__class__, datetime.datetime)) or \
337
341
  isinstance(self, other.__class__):
@@ -755,6 +759,9 @@ class Time(AbstractDateTime):
755
759
  raise TypeError("wrong type %r for operand %r" % (type(other), other))
756
760
 
757
761
 
762
+ _D = TypeVar('_D', bound='Duration')
763
+
764
+
758
765
  class Duration(AnyAtomicType):
759
766
  """
760
767
  Base class for the XSD duration types.
@@ -817,7 +824,7 @@ class Duration(AnyAtomicType):
817
824
  return value
818
825
 
819
826
  @classmethod
820
- def fromstring(cls, text: str) -> 'Duration':
827
+ def fromstring(cls: Type[_D], text: str) -> _D:
821
828
  """
822
829
  Creates a Duration instance from a formatted XSD duration string.
823
830
 
@@ -11,7 +11,7 @@ import re
11
11
  import math
12
12
  from typing import Any, Optional, SupportsFloat, SupportsInt, Union, Type
13
13
 
14
- from ..helpers import NUMERIC_INF_OR_NAN, INVALID_NUMERIC, collapse_white_spaces
14
+ from elementpath.helpers import NUMERIC_INF_OR_NAN, INVALID_NUMERIC, collapse_white_spaces
15
15
  from .atomic_types import AnyAtomicType
16
16
 
17
17
 
@@ -48,6 +48,9 @@ class Float10(float, AnyAtomicType):
48
48
  return super().__new__(cls, -0.0 if str(_value).startswith('-') else 0.0)
49
49
  return _value
50
50
 
51
+ def __init__(self, value: Union[str, SupportsFloat]) -> None:
52
+ float.__init__(self)
53
+
51
54
  def __hash__(self) -> int:
52
55
  return super(Float10, self).__hash__()
53
56
 
@@ -160,7 +163,7 @@ class Integer(int, AnyAtomicType):
160
163
  raise ValueError("value {} is too low for {!r}".format(value, self.__class__))
161
164
  elif self.higher_bound is not None and self >= self.higher_bound:
162
165
  raise ValueError("value {} is too high for {!r}".format(value, self.__class__))
163
- super(Integer, self).__init__()
166
+ int.__init__(self)
164
167
 
165
168
  @classmethod
166
169
  def __subclasshook__(cls, subclass: Type[Any]) -> bool:
@@ -12,7 +12,7 @@ import math
12
12
  from decimal import Decimal
13
13
  from typing import Any, Union, SupportsFloat
14
14
 
15
- from ..helpers import BOOLEAN_VALUES, collapse_white_spaces, get_double
15
+ from elementpath.helpers import BOOLEAN_VALUES, collapse_white_spaces, get_double
16
16
  from .atomic_types import AnyAtomicType
17
17
  from .untyped import UntypedAtomic
18
18
  from .numeric import Float10, Integer
@@ -45,6 +45,9 @@ class BooleanProxy(AnyAtomicType):
45
45
  raise ValueError('invalid value {!r} for xs:{}'.format(value, cls.name))
46
46
  return 't' in value or '1' in value
47
47
 
48
+ def __init__(self, value: object) -> None:
49
+ bool.__init__(self)
50
+
48
51
  @classmethod
49
52
  def __subclasshook__(cls, subclass: type) -> bool:
50
53
  return issubclass(subclass, bool)
@@ -78,6 +81,9 @@ class DecimalProxy(AnyAtomicType):
78
81
  msg = 'invalid value {!r} for xs:{}'
79
82
  raise ArithmeticError(msg.format(value, cls.name)) from None
80
83
 
84
+ def __init__(self, value: Any) -> None:
85
+ pass
86
+
81
87
  @classmethod
82
88
  def __subclasshook__(cls, subclass: type) -> bool:
83
89
  return issubclass(subclass, (int, Decimal, Integer)) and not issubclass(subclass, bool)
@@ -106,6 +112,9 @@ class DoubleProxy10(AnyAtomicType):
106
112
  def __new__(cls, value: Union[SupportsFloat, str]) -> float: # type: ignore[misc]
107
113
  return get_double(value, cls.xsd_version)
108
114
 
115
+ def __init__(self, value: Union[SupportsFloat, str]) -> None:
116
+ float.__init__(self)
117
+
109
118
  @classmethod
110
119
  def __subclasshook__(cls, subclass: type) -> bool:
111
120
  return issubclass(subclass, float) and not issubclass(subclass, Float10)
@@ -132,6 +141,9 @@ class StringProxy(AnyAtomicType):
132
141
  def __new__(cls, *args: object, **kwargs: object) -> str: # type: ignore[misc]
133
142
  return str(*args, **kwargs)
134
143
 
144
+ def __init__(self, *args: object, **kwargs: object) -> None:
145
+ str.__init__(self)
146
+
135
147
  @classmethod
136
148
  def __subclasshook__(cls, subclass: type) -> bool:
137
149
  return issubclass(subclass, str)