cfinterface 1.10.1__tar.gz → 1.10.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.
Files changed (163) hide show
  1. {cfinterface-1.10.1 → cfinterface-1.10.3}/CHANGELOG.md +12 -0
  2. {cfinterface-1.10.1 → cfinterface-1.10.3}/PKG-INFO +2 -2
  3. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/__init__.py +1 -1
  4. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/components/line/repository.py +4 -0
  5. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/files/blockfile.py +7 -5
  6. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/files/registerfile.py +7 -5
  7. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/files/sectionfile.py +7 -5
  8. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/guides/architecture.rst +2 -1
  9. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/components/test_linerepository.py +8 -0
  10. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_tabular.py +9 -0
  11. cfinterface-1.10.3/tests/files/test_read_typing.py +38 -0
  12. {cfinterface-1.10.1 → cfinterface-1.10.3}/.github/workflows/benchmark.yml +0 -0
  13. {cfinterface-1.10.1 → cfinterface-1.10.3}/.github/workflows/docs.yml +0 -0
  14. {cfinterface-1.10.1 → cfinterface-1.10.3}/.github/workflows/main.yml +0 -0
  15. {cfinterface-1.10.1 → cfinterface-1.10.3}/.github/workflows/publish.yml +0 -0
  16. {cfinterface-1.10.1 → cfinterface-1.10.3}/.gitignore +0 -0
  17. {cfinterface-1.10.1 → cfinterface-1.10.3}/.pre-commit-config.yaml +0 -0
  18. {cfinterface-1.10.1 → cfinterface-1.10.3}/CONTRIBUTING.md +0 -0
  19. {cfinterface-1.10.1 → cfinterface-1.10.3}/LICENSE +0 -0
  20. {cfinterface-1.10.1 → cfinterface-1.10.3}/README.md +0 -0
  21. {cfinterface-1.10.1 → cfinterface-1.10.3}/benchmarks/__init__.py +0 -0
  22. {cfinterface-1.10.1 → cfinterface-1.10.3}/benchmarks/bench_floatfield_write.py +0 -0
  23. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/_utils/__init__.py +0 -0
  24. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/__init__.py +0 -0
  25. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/components/__init__.py +0 -0
  26. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/components/line/__init__.py +0 -0
  27. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/components/repository.py +0 -0
  28. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/reading/__init__.py +0 -0
  29. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/reading/repository.py +0 -0
  30. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/writing/__init__.py +0 -0
  31. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/adapters/writing/repository.py +0 -0
  32. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/__init__.py +0 -0
  33. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/block.py +0 -0
  34. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/datetimefield.py +0 -0
  35. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/defaultblock.py +0 -0
  36. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/defaultregister.py +0 -0
  37. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/defaultsection.py +0 -0
  38. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/field.py +0 -0
  39. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/floatfield.py +0 -0
  40. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/integerfield.py +0 -0
  41. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/line.py +0 -0
  42. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/literalfield.py +0 -0
  43. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/register.py +0 -0
  44. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/section.py +0 -0
  45. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/components/tabular.py +0 -0
  46. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/data/__init__.py +0 -0
  47. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/data/blockdata.py +0 -0
  48. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/data/registerdata.py +0 -0
  49. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/data/sectiondata.py +0 -0
  50. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/files/__init__.py +0 -0
  51. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/py.typed +0 -0
  52. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/reading/__init__.py +0 -0
  53. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/reading/blockreading.py +0 -0
  54. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/reading/registerreading.py +0 -0
  55. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/reading/sectionreading.py +0 -0
  56. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/storage.py +0 -0
  57. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/versioning.py +0 -0
  58. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/writing/__init__.py +0 -0
  59. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/writing/blockwriting.py +0 -0
  60. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/writing/registerwriting.py +0 -0
  61. {cfinterface-1.10.1 → cfinterface-1.10.3}/cfinterface/writing/sectionwriting.py +0 -0
  62. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/_static/logo_cfinterface.svg +0 -0
  63. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/conf.py +0 -0
  64. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/getting_started/contributing.rst +0 -0
  65. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/getting_started/tutorial.rst +0 -0
  66. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/guides/faq.rst +0 -0
  67. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/guides/migration-v1.9.rst +0 -0
  68. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/guides/performance.rst +0 -0
  69. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/index.rst +0 -0
  70. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/install/install.rst +0 -0
  71. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/adapters/files/components.rst +0 -0
  72. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/adapters/files/line.rst +0 -0
  73. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/adapters/files/reading.rst +0 -0
  74. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/adapters/files/writing.rst +0 -0
  75. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/adapters/index.rst +0 -0
  76. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/blocks/files/block.rst +0 -0
  77. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/blocks/files/blockdata.rst +0 -0
  78. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/blocks/files/blockfile.rst +0 -0
  79. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/blocks/index.rst +0 -0
  80. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/fields/components/datetimefield.rst +0 -0
  81. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/fields/components/field.rst +0 -0
  82. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/fields/components/floatfield.rst +0 -0
  83. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/fields/components/integerfield.rst +0 -0
  84. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/fields/components/literalfield.rst +0 -0
  85. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/fields/index.rst +0 -0
  86. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/line/index.rst +0 -0
  87. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/registers/files/register.rst +0 -0
  88. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/registers/files/registerdata.rst +0 -0
  89. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/registers/files/registerfile.rst +0 -0
  90. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/registers/index.rst +0 -0
  91. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/sections/files/section.rst +0 -0
  92. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/sections/files/sectiondata.rst +0 -0
  93. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/sections/files/sectionfile.rst +0 -0
  94. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/sections/index.rst +0 -0
  95. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/storage/files/storagetype.rst +0 -0
  96. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/storage/index.rst +0 -0
  97. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/tabular/files/tabularparser.rst +0 -0
  98. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/tabular/files/tabularsection.rst +0 -0
  99. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/tabular/index.rst +0 -0
  100. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/versioning/files/versioning.rst +0 -0
  101. {cfinterface-1.10.1 → cfinterface-1.10.3}/docs/source/reference/versioning/index.rst +0 -0
  102. {cfinterface-1.10.1 → cfinterface-1.10.3}/examples/README.rst +0 -0
  103. {cfinterface-1.10.1 → cfinterface-1.10.3}/examples/__init__.py +0 -0
  104. {cfinterface-1.10.1 → cfinterface-1.10.3}/examples/plot_block_file.py +0 -0
  105. {cfinterface-1.10.1 → cfinterface-1.10.3}/examples/plot_register_file.py +0 -0
  106. {cfinterface-1.10.1 → cfinterface-1.10.3}/examples/plot_section_file.py +0 -0
  107. {cfinterface-1.10.1 → cfinterface-1.10.3}/examples/plot_tabular_parsing.py +0 -0
  108. {cfinterface-1.10.1 → cfinterface-1.10.3}/examples/plot_versioned_file.py +0 -0
  109. {cfinterface-1.10.1 → cfinterface-1.10.3}/pyproject.toml +0 -0
  110. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/__init__.py +0 -0
  111. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/_utils/__init__.py +0 -0
  112. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/_utils/test_is_null.py +0 -0
  113. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/__init__.py +0 -0
  114. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/components/__init__.py +0 -0
  115. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/components/test_blockrepository.py +0 -0
  116. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/components/test_factory.py +0 -0
  117. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/components/test_pattern_cache.py +0 -0
  118. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/components/test_registerrepository.py +0 -0
  119. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/components/test_sectionrepository.py +0 -0
  120. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/reading/__init__.py +0 -0
  121. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/reading/test_readingrepository.py +0 -0
  122. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/writing/__init__.py +0 -0
  123. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/adapters/writing/test_writingrepository.py +0 -0
  124. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/__init__.py +0 -0
  125. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/conftest.py +0 -0
  126. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_block.py +0 -0
  127. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_datetimefield.py +0 -0
  128. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_defaultblock.py +0 -0
  129. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_defaultregister.py +0 -0
  130. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_defaultsection.py +0 -0
  131. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_field.py +0 -0
  132. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_field_hypothesis.py +0 -0
  133. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_floatfield.py +0 -0
  134. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_floatfield_benchmark.py +0 -0
  135. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_integerfield.py +0 -0
  136. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_line.py +0 -0
  137. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_literalfield.py +0 -0
  138. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_register.py +0 -0
  139. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_section.py +0 -0
  140. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_storage_integration.py +0 -0
  141. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/components/test_tabular_hypothesis.py +0 -0
  142. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/conftest.py +0 -0
  143. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/data/__init__.py +0 -0
  144. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/data/test_blockdata.py +0 -0
  145. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/data/test_registerdata.py +0 -0
  146. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/data/test_sectiondata.py +0 -0
  147. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/files/__init__.py +0 -0
  148. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/files/test_blockfile.py +0 -0
  149. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/files/test_registerfile.py +0 -0
  150. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/files/test_sectionfile.py +0 -0
  151. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/mocks/__init__.py +0 -0
  152. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/mocks/mock_open.py +0 -0
  153. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/reading/__init__.py +0 -0
  154. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/reading/test_blockreading.py +0 -0
  155. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/reading/test_registerreading.py +0 -0
  156. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/reading/test_sectionreading.py +0 -0
  157. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/test_storage.py +0 -0
  158. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/test_storage_deprecation.py +0 -0
  159. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/test_versioning.py +0 -0
  160. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/writing/__init__.py +0 -0
  161. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/writing/test_blockwriting.py +0 -0
  162. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/writing/test_registerwriting.py +0 -0
  163. {cfinterface-1.10.1 → cfinterface-1.10.3}/tests/writing/test_sectionwriting.py +0 -0
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.10.3] - 2026-10-30
11
+
12
+ ### Fixed
13
+
14
+ - A delimited line with fewer values than its fields reads the missing fields as `None`; it used to keep the values the previous line of the same register or table left in them
15
+
16
+ ## [1.10.2] - 2026-10-14
17
+
18
+ ### Fixed
19
+
20
+ - Type `SectionFile.read()`, `RegisterFile.read()` and `BlockFile.read()` (and their `read_many()`) as returning the calling subclass instead of the base class, so mypy sees the attributes of files such as `Dadger` or `Caso` read through them
21
+
10
22
  ## [1.10.1] - 2026-04-13
11
23
 
12
24
  ### Fixed
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cfinterface
3
- Version: 1.10.1
3
+ Version: 1.10.3
4
4
  Summary: Interface for handling custom formatted files
5
5
  Project-URL: Documentation, https://rjmalves.github.io/cfinterface/
6
6
  Project-URL: Repository, https://github.com/rjmalves/cfinterface/
@@ -6,7 +6,7 @@ cfi is a Python module for handling custom formatted files
6
6
  and provide reading, storing and writing utilities.
7
7
  """
8
8
 
9
- __version__ = "1.10.1"
9
+ __version__ = "1.10.3"
10
10
 
11
11
  from . import components # noqa
12
12
  from . import data # noqa
@@ -56,6 +56,10 @@ class TextualRepository(Repository):
56
56
  values = [v.strip() for v in line.split(delimiter)]
57
57
  for field, value in zip(fields, values, strict=False):
58
58
  field.read(value)
59
+ # A field past the end of a short line must be reset, or it keeps the
60
+ # value the previous line left in the shared field.
61
+ for field in fields[len(values) :]:
62
+ field.value = None
59
63
  return self.values
60
64
 
61
65
  def read(
@@ -1,5 +1,5 @@
1
1
  import warnings
2
- from typing import IO, TYPE_CHECKING, Any
2
+ from typing import IO, TYPE_CHECKING, Any, TypeVar
3
3
 
4
4
  from cfinterface.components.block import Block
5
5
  from cfinterface.components.defaultblock import DefaultBlock
@@ -12,6 +12,8 @@ from cfinterface.writing.blockwriting import BlockWriting
12
12
  if TYPE_CHECKING:
13
13
  from cfinterface.versioning import VersionMatchResult
14
14
 
15
+ _BlockFileT = TypeVar("_BlockFileT", bound="BlockFile")
16
+
15
17
 
16
18
  class BlockFile:
17
19
  """
@@ -48,12 +50,12 @@ class BlockFile:
48
50
 
49
51
  @classmethod
50
52
  def read(
51
- cls,
53
+ cls: type[_BlockFileT],
52
54
  content: str | bytes,
53
55
  *args: Any,
54
56
  version: str | None = None,
55
57
  **kwargs: Any,
56
- ) -> "BlockFile":
58
+ ) -> _BlockFileT:
57
59
  """Read from a file path or buffer. ``version`` selects a component set
58
60
  from VERSIONS without mutating the class."""
59
61
  components = cls.BLOCKS
@@ -86,11 +88,11 @@ class BlockFile:
86
88
 
87
89
  @classmethod
88
90
  def read_many(
89
- cls,
91
+ cls: type[_BlockFileT],
90
92
  paths: list[str],
91
93
  *,
92
94
  version: str | None = None,
93
- ) -> dict[str, "BlockFile"]:
95
+ ) -> dict[str, _BlockFileT]:
94
96
  """Read multiple files and return a dict keyed by file path.
95
97
 
96
98
  Parameters
@@ -1,5 +1,5 @@
1
1
  import warnings
2
- from typing import IO, TYPE_CHECKING, Any
2
+ from typing import IO, TYPE_CHECKING, Any, TypeVar
3
3
 
4
4
  from cfinterface.components.defaultregister import DefaultRegister
5
5
  from cfinterface.components.register import Register
@@ -12,6 +12,8 @@ from cfinterface.writing.registerwriting import RegisterWriting
12
12
  if TYPE_CHECKING:
13
13
  from cfinterface.versioning import VersionMatchResult
14
14
 
15
+ _RegisterFileT = TypeVar("_RegisterFileT", bound="RegisterFile")
16
+
15
17
 
16
18
  class RegisterFile:
17
19
  """
@@ -65,12 +67,12 @@ class RegisterFile:
65
67
 
66
68
  @classmethod
67
69
  def read(
68
- cls,
70
+ cls: type[_RegisterFileT],
69
71
  content: str | bytes,
70
72
  *args: Any,
71
73
  version: str | None = None,
72
74
  **kwargs: Any,
73
- ) -> "RegisterFile":
75
+ ) -> _RegisterFileT:
74
76
  """Read from a file path or buffer. ``version`` selects a component set
75
77
  from VERSIONS without mutating the class."""
76
78
  components = cls.REGISTERS
@@ -103,11 +105,11 @@ class RegisterFile:
103
105
 
104
106
  @classmethod
105
107
  def read_many(
106
- cls,
108
+ cls: type[_RegisterFileT],
107
109
  paths: list[str],
108
110
  *,
109
111
  version: str | None = None,
110
- ) -> dict[str, "RegisterFile"]:
112
+ ) -> dict[str, _RegisterFileT]:
111
113
  """Read multiple files and return a dict keyed by file path.
112
114
 
113
115
  Parameters
@@ -1,5 +1,5 @@
1
1
  import warnings
2
- from typing import IO, TYPE_CHECKING, Any
2
+ from typing import IO, TYPE_CHECKING, Any, TypeVar
3
3
 
4
4
  from cfinterface.components.defaultsection import DefaultSection
5
5
  from cfinterface.components.section import Section
@@ -12,6 +12,8 @@ from cfinterface.writing.sectionwriting import SectionWriting
12
12
  if TYPE_CHECKING:
13
13
  from cfinterface.versioning import VersionMatchResult
14
14
 
15
+ _SectionFileT = TypeVar("_SectionFileT", bound="SectionFile")
16
+
15
17
 
16
18
  class SectionFile:
17
19
  """
@@ -48,12 +50,12 @@ class SectionFile:
48
50
 
49
51
  @classmethod
50
52
  def read(
51
- cls,
53
+ cls: type[_SectionFileT],
52
54
  content: str | bytes,
53
55
  *args: Any,
54
56
  version: str | None = None,
55
57
  **kwargs: Any,
56
- ) -> "SectionFile":
58
+ ) -> _SectionFileT:
57
59
  """Read from a file path or buffer. ``version`` selects a component set
58
60
  from VERSIONS without mutating the class."""
59
61
  components = cls.SECTIONS
@@ -86,11 +88,11 @@ class SectionFile:
86
88
 
87
89
  @classmethod
88
90
  def read_many(
89
- cls,
91
+ cls: type[_SectionFileT],
90
92
  paths: list[str],
91
93
  *,
92
94
  version: str | None = None,
93
- ) -> dict[str, "SectionFile"]:
95
+ ) -> dict[str, _SectionFileT]:
94
96
  """Read multiple files and return a dict keyed by file path.
95
97
 
96
98
  Parameters
@@ -88,7 +88,8 @@ configured ``StorageType``. That repository is what executes reading and writing
88
88
  to the storage backend (textual or binary).
89
89
 
90
90
  ``Line`` accepts an optional ``delimiter``: when provided, fields are separated by that
91
- character instead of occupying fixed positions.
91
+ character instead of occupying fixed positions. A line with fewer values than fields reads
92
+ each missing field as ``None``.
92
93
 
93
94
  .. code-block:: python
94
95
 
@@ -2,6 +2,7 @@ from cfinterface.adapters.components.line.repository import (
2
2
  BinaryRepository,
3
3
  TextualRepository,
4
4
  )
5
+ from cfinterface.components.integerfield import IntegerField
5
6
  from cfinterface.components.literalfield import LiteralField
6
7
 
7
8
 
@@ -49,6 +50,13 @@ def test_delimitedrepository_read_with_fields():
49
50
  assert values[1] == "world!"
50
51
 
51
52
 
53
+ def test_delimitedrepository_read_short_line_does_not_keep_previous_values():
54
+ fields = [LiteralField(6, 0), LiteralField(6, 7), IntegerField(4, 14)]
55
+ repo = TextualRepository(fields)
56
+ repo.read("a;b;7", delimiter=";")
57
+ assert repo.read("c", delimiter=";") == ["c", None, None]
58
+
59
+
52
60
  def test_delimitedrepository_write_no_fields():
53
61
  repo = TextualRepository([])
54
62
  assert len(repo.write([], delimiter=";")) == 1
@@ -643,6 +643,15 @@ def test_parse_lines_delimited_missing_tokens_filled_with_none() -> None:
643
643
  assert result["label"] == [None]
644
644
 
645
645
 
646
+ def test_parse_lines_delimited_short_line_after_full_line() -> None:
647
+ """A short line after a full one does not keep the full line's values."""
648
+ parser = _make_semicolon_parser()
649
+ lines = ["1;1.25;alpha\n", "10;5.00\n"]
650
+ result = parser.parse_lines(lines)
651
+ assert result["id"] == [1, 10]
652
+ assert result["label"] == ["alpha", None]
653
+
654
+
646
655
  def test_format_rows_semicolon_delimited() -> None:
647
656
  """format_rows with ';' delimiter joins fields with semicolons."""
648
657
  parser = _make_semicolon_parser()
@@ -0,0 +1,38 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+ from mypy import api
5
+
6
+ SUBCLASS_READS = """
7
+ from cfinterface.files.blockfile import BlockFile
8
+ from cfinterface.files.registerfile import RegisterFile
9
+ from cfinterface.files.sectionfile import SectionFile
10
+
11
+
12
+ class MySectionFile(SectionFile):
13
+ pass
14
+
15
+
16
+ class MyRegisterFile(RegisterFile):
17
+ pass
18
+
19
+
20
+ class MyBlockFile(BlockFile):
21
+ pass
22
+
23
+
24
+ section: MySectionFile = MySectionFile.read("file.txt")
25
+ register: MyRegisterFile = MyRegisterFile.read("file.txt")
26
+ block: MyBlockFile = MyBlockFile.read("file.txt")
27
+ sections: dict[str, MySectionFile] = MySectionFile.read_many(["file.txt"])
28
+ registers: dict[str, MyRegisterFile] = MyRegisterFile.read_many(["file.txt"])
29
+ blocks: dict[str, MyBlockFile] = MyBlockFile.read_many(["file.txt"])
30
+ """
31
+
32
+
33
+ @pytest.mark.slow
34
+ def test_read_is_typed_as_returning_the_subclass(tmp_path: Path):
35
+ stdout, stderr, status = api.run(
36
+ ["--cache-dir", str(tmp_path), "-c", SUBCLASS_READS]
37
+ )
38
+ assert status == 0, stdout + stderr
File without changes
File without changes
File without changes