emcd-projects 1.15rc0__tar.gz → 1.17rc0__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 (27) hide show
  1. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/.gitignore +2 -0
  2. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/PKG-INFO +1 -1
  3. emcd_projects-1.17rc0/data/copier/answers-default.yaml +4 -0
  4. emcd_projects-1.17rc0/data/copier/answers-maximum.yaml +14 -0
  5. emcd_projects-1.17rc0/pyproject.toml +329 -0
  6. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__/imports.py +11 -9
  7. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__/preparation.py +4 -3
  8. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__init__.py +1 -1
  9. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/cli.py +13 -7
  10. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/interfaces.py +44 -2
  11. emcd_projects-1.17rc0/sources/emcdproj/template.py +127 -0
  12. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/website.py +18 -17
  13. emcd_projects-1.15rc0/pyproject.toml +0 -532
  14. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/LICENSE.txt +0 -0
  15. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/data/.gitignore +0 -0
  16. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/data/templates/coverage.svg.jinja +0 -0
  17. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/data/templates/website.html.jinja +0 -0
  18. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/README.rst +0 -0
  19. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__/__init__.py +0 -0
  20. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__/application.py +0 -0
  21. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__/distribution.py +0 -0
  22. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__/state.py +0 -0
  23. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/__main__.py +0 -0
  24. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/_typedecls/__builtins__.pyi +0 -0
  25. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/exceptions.py +0 -0
  26. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/filesystem.py +0 -0
  27. {emcd_projects-1.15rc0 → emcd_projects-1.17rc0}/sources/emcdproj/py.typed +0 -0
@@ -4,3 +4,5 @@
4
4
  __pycache__/
5
5
  bugs/
6
6
  dist/
7
+ CLAUDE.md
8
+ codex.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emcd-projects
3
- Version: 1.15rc0
3
+ Version: 1.17rc0
4
4
  Summary: Project management utilities.
5
5
  Project-URL: Homepage, https://github.com/emcd/python-project-common
6
6
  Project-URL: Documentation, https://emcd.github.io/python-project-common
@@ -0,0 +1,4 @@
1
+ project_name: python-test-defaults
2
+ distribution_name: emcdproj-defaults
3
+ package_name: defaults
4
+ description: 'All configuration options are defaults.'
@@ -0,0 +1,14 @@
1
+ project_name: python-test-maximum
2
+ distribution_name: emcdproj-maximum
3
+ package_name: maximum
4
+ description: 'All configuration options set.'
5
+ enable_rust_extension: true
6
+ include_data_resources: true
7
+ enable_property_tests: true
8
+ enable_publication: true
9
+ enable_cli: true
10
+ enable_executables: true
11
+ inject_foundations: true
12
+ inject_exceptions: true
13
+ inject_immutables: true
14
+ inject_docstring_utils: true
@@ -0,0 +1,329 @@
1
+ # vim: set filetype=toml fileencoding=utf-8:
2
+ # -*- mode: toml ; coding: utf-8 -*-
3
+
4
+ [build-system]
5
+ requires = [
6
+ 'hatchling',
7
+ ]
8
+ build-backend = 'hatchling.build'
9
+
10
+ [project]
11
+ name = 'emcd-projects'
12
+ description = 'Project management utilities.'
13
+ dynamic = [ 'version' ]
14
+ license = 'Apache-2.0'
15
+ readme = { 'file' = 'sources/emcdproj/README.rst', 'content-type' = 'text/x-rst' }
16
+ requires-python = '>= 3.10'
17
+ dependencies = [
18
+ 'Jinja2',
19
+ 'absence',
20
+ 'defusedxml',
21
+ 'frigid',
22
+ 'icecream-truck',
23
+ 'importlib-metadata', # TODO: Drop once we have separate appcore package.
24
+ 'importlib-resources', # TODO: Drop once we have separate appcore package.
25
+ 'packaging',
26
+ 'platformdirs', # TODO: Drop once we have separate appcore package.
27
+ 'tomli', # TODO: Drop once we have separate appcore package.
28
+ 'typing-extensions',
29
+ # --- BEGIN: Injected by Copier ---
30
+ 'tyro',
31
+ # --- END: Injected by Copier ---
32
+ ]
33
+ classifiers = [ # https://pypi.org/classifiers
34
+ 'Development Status :: 3 - Alpha',
35
+ 'Intended Audience :: Developers',
36
+ 'License :: OSI Approved :: Apache Software License',
37
+ 'Programming Language :: Python :: 3 :: Only',
38
+ # --- BEGIN: Injected by Copier ---
39
+ 'Programming Language :: Python :: 3.10',
40
+ 'Programming Language :: Python :: 3.11',
41
+ 'Programming Language :: Python :: 3.12',
42
+ 'Programming Language :: Python :: 3.13',
43
+ 'Programming Language :: Python :: Implementation :: CPython',
44
+ 'Programming Language :: Python :: Implementation :: PyPy',
45
+ # --- END: Injected by Copier ---
46
+ 'Topic :: Software Development',
47
+ ]
48
+ keywords = [ 'maintenance', 'project', 'template' ]
49
+ [[project.authors]]
50
+ name = 'Eric McDonald'
51
+ email = 'emcd@users.noreply.github.com'
52
+ [project.scripts]
53
+ emcdproj = 'emcdproj:main'
54
+ [project.urls]
55
+ 'Homepage' = 'https://github.com/emcd/python-project-common'
56
+ 'Documentation' = 'https://emcd.github.io/python-project-common'
57
+ 'Download' = 'https://pypi.org/project/emcd-projects/#files'
58
+ 'Source Code' = 'https://github.com/emcd/python-project-common'
59
+ 'Issue Tracker' = 'https://github.com/emcd/python-project-common/issues'
60
+
61
+ [tool.SELF]
62
+ year-of-origin = 2024
63
+
64
+ # https://coverage.readthedocs.io/en/latest/config.html
65
+ [tool.coverage.paths]
66
+ gha-runners = [
67
+ '/home/runner/work/python-project-common/python-project-common/',
68
+ '/Users/runner/work/python-project-common/python-project-common/',
69
+ 'D:\a\python-project-common\python-project-common\',
70
+ ]
71
+ [tool.coverage.run]
72
+ branch = true
73
+ command_line = '-m pytest' # TODO? '--fail-under'
74
+ data_file = '.auxiliary/caches/pytest/coverage.sqlite3'
75
+ parallel = true
76
+ source = [ 'sources' ]
77
+ [tool.coverage.html]
78
+ directory = '.auxiliary/artifacts/coverage-pytest'
79
+ [tool.coverage.xml]
80
+ output = '.auxiliary/artifacts/coverage-pytest/coverage.xml'
81
+
82
+ # https://hatch.pypa.io/latest/config/metadata/
83
+ [tool.hatch.build]
84
+ directory = '.auxiliary/artifacts/hatch-build'
85
+ [tool.hatch.build.targets.sdist]
86
+ only-include = [
87
+ 'sources/emcdproj',
88
+ # --- BEGIN: Injected by Copier ---
89
+ 'data',
90
+ # --- END: Injected by Copier ---
91
+ ]
92
+ strict-naming = false
93
+ [tool.hatch.build.targets.wheel]
94
+ only-include = [
95
+ 'sources/emcdproj',
96
+ # --- BEGIN: Injected by Copier ---
97
+ 'data',
98
+ # --- END: Injected by Copier ---
99
+ ]
100
+ strict-naming = false
101
+ [tool.hatch.build.targets.wheel.sources]
102
+ 'sources/emcdproj' = 'emcdproj'
103
+ # --- BEGIN: Injected by Copier ---
104
+ 'data' = 'emcdproj/data'
105
+ # --- END: Injected by Copier ---
106
+ [tool.hatch.envs.default]
107
+ python = '3.10'
108
+ [tool.hatch.envs.develop]
109
+ description = ''' Development environment. '''
110
+ dependencies = [
111
+ 'Jinja2',
112
+ 'coverage[toml]',
113
+ 'emcd-projects',
114
+ 'furo',
115
+ 'icecream-truck',
116
+ 'packaging',
117
+ 'pre-commit',
118
+ 'pyfakefs',
119
+ 'pyright',
120
+ 'pytest',
121
+ 'pytest-asyncio',
122
+ 'ruff',
123
+ 'sphinx',
124
+ 'sphinx-copybutton',
125
+ 'sphinx-inline-tabs',
126
+ 'towncrier',
127
+ # --- BEGIN: Injected by Copier ---
128
+ # --- END: Injected by Copier ---
129
+ ]
130
+ post-install-commands = [
131
+ # --- BEGIN: Injected by Copier ---
132
+ # --- END: Injected by Copier ---
133
+ ]
134
+ [tool.hatch.envs.develop.env-vars]
135
+ PYTHONUNBUFFERED = 'TRUE' # TODO: Only for coverage/pytest.
136
+ # --- BEGIN: Injected by Copier ---
137
+ # --- END: Injected by Copier ---
138
+ [tool.hatch.envs.develop.scripts]
139
+ docsgen = [
140
+ '''sphinx-build -E -b doctest -d .auxiliary/caches/sphinx \
141
+ documentation .auxiliary/artifacts/sphinx-doctest''',
142
+ '''sphinx-build -E -b linkcheck -d .auxiliary/caches/sphinx \
143
+ documentation .auxiliary/artifacts/sphinx-linkcheck''',
144
+ '''sphinx-build -a -d .auxiliary/caches/sphinx \
145
+ documentation .auxiliary/artifacts/sphinx-html''',
146
+ ]
147
+ linters = [
148
+ '''ruff check --quiet sources documentation tests''',
149
+ # --- BEGIN: Injected by Copier ---
150
+ # --- END: Injected by Copier ---
151
+ '''pyright sources''',
152
+ ]
153
+ packagers = [
154
+ '''hatch build''',
155
+ # --- BEGIN: Injected by Copier ---
156
+ # --- END: Injected by Copier ---
157
+ ]
158
+ testers = [
159
+ 'coverage erase',
160
+ 'coverage run',
161
+ 'coverage combine',
162
+ 'coverage report --skip-covered',
163
+ 'coverage html',
164
+ 'coverage xml',
165
+ ]
166
+ make-all = [
167
+ 'linters',
168
+ 'testers',
169
+ 'packagers',
170
+ 'docsgen',
171
+ ]
172
+ [tool.hatch.envs.qa]
173
+ description = ''' Quality assurance environment. '''
174
+ template = 'develop'
175
+ [[tool.hatch.envs.qa.matrix]]
176
+ python = [
177
+ '3.10',
178
+ '3.11',
179
+ '3.12',
180
+ '3.13',
181
+ 'pypy3.10',
182
+ ]
183
+ [tool.hatch.version]
184
+ path = 'sources/emcdproj/__init__.py'
185
+
186
+ # https://mypy.readthedocs.io/en/stable/config_file.html
187
+ [tool.mypy]
188
+ # Note: Due to repeated painful experiences with Mypy, we use Pyright instead.
189
+ # Pyright properly handles TypeVars, etc...
190
+ cache_dir = '.auxiliary/caches/mypy'
191
+ exclude = [ '.*' ] # Ignore everything
192
+ ignore_errors = true
193
+ follow_imports = 'skip'
194
+ pretty = true
195
+ strict = false
196
+
197
+ # https://microsoft.github.io/pyright/#/configuration
198
+ [tool.pyright]
199
+ ignore = [ 'tests' ] # Stronger hint for language server.
200
+ include = [ 'sources' ]
201
+ reportConstantRedefinition = true
202
+ reportInvalidTypeVarUse = true
203
+ reportMatchNotExhaustive = true
204
+ reportMissingImports = true
205
+ reportMissingTypeStubs = true
206
+ reportMissingTypeArgument = true
207
+ reportPossiblyUnboundVariable = false # Covered by other linters.
208
+ reportPrivateImportUsage = false # Covered by other linters.
209
+ reportPrivateUsage = false # Covered by other linters.
210
+ reportSelfClsParameterName = false # Too opinionated.
211
+ reportUnknownArgumentType = true
212
+ reportUnknownLambdaType = true
213
+ reportUnknownMemberType = true
214
+ reportUnknownParameterType = true
215
+ reportUnknownVariableType = true
216
+ reportUnnecessaryCast = true
217
+ reportUnnecessaryComparison = true
218
+ reportUntypedBaseClass = true
219
+ reportUntypedClassDecorator = true
220
+ reportUntypedFunctionDecorator = true
221
+ reportUntypedNamedTuple = true
222
+ reportUnusedExpression = true
223
+ reportUnusedImport = false # Covered by other linters.
224
+ reportUnusedVariable = false # Covered by other linters.
225
+ #strict = [ 'sources' ]
226
+ stubPath = 'sources/emcdproj/_typedecls'
227
+
228
+ [tool.pytest.ini_options]
229
+ # Note: Cannot run doctests from Pytest, because Pytest tries to update '_'
230
+ # attribute on protected modules. Instead, we use Sphinx to run doctests.
231
+ minversion = '8.1'
232
+ addopts = '--capture=no --exitfirst -rfE'
233
+ testpaths = [ 'tests' ]
234
+ python_files = [ 'test_*.py' ]
235
+ python_functions = [ 'test_[0-9][0-9][0-9]_*' ]
236
+ cache_dir = '.auxiliary/caches/pytest'
237
+
238
+ [tool.ruff]
239
+ builtins = [ 'ictr' ]
240
+ cache-dir = '.auxiliary/caches/ruff'
241
+ indent-width = 4
242
+ line-length = 79
243
+ [tool.ruff.lint]
244
+ dummy-variable-rgx = '^_$'
245
+ ignore = [
246
+ 'E701', # multiple-statements-on-one-line-colon
247
+ 'PLC0415', # import-outside-top-level
248
+ 'SIM300', # yoda-condition: scarred by assignment expressions, I am
249
+ ]
250
+ select = [ # default: E4, E7, E9, F
251
+ 'A', # Flake8 builtins rules
252
+ 'B006', # mutable-argument-default
253
+ 'B008', # function-call-in-default-argument
254
+ 'B011', # assert-false
255
+ 'B023', # function-uses-loop-variable
256
+ 'B904', # raise-without-from-inside-except
257
+ 'B909', # PREVIEW: loop-iterator-mutation
258
+ 'C90', # McCabe complexity rules
259
+ 'E101', # mixed-spaces-and-tabs
260
+ 'E111', # PREVIEW: indentation-with-invalid-multiple
261
+ 'E112', # PREVIEW: no-indented-block
262
+ 'E4', # Pycodestyle import rules
263
+ 'E501', # line-too-long
264
+ 'E7', # Pycodestyle general rules
265
+ 'E9', # Pycodestyle runtime errors
266
+ 'F', # Pyflakes rules
267
+ 'PERF', # Perflint rules
268
+ 'PLC', # Pylint convention rules
269
+ 'PLE', # Pylint error rules
270
+ 'PLR', # Pylint refactor rules
271
+ 'PLW', # Pylint warning rules
272
+ 'RET', # Flake8 return rules
273
+ 'RUF', # Ruff rules
274
+ 'S', # Flake8 Bandit rules
275
+ 'SIM', # Flake8 simplify rules
276
+ 'SLF', # Flake8 self rules
277
+ 'TRY', # Tryceratops rules
278
+ ]
279
+ [tool.ruff.lint.mccabe]
280
+ max-complexity = 12
281
+ [tool.ruff.lint.per-file-ignores]
282
+ '__init__.py' = [
283
+ 'F401', # unused-import
284
+ 'F403', # undefined-local-with-import-star
285
+ 'F405', # undefined-local-with-import-star-usage
286
+ ]
287
+ 'tests/**/*.py' = [
288
+ 'PLR0124', # comparison-with-itself
289
+ 'PLR0913', # too-many-arguments
290
+ 'PLR0915', # too-many-statements
291
+ 'PLR1704', # redefined-argument-from-local
292
+ 'PLR2004', # magic-value-comparison
293
+ 'PLW0129', # assert-on-string-literal
294
+ 'PLW0603', # global-statement
295
+ 'PLW0642', # self-assignment
296
+ 'S101', # assert
297
+ 'SLF001', # private-member-accessed
298
+ 'TRY', # Tryceratops rules
299
+ ]
300
+ [tool.ruff.lint.pylint]
301
+ max-locals = 10
302
+ max-public-methods = 10
303
+ max-statements = 30
304
+
305
+ [tool.towncrier]
306
+ directory = '.auxiliary/data/towncrier'
307
+ filename = 'documentation/changelog.rst'
308
+ package = 'emcdproj'
309
+ package_dir = 'sources'
310
+ [[tool.towncrier.type]]
311
+ # features and other improvements
312
+ directory = 'enhance'
313
+ name = 'Enhancements'
314
+ showcontent = true
315
+ [[tool.towncrier.type]]
316
+ # deprecations and other notices
317
+ directory = 'notify'
318
+ name = 'Notices'
319
+ showcontent = true
320
+ [[tool.towncrier.type]]
321
+ # removals of feature or platform support
322
+ directory = 'remove'
323
+ name = 'Removals'
324
+ showcontent = true
325
+ [[tool.towncrier.type]]
326
+ # bug fixes
327
+ directory = 'repair'
328
+ name = 'Repairs'
329
+ showcontent = true
@@ -20,20 +20,22 @@
20
20
 
21
21
  ''' Common imports and type aliases used throughout the package. '''
22
22
 
23
- # pylint: disable=unused-import
24
23
  # ruff: noqa: F401
25
24
 
26
25
 
27
26
  from __future__ import annotations
28
27
 
29
- import abc
30
- import collections.abc as cabc
31
- import contextlib as ctxl
32
- import json
33
- import math
34
- import os
35
- import shutil
36
- import types
28
+ import abc
29
+ import collections.abc as cabc
30
+ import contextlib as ctxl
31
+ import enum
32
+ import io
33
+ import json
34
+ import math
35
+ import os
36
+ import shutil
37
+ import sys
38
+ import types
37
39
 
38
40
  from pathlib import Path
39
41
 
@@ -30,9 +30,10 @@ from . import distribution as _distribution
30
30
  from . import state as _state
31
31
 
32
32
 
33
- async def prepare( # pylint: disable=too-many-arguments,too-many-locals
33
+ async def prepare(
34
34
  exits: __.ctxl.AsyncExitStack,
35
- application: _application.Information = _application.Information( ),
35
+ application: _application.Information = (
36
+ _application.Information( ) ), # noqa: B008
36
37
  # configedits: _dictedits.Edits = ( ),
37
38
  # configfile: __.Absential[ __.Path ] = __.absent,
38
39
  # environment: bool = False,
@@ -66,7 +67,7 @@ async def prepare( # pylint: disable=too-many-arguments,too-many-locals
66
67
  exits = exits )
67
68
  # if environment: await _environment.update( auxdata )
68
69
  # _inscribe_preparation_report( auxdata )
69
- return auxdata
70
+ return auxdata # noqa: RET504
70
71
 
71
72
 
72
73
  # def _inscribe_preparation_report( auxdata: _state.Globals ):
@@ -27,7 +27,7 @@ from . import exceptions
27
27
  # --- END: Injected by Copier ---
28
28
 
29
29
 
30
- __version__ = '1.15rc0'
30
+ __version__ = '1.17rc0'
31
31
 
32
32
 
33
33
  def main( ):
@@ -25,6 +25,7 @@ from __future__ import annotations
25
25
 
26
26
  from . import __
27
27
  from . import interfaces as _interfaces
28
+ from . import template as _template
28
29
  from . import website as _website
29
30
 
30
31
 
@@ -34,8 +35,10 @@ class VersionCommand(
34
35
  ):
35
36
  ''' Prints version information. '''
36
37
 
37
- async def __call__( self, auxdata: __.Globals ) -> None:
38
- from . import __version__ # pylint: disable=cyclic-import
38
+ async def __call__(
39
+ self, auxdata: __.Globals, display: _interfaces.ConsoleDisplay
40
+ ) -> None:
41
+ from . import __version__
39
42
  print( f"{__package__} {__version__}" )
40
43
  raise SystemExit( 0 )
41
44
 
@@ -48,8 +51,12 @@ class Cli(
48
51
 
49
52
  application: __.ApplicationInformation
50
53
  # configfile: __.typx.Optional[ str ] = None
51
- # display: ConsoleDisplay
54
+ display: _interfaces.ConsoleDisplay
52
55
  command: __.typx.Union[
56
+ __.typx.Annotated[
57
+ _template.CommandDispatcher,
58
+ __.tyro.conf.subcommand( 'template', prefix_name = False ),
59
+ ],
53
60
  __.typx.Annotated[
54
61
  _website.CommandDispatcher,
55
62
  __.tyro.conf.subcommand( 'website', prefix_name = False ),
@@ -66,8 +73,8 @@ class Cli(
66
73
  async with __.ctxl.AsyncExitStack( ) as exits:
67
74
  auxdata = await _prepare( exits = exits, **nomargs )
68
75
  ictr( 0 )( self.command )
69
- await self.command( auxdata = auxdata )
70
- # await self.command( auxdata = auxdata, display = self.display )
76
+ # await self.command( auxdata = auxdata )
77
+ await self.command( auxdata = auxdata, display = self.display )
71
78
 
72
79
  def prepare_invocation_args(
73
80
  self,
@@ -107,9 +114,8 @@ async def _prepare(
107
114
  import ictruck
108
115
  # TODO: Finetune Icecream truck installation from CLI arguments.
109
116
  ictruck.install( trace_levels = 9 )
110
- auxdata = await __.prepare(
117
+ return await __.prepare(
111
118
  application = application,
112
119
  # configedits = configedits,
113
120
  # environment = environment,
114
121
  exits = exits )
115
- return auxdata
@@ -26,7 +26,47 @@ from __future__ import annotations
26
26
  from . import __
27
27
 
28
28
 
29
- class CliCommand( # pylint: disable=invalid-metaclass
29
+ class DisplayStreams( __.enum.Enum ): # TODO: Python 3.11: StrEnum
30
+ # TODO: Protected class attributes.
31
+ ''' Stream upon which to place output. '''
32
+
33
+ Stderr = 'stderr'
34
+ Stdout = 'stdout'
35
+
36
+
37
+ class ConsoleDisplay(
38
+ metaclass = __.ImmutableDataclass,
39
+ ):
40
+ silence: __.typx.Annotated[
41
+ bool,
42
+ __.tyro.conf.arg(
43
+ aliases = ( '--quiet', '--silent', ), prefix_name = False ),
44
+ ] = False
45
+ file: __.typx.Annotated[
46
+ __.typx.Optional[ __.Path ],
47
+ __.tyro.conf.arg(
48
+ name = 'console-capture-file', prefix_name = False ),
49
+ ] = None
50
+ stream: __.typx.Annotated[
51
+ DisplayStreams,
52
+ __.tyro.conf.arg( name = 'console-stream', prefix_name = False ),
53
+ ] = DisplayStreams.Stderr
54
+
55
+ async def provide_stream( self ) -> __.io.TextIOWrapper:
56
+ ''' Provides output stream for display. '''
57
+ # TODO: register file stream as a process-lifetime exit
58
+ if self.file: return open( self.file, 'w' )
59
+ # TODO: async context manager for async file streams
60
+ # TODO: return async stream - need async printers
61
+ # TODO: handle non-TextIOWrapper streams
62
+ match self.stream:
63
+ case DisplayStreams.Stdout:
64
+ return __.sys.stdout # pyright: ignore[reportReturnType]
65
+ case DisplayStreams.Stderr:
66
+ return __.sys.stderr # pyright: ignore[reportReturnType]
67
+
68
+
69
+ class CliCommand(
30
70
  __.typx.Protocol,
31
71
  metaclass = __.ImmutableProtocolDataclass,
32
72
  decorators = ( __.typx.runtime_checkable, ),
@@ -34,7 +74,9 @@ class CliCommand( # pylint: disable=invalid-metaclass
34
74
  ''' CLI command. '''
35
75
 
36
76
  @__.abc.abstractmethod
37
- async def __call__( self, auxdata: __.Globals ) -> None:
77
+ async def __call__(
78
+ self, auxdata: __.Globals, display: ConsoleDisplay
79
+ ) -> None:
38
80
  ''' Executes command with global state. '''
39
81
  raise NotImplementedError
40
82
 
@@ -0,0 +1,127 @@
1
+ # vim: set filetype=python fileencoding=utf-8:
2
+ # -*- coding: utf-8 -*-
3
+
4
+ #============================================================================#
5
+ # #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); #
7
+ # you may not use this file except in compliance with the License. #
8
+ # You may obtain a copy of the License at #
9
+ # #
10
+ # http://www.apache.org/licenses/LICENSE-2.0 #
11
+ # #
12
+ # Unless required by applicable law or agreed to in writing, software #
13
+ # distributed under the License is distributed on an "AS IS" BASIS, #
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15
+ # See the License for the specific language governing permissions and #
16
+ # limitations under the License. #
17
+ # #
18
+ #============================================================================#
19
+
20
+
21
+ ''' Copier template maintenance and validation. '''
22
+
23
+
24
+ from __future__ import annotations
25
+
26
+ import subprocess as _subprocess
27
+ import tempfile as _tempfile
28
+
29
+ from . import __
30
+ from . import interfaces as _interfaces
31
+
32
+
33
+ class CommandDispatcher(
34
+ _interfaces.CliCommand, decorators = ( __.standard_tyro_class, ),
35
+ ):
36
+ ''' Dispatches commands for static website maintenance. '''
37
+
38
+ command: __.typx.Union[
39
+ __.typx.Annotated[
40
+ SurveyCommand,
41
+ __.tyro.conf.subcommand( 'survey', prefix_name = False ),
42
+ ],
43
+ __.typx.Annotated[
44
+ ValidateCommand,
45
+ __.tyro.conf.subcommand( 'validate', prefix_name = False ),
46
+ ],
47
+ ]
48
+
49
+ async def __call__(
50
+ self, auxdata: __.Globals, display: _interfaces.ConsoleDisplay
51
+ ) -> None:
52
+ ictr( 1 )( self.command )
53
+ await self.command( auxdata = auxdata, display = display )
54
+
55
+
56
+ class SurveyCommand(
57
+ _interfaces.CliCommand, decorators = ( __.standard_tyro_class, ),
58
+ ):
59
+ ''' Surveys available configuration variants. '''
60
+
61
+ async def __call__(
62
+ self, auxdata: __.Globals, display: _interfaces.ConsoleDisplay
63
+ ) -> None:
64
+ stream = await display.provide_stream( )
65
+ for variant in survey_variants( auxdata ):
66
+ print( variant, file = stream )
67
+
68
+
69
+ class ValidateCommand(
70
+ _interfaces.CliCommand, decorators = ( __.standard_tyro_class, ),
71
+ ):
72
+ ''' Validates template against configuration variant. '''
73
+
74
+ variant: __.typx.Annotated[
75
+ str,
76
+ __.typx.Doc( ''' Configuration variant to validate. ''' ),
77
+ __.tyro.conf.Positional,
78
+ ]
79
+
80
+ async def __call__(
81
+ self, auxdata: __.Globals, display: _interfaces.ConsoleDisplay
82
+ ) -> None:
83
+ ''' Copies new project from template for configuration variant. '''
84
+ # TODO: Validate variant argument.
85
+ validate_variant( auxdata, self.variant )
86
+
87
+
88
+ def copy_template( answers_file: __.Path, projectdir: __.Path ) -> None:
89
+ ''' Copies template to target directory using answers. '''
90
+ _subprocess.run( # noqa: S603
91
+ ( 'copier', 'copy', '--data-file', str( answers_file ),
92
+ '--defaults', '--overwrite', '--vcs-ref', 'HEAD',
93
+ '.', str( projectdir ) ),
94
+ cwd = __.Path( ), check = True )
95
+
96
+
97
+ def survey_variants( auxdata: __.Globals ) -> __.cabc.Sequence[ str ]:
98
+ ''' Surveys available configuration variants. '''
99
+ location = auxdata.distribution.provide_data_location( 'copier' )
100
+ return tuple(
101
+ fsent.stem.lstrip( 'answers-' )
102
+ for fsent in location.glob( 'answers-*.yaml' )
103
+ if fsent.is_file( ) )
104
+
105
+
106
+ def validate_variant( auxdata: __.Globals, variant: str ) -> None:
107
+ ''' Validates configuration variant. '''
108
+ answers_file = (
109
+ auxdata.distribution.provide_data_location(
110
+ 'copier', f"answers-{variant}.yaml" ) )
111
+ if not answers_file.is_file( ):
112
+ # TODO: Raise error.
113
+ return
114
+ with _tempfile.TemporaryDirectory( ) as tmpdir:
115
+ projectdir = __.Path( tmpdir ) / variant
116
+ copy_template( answers_file, projectdir )
117
+ validate_variant_project( projectdir )
118
+
119
+
120
+ def validate_variant_project( projectdir: __.Path ) -> None:
121
+ ''' Validates standard project as generated from template. '''
122
+ for command in (
123
+ ( 'hatch', '--env', 'develop', 'run',
124
+ 'python', '-m', 'pip', 'install',
125
+ '--upgrade', 'pip', 'build' ),
126
+ ( 'hatch', '--env', 'develop', 'run', 'make-all' ),
127
+ ): _subprocess.run( command, cwd = str( projectdir ), check = True ) # noqa: S603
@@ -33,8 +33,7 @@ from . import interfaces as _interfaces
33
33
 
34
34
 
35
35
  class CommandDispatcher(
36
- _interfaces.CliCommand,
37
- decorators = ( __.standard_tyro_class, ),
36
+ _interfaces.CliCommand, decorators = ( __.standard_tyro_class, ),
38
37
  ):
39
38
  ''' Dispatches commands for static website maintenance. '''
40
39
 
@@ -49,25 +48,27 @@ class CommandDispatcher(
49
48
  ],
50
49
  ]
51
50
 
52
- async def __call__( self, auxdata: __.Globals ) -> None:
51
+ async def __call__(
52
+ self, auxdata: __.Globals, display: _interfaces.ConsoleDisplay
53
+ ) -> None:
53
54
  ictr( 1 )( self.command )
54
- await self.command( auxdata = auxdata )
55
+ await self.command( auxdata = auxdata, display = display )
55
56
 
56
57
 
57
58
  class SurveyCommand(
58
- _interfaces.CliCommand,
59
- decorators = ( __.standard_tyro_class, ),
59
+ _interfaces.CliCommand, decorators = ( __.standard_tyro_class, ),
60
60
  ):
61
61
  ''' Surveys release versions published in static website. '''
62
62
 
63
- async def __call__( self, auxdata: __.Globals ) -> None:
63
+ async def __call__(
64
+ self, auxdata: __.Globals, display: _interfaces.ConsoleDisplay
65
+ ) -> None:
64
66
  # TODO: Implement.
65
67
  pass
66
68
 
67
69
 
68
70
  class UpdateCommand(
69
- _interfaces.CliCommand,
70
- decorators = ( __.standard_tyro_class, ),
71
+ _interfaces.CliCommand, decorators = ( __.standard_tyro_class, ),
71
72
  ):
72
73
  ''' Updates static website for particular release version. '''
73
74
 
@@ -77,7 +78,9 @@ class UpdateCommand(
77
78
  __.tyro.conf.Positional,
78
79
  ]
79
80
 
80
- async def __call__( self, auxdata: __.Globals ) -> None:
81
+ async def __call__(
82
+ self, auxdata: __.Globals, display: _interfaces.ConsoleDisplay
83
+ ) -> None:
81
84
  update( auxdata, self.version )
82
85
 
83
86
 
@@ -146,7 +149,7 @@ def update(
146
149
  locations.website.mkdir( exist_ok = True, parents = True )
147
150
  if locations.archive.is_file( ):
148
151
  with tarfile_open( locations.archive, 'r:xz' ) as archive:
149
- archive.extractall( path = locations.website )
152
+ archive.extractall( path = locations.website ) # noqa: S202
150
153
  available_species = _update_available_species( locations, version )
151
154
  j2context = _jinja2.Environment(
152
155
  loader = _jinja2.FileSystemLoader( locations.templates ),
@@ -157,7 +160,7 @@ def update(
157
160
  _update_coverage_badge( locations, j2context )
158
161
  ( locations.website / '.nojekyll' ).touch( )
159
162
  from .filesystem import chdir
160
- with chdir( locations.website ):
163
+ with chdir( locations.website ): # noqa: SIM117
161
164
  with tarfile_open( locations.archive, 'w:xz' ) as archive:
162
165
  archive.add( '.' )
163
166
 
@@ -195,7 +198,7 @@ def _update_available_species(
195
198
  return tuple( available_species )
196
199
 
197
200
 
198
- def _update_coverage_badge( # pylint: disable=too-many-locals
201
+ def _update_coverage_badge(
199
202
  locations: Locations, j2context: _jinja2.Environment
200
203
  ) -> None:
201
204
  ''' Updates coverage badge SVG.
@@ -207,11 +210,9 @@ def _update_coverage_badge( # pylint: disable=too-many-locals
207
210
  - green: >= 80%
208
211
  '''
209
212
  coverage = _extract_coverage( locations )
210
- # pylint: disable=magic-value-comparison
211
213
  color = (
212
- 'red' if coverage < 50 else (
213
- 'yellow' if coverage < 80 else 'green' ) )
214
- # pylint: enable=magic-value-comparison
214
+ 'red' if coverage < 50 else ( # noqa: PLR2004
215
+ 'yellow' if coverage < 80 else 'green' ) ) # noqa: PLR2004
215
216
  label_text = 'coverage'
216
217
  value_text = f"{coverage}%"
217
218
  label_width = len( label_text ) * 6 + 10
@@ -1,532 +0,0 @@
1
- # vim: set filetype=toml fileencoding=utf-8:
2
- # -*- mode: toml ; coding: utf-8 -*-
3
-
4
- [build-system]
5
- requires = [
6
- 'hatchling',
7
- ]
8
- build-backend = 'hatchling.build'
9
-
10
- [project]
11
- name = 'emcd-projects'
12
- description = 'Project management utilities.'
13
- dynamic = [ 'version' ]
14
- license = 'Apache-2.0'
15
- readme = { 'file' = 'sources/emcdproj/README.rst', 'content-type' = 'text/x-rst' }
16
- requires-python = '>= 3.10'
17
- dependencies = [
18
- 'Jinja2',
19
- 'absence',
20
- 'defusedxml',
21
- 'frigid',
22
- 'icecream-truck',
23
- 'importlib-metadata', # TODO: Drop once we have separate appcore package.
24
- 'importlib-resources', # TODO: Drop once we have separate appcore package.
25
- 'packaging',
26
- 'platformdirs', # TODO: Drop once we have separate appcore package.
27
- 'tomli', # TODO: Drop once we have separate appcore package.
28
- 'typing-extensions',
29
- # --- BEGIN: Injected by Copier ---
30
- 'tyro',
31
- # --- END: Injected by Copier ---
32
- ]
33
- classifiers = [ # https://pypi.org/classifiers
34
- 'Development Status :: 3 - Alpha',
35
- 'Intended Audience :: Developers',
36
- 'License :: OSI Approved :: Apache Software License',
37
- 'Programming Language :: Python :: 3 :: Only',
38
- # --- BEGIN: Injected by Copier ---
39
- 'Programming Language :: Python :: 3.10',
40
- 'Programming Language :: Python :: 3.11',
41
- 'Programming Language :: Python :: 3.12',
42
- 'Programming Language :: Python :: 3.13',
43
- 'Programming Language :: Python :: Implementation :: CPython',
44
- 'Programming Language :: Python :: Implementation :: PyPy',
45
- # --- END: Injected by Copier ---
46
- 'Topic :: Software Development',
47
- ]
48
- keywords = [ 'maintenance', 'project', 'template' ]
49
- [[project.authors]]
50
- name = 'Eric McDonald'
51
- email = 'emcd@users.noreply.github.com'
52
- [project.scripts]
53
- emcdproj = 'emcdproj:main'
54
- [project.urls]
55
- 'Homepage' = 'https://github.com/emcd/python-project-common'
56
- 'Documentation' = 'https://emcd.github.io/python-project-common'
57
- 'Download' = 'https://pypi.org/project/emcd-projects/#files'
58
- 'Source Code' = 'https://github.com/emcd/python-project-common'
59
- 'Issue Tracker' = 'https://github.com/emcd/python-project-common/issues'
60
-
61
- [tool.SELF]
62
- year-of-origin = 2024
63
-
64
- [tool.bandit]
65
- exclude_dirs = [ 'tests' ]
66
-
67
- # https://coverage.readthedocs.io/en/latest/config.html
68
- [tool.coverage.run]
69
- branch = true
70
- command_line = '-m pytest' # TODO? '--fail-under'
71
- data_file = '.auxiliary/caches/pytest/coverage.sqlite3'
72
- parallel = true
73
- source = [ 'sources' ]
74
- [tool.coverage.html]
75
- directory = '.auxiliary/artifacts/coverage-pytest'
76
- [tool.coverage.xml]
77
- output = '.auxiliary/artifacts/coverage-pytest/coverage.xml'
78
-
79
- # https://hatch.pypa.io/latest/config/metadata/
80
- [tool.hatch.build]
81
- directory = '.auxiliary/artifacts/hatch-build'
82
- [tool.hatch.build.targets.sdist]
83
- only-include = [
84
- 'sources/emcdproj',
85
- # --- BEGIN: Injected by Copier ---
86
- 'data',
87
- # --- END: Injected by Copier ---
88
- ]
89
- strict-naming = false
90
- [tool.hatch.build.targets.wheel]
91
- only-include = [
92
- 'sources/emcdproj',
93
- # --- BEGIN: Injected by Copier ---
94
- 'data',
95
- # --- END: Injected by Copier ---
96
- ]
97
- strict-naming = false
98
- [tool.hatch.build.targets.wheel.sources]
99
- 'sources/emcdproj' = 'emcdproj'
100
- # --- BEGIN: Injected by Copier ---
101
- 'data' = 'emcdproj/data'
102
- # --- END: Injected by Copier ---
103
- [tool.hatch.envs.default]
104
- python = '3.10'
105
- [tool.hatch.envs.develop]
106
- description = ''' Development environment. '''
107
- dependencies = [
108
- 'Jinja2',
109
- 'bandit',
110
- 'coverage[toml]',
111
- 'furo',
112
- 'icecream',
113
- 'isort',
114
- 'packaging',
115
- 'pre-commit',
116
- 'pyfakefs',
117
- 'pylint',
118
- 'pylint-per-file-ignores',
119
- 'pyright',
120
- 'pytest',
121
- 'pytest-asyncio',
122
- 'ruff',
123
- 'semgrep;platform_system!="Windows"', # https://github.com/returntocorp/semgrep/issues/1330
124
- 'sphinx',
125
- 'sphinx-copybutton',
126
- 'sphinx-inline-tabs',
127
- 'towncrier',
128
- 'tryceratops',
129
- 'yapf',
130
- # --- BEGIN: Injected by Copier ---
131
- # --- END: Injected by Copier ---
132
- ]
133
- post-install-commands = [
134
- # --- BEGIN: Injected by Copier ---
135
- # --- END: Injected by Copier ---
136
- ]
137
- [tool.hatch.envs.develop.env-vars]
138
- PYTHONUNBUFFERED = 'TRUE' # TODO: Only for coverage/pytest.
139
- # --- BEGIN: Injected by Copier ---
140
- # --- END: Injected by Copier ---
141
- [tool.hatch.envs.develop.scripts]
142
- docsgen = [
143
- '''sphinx-build -E -b doctest -d .auxiliary/caches/sphinx \
144
- documentation .auxiliary/artifacts/sphinx-doctest''',
145
- '''sphinx-build -E -b linkcheck -d .auxiliary/caches/sphinx \
146
- documentation .auxiliary/artifacts/sphinx-linkcheck''',
147
- '''sphinx-build -a -d .auxiliary/caches/sphinx \
148
- documentation .auxiliary/artifacts/sphinx-html''',
149
- ]
150
- linters = [
151
- '''ruff check --quiet sources documentation tests''',
152
- # --- BEGIN: Injected by Copier ---
153
- # --- END: Injected by Copier ---
154
- '''bandit --configfile pyproject.toml --quiet --recursive sources''',
155
- '''tryceratops sources''',
156
- '''pyright sources''',
157
- '''pylint --recursive yes sources documentation tests''',
158
- '''semgrep --config p/python --error --quiet --skip-unknown-extensions \
159
- sources/emcdproj''',
160
- # --- BEGIN: Injected by Copier ---
161
- # --- END: Injected by Copier ---
162
- ]
163
- packagers = [
164
- '''hatch build''',
165
- # --- BEGIN: Injected by Copier ---
166
- # --- END: Injected by Copier ---
167
- ]
168
- testers = [
169
- 'coverage erase',
170
- 'coverage run',
171
- 'coverage combine',
172
- 'coverage report --skip-covered',
173
- 'coverage html',
174
- 'coverage xml',
175
- ]
176
- make-all = [
177
- 'linters',
178
- 'testers',
179
- 'packagers',
180
- 'docsgen',
181
- ]
182
- [tool.hatch.envs.qa]
183
- description = ''' Quality assurance environment. '''
184
- template = 'develop'
185
- [[tool.hatch.envs.qa.matrix]]
186
- python = [
187
- '3.10',
188
- '3.11',
189
- '3.12',
190
- '3.13',
191
- 'pypy3.10',
192
- ]
193
- [tool.hatch.version]
194
- path = 'sources/emcdproj/__init__.py'
195
-
196
- # https://pycqa.github.io/isort/docs/configuration/options.html
197
- # https://pycqa.github.io/isort/docs/configuration/multi_line_output_modes.html
198
- [tool.isort]
199
- atomic = true
200
- #color = false
201
- #combine_as = false
202
- #combine_straight_imports = false
203
- #extra_standard_library = [ ]
204
- follow_links = false
205
- #force_alphabetical_sort_within_sections = false
206
- #float_to_top = false
207
- honor_noqa = true
208
- #ignore_whitespace = false
209
- include_trailing_comma = true
210
- # known_OTHER: substitute OTHER
211
- known_first_party = [ 'emcdproj' ]
212
- #known_third_party = [ ]
213
- #line_length = 79
214
- #lines_after_imports = -1 # automatic
215
- #lines_before_imports = -1 # automatic
216
- multi_line_output = 3 # vertical hanging indent
217
- #quiet = false
218
- #sections = [ 'FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER' ]
219
- skip_gitignore = true
220
- split_on_trailing_comma = true
221
- #src_paths = [ ]
222
- #star_first = false
223
- #supported_extensions = [ 'pxd', 'py', 'pyi', 'pyx' ]
224
- use_parentheses = true
225
-
226
- # https://mypy.readthedocs.io/en/stable/config_file.html
227
- [tool.mypy]
228
- # Note: Due to repeated painful experiences with Mypy, we use Pyright instead.
229
- # Pyright properly handles TypeVars, etc...
230
- cache_dir = '.auxiliary/caches/mypy'
231
- exclude = [ '.*' ] # Ignore everything
232
- ignore_errors = true
233
- follow_imports = 'skip'
234
- pretty = true
235
- strict = false
236
-
237
- # https://pylint.pycqa.org/en/latest/user_guide/configuration/index.html
238
- [tool.pylint.main]
239
- fail-under = 10
240
- # TODO: jobs: Consider parallelization if output interleaving, dependency
241
- # grouping, and similarities detection bugs have been fixed.
242
- load-plugins = [
243
- # 'pylint.extensions.bad_builtin',
244
- 'pylint.extensions.broad_try_clause',
245
- 'pylint.extensions.check_elif',
246
- 'pylint.extensions.code_style',
247
- 'pylint.extensions.confusing_elif',
248
- # 'pylint.extensions.consider_ternary_expression',
249
- 'pylint.extensions.dict_init_mutate',
250
- 'pylint.extensions.dunder',
251
- 'pylint.extensions.eq_without_hash',
252
- 'pylint.extensions.for_any_all',
253
- 'pylint.extensions.magic_value',
254
- 'pylint.extensions.mccabe',
255
- 'pylint.extensions.no_self_use',
256
- 'pylint.extensions.overlapping_exceptions',
257
- 'pylint.extensions.private_import',
258
- 'pylint.extensions.redefined_loop_name',
259
- 'pylint.extensions.redefined_variable_type',
260
- # 'pylint.extensions.set_membership',
261
- # 'pylint.extensions.while_used',
262
- 'pylint_per_file_ignores',
263
- ]
264
- recursive = false
265
- suggestion-mode = true
266
- [tool.pylint.basic]
267
- const-rgx = '([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$'
268
- good-names = [ '_', '__' ]
269
- include-naming-hint = true
270
- [tool.pylint.broad_try_clause]
271
- max-try-statements = 2
272
- [tool.pylint.classes]
273
- defining-attr-methods = [ '__init__', '__new__', '__post_init__' ]
274
- exclude-protected = [ ]
275
- valid-classmethod-first-arg = [ 'selfclass' ]
276
- valid-metaclass-classmethod-first-arg = [ 'clscls' ]
277
- [tool.pylint.design]
278
- # TODO: exclude-too-few-public-methods: Consider exception hierarchies.
279
- ignored-parents = [
280
- 'abc.ABCMeta',
281
- 'builtins.AttributeError',
282
- 'builtins.BaseException',
283
- 'builtins.Exception',
284
- 'builtins.KeyError',
285
- 'builtins.TypeError',
286
- 'builtins.ValueError',
287
- 'builtins.type',
288
- 'typing.Protocol',
289
- 'typing_extensions.Protocol',
290
- # --- BEGIN: Injected by Copier ---
291
- 'emcdproj.exceptions.Omnierror',
292
- 'emcdproj.exceptions.Omniexception',
293
- # --- END: Injected by Copier ---
294
- ]
295
- max-args = 5
296
- max-attributes = 7
297
- max-bool-expr = 3
298
- max-branches = 12
299
- max-locals = 10
300
- max-parents = 3
301
- max-public-methods = 10
302
- max-returns = 6
303
- max-statements = 30
304
- min-public-methods = 1
305
- [tool.pylint.exceptions]
306
- overgeneral-exceptions = [
307
- 'builtins.BaseException',
308
- 'builtins.Exception',
309
- # --- BEGIN: Injected by Copier ---
310
- 'emcdproj.exceptions.Omnierror',
311
- 'emcdproj.exceptions.Omniexception',
312
- # --- END: Injected by Copier ---
313
- ]
314
- [tool.pylint.format]
315
- ignore-long-lines = '''^(\s*<?https?://\S+>?|([^'"]*\s+)?#.*)$'''
316
- max-line-length = 79
317
- max-module-lines = 800
318
- single-line-class-stmt = true
319
- single-line-if-stmt = true
320
- [tool.pylint.imports]
321
- allow-wildcard-with-all = false
322
- # TODO: ext-import-graph
323
- # TODO: int-import-graph
324
- [tool.pylint.logging]
325
- logging-format-style = 'new'
326
- [tool.pylint.'messages control']
327
- disable = [
328
- 'bad-dunder-name',
329
- 'consider-using-assignment-expr',
330
- 'consider-using-f-string',
331
- 'duplicate-code', # TODO: Re-enable after heuristic is fixed.
332
- 'fixme',
333
- 'f-string-without-interpolation',
334
- 'import-outside-toplevel',
335
- 'invalid-name',
336
- 'logging-format-interpolation',
337
- 'logging-fstring-interpolation',
338
- 'multiple-statements',
339
- 'reimported',
340
- 'too-few-public-methods',
341
- 'too-many-positional-arguments', # 'too-many-arguments' is good enough
342
- 'ungrouped-imports',
343
- 'unused-wildcard-import',
344
- 'use-dict-literal',
345
- 'using-exception-groups-in-unsupported-version',
346
- 'wrong-import-order',
347
- 'wrong-import-position',
348
- ]
349
- # TODO: Latest 'per-file-ignores' code may supports dicts and lists in addition to strings.
350
- per-file-ignores = '''
351
- /tests/:attribute-defined-outside-init,comparison-with-itself,magic-value-comparison,missing-class-docstring,missing-function-docstring,protected-access,redefined-outer-name,singleton-comparison,superfluous-parens,too-many-try-statements,unexpected-keyword-arg
352
- __init__\.py:unused-import
353
- ''' # Note: Paths are regexes.
354
- [tool.pylint.refactoring]
355
- max-nested-blocks = 3
356
- never-returning-functions = [ 'sys.exit', 'argparse.parse_error' ]
357
- [tool.pylint.reports]
358
- msg-template = '{path} {line:3d},{column:2d} [{symbol}] {msg}'
359
- output-format = 'colorized'
360
- reports = false
361
- score = true
362
- [tool.pylint.similarities]
363
- ignore-comments = true
364
- ignore-docstrings = true
365
- ignore-imports = true
366
- ignore-signatures = true
367
- min-similarity-lines = 5
368
- [tool.pylint.spelling]
369
- max-spelling-suggestions = 4
370
- # TODO: spelling-dict
371
- [tool.pylint.typecheck]
372
- contextmanager-decorators = [ 'contextlib.contextmanager' ]
373
- ignored-checks-for-mixins = [
374
- 'attribute-defined-outside-init',
375
- 'no-member',
376
- 'not-async-context-manager',
377
- 'not-context-manager',
378
- ]
379
- #ignored-classes = [ 'thread._local', '_thread._local', 'argparse.Namespace' ]
380
- ignore-mixin-members = false
381
- missing-member-hint = true
382
- missing-member-hint-distance = 1
383
- missing-member-max-choices = 2
384
- #mixin-class-rgx = '.*[Mm]ixin'
385
- #signature-mutators = [ ]
386
- [tool.pylint.variables]
387
- additional-builtins = [ 'ictr' ]
388
- callbacks = [ ]
389
- dummy-variables-rgx = '''_$'''
390
- ignored-argument-names = '''_.*'''
391
- redefining-builtins-modules = [ 'builtins', 'io' ]
392
-
393
- # https://microsoft.github.io/pyright/#/configuration
394
- [tool.pyright]
395
- include = [ 'sources' ]
396
- ignore = [ 'tests' ]
397
- reportConstantRedefinition = true
398
- reportInvalidTypeVarUse = true
399
- reportMatchNotExhaustive = true
400
- reportMissingImports = true
401
- reportMissingTypeStubs = true
402
- reportMissingTypeArgument = true
403
- reportPossiblyUnboundVariable = false # Covered by other linters.
404
- reportPrivateImportUsage = false # Covered by other linters.
405
- reportPrivateUsage = false # Covered by other linters.
406
- reportSelfClsParameterName = false # Too opinionated.
407
- reportUnknownArgumentType = true
408
- reportUnknownLambdaType = true
409
- reportUnknownMemberType = true
410
- reportUnknownParameterType = true
411
- reportUnknownVariableType = true
412
- reportUnnecessaryCast = true
413
- reportUnnecessaryComparison = true
414
- reportUntypedBaseClass = true
415
- reportUntypedClassDecorator = true
416
- reportUntypedFunctionDecorator = true
417
- reportUntypedNamedTuple = true
418
- reportUnusedExpression = true
419
- reportUnusedImport = false # Covered by other linters.
420
- reportUnusedVariable = false # Covered by other linters.
421
- #strict = [ 'sources' ]
422
- stubPath = 'sources/emcdproj/_typedecls'
423
-
424
- [tool.pytest.ini_options]
425
- # Note: Cannot run doctests from Pytest, because Pytest tries to update '_'
426
- # attribute on protected modules. Instead, we use Sphinx to run doctests.
427
- minversion = '8.1'
428
- addopts = '--capture=no --exitfirst -rfE'
429
- testpaths = [ 'tests' ]
430
- python_files = [ 'test_*.py' ]
431
- python_functions = [ 'test_[0-9][0-9][0-9]_*' ]
432
- cache_dir = '.auxiliary/caches/pytest'
433
-
434
- [tool.ruff]
435
- builtins = [ 'ictr' ]
436
- cache-dir = '.auxiliary/caches/ruff'
437
- [tool.ruff.lint]
438
- ignore = [
439
- 'E701', # multiple-statements-on-one-line-colon
440
- ]
441
- [tool.ruff.lint.per-file-ignores]
442
- '__init__.py' = [
443
- 'F401', # unused-import
444
- 'F403', # undefined-local-with-import-star
445
- 'F405', # undefined-local-with-import-star-usage
446
- ]
447
-
448
- [tool.towncrier]
449
- directory = '.auxiliary/data/towncrier'
450
- filename = 'documentation/changelog.rst'
451
- package = 'emcdproj'
452
- package_dir = 'sources'
453
- [[tool.towncrier.type]]
454
- directory = 'bugfix'
455
- name = 'Bugfixes'
456
- showcontent = true
457
- [[tool.towncrier.type]]
458
- directory = 'docs'
459
- name = 'Documentation Improvements'
460
- showcontent = true
461
- [[tool.towncrier.type]]
462
- directory = 'feature'
463
- name = 'Features'
464
- showcontent = true
465
- [[tool.towncrier.type]]
466
- directory = 'platform'
467
- name = 'Supported Platforms'
468
- showcontent = true
469
- [[tool.towncrier.type]]
470
- directory = 'removal'
471
- name = 'Deprecations and Removals'
472
- showcontent = true
473
-
474
- [tool.tryceratops]
475
- exclude = [ 'tests' ]
476
-
477
- # https://github.com/google/yapf?tab=readme-ov-file#knobs
478
- # yapf --style-help
479
- [tool.yapf]
480
- align_closing_bracket_with_visual_indent = false
481
- allow_multiline_dictionary_keys = true
482
- allow_multiline_lambdas = true
483
- #allow_split_before_default_or_named_assigns = true
484
- #allow_split_before_dict_value = true
485
- blank_line_before_module_docstring = true
486
- #blank_line_before_nested_class_or_def = true
487
- #blank_lines_around_top_level_definition = 2
488
- blank_lines_between_top_level_imports_and_variables = 2
489
- #coalesce_brackets = false
490
- #column_limit = 79
491
- #continuation_align_style = 'SPACE'
492
- #continuation_indent_width = 4
493
- dedent_closing_brackets = true
494
- #disable_ending_comma_heuristic = false
495
- #disable_split_list_with_comment = false
496
- #each_dict_entry_on_separate_line = true
497
- #force_multiline_dict = false
498
- indent_dictionary_value = true
499
- #indent_width = 4
500
- join_multiple_lines = true
501
- #space_between_ending_comma_and_closing_bracket = true
502
- space_inside_brackets = true
503
- spaces_around_default_or_named_assign = true
504
- spaces_around_dict_delimiters = true
505
- spaces_around_list_delimiters = true
506
- spaces_around_power_operator = true
507
- spaces_around_subscript_colon = true
508
- spaces_around_tuple_delimiters = true
509
- #spaces_before_comment = 2
510
- #split_all_comma_separated_values = false
511
- #split_all_top_level_comma_separated_values = false
512
- split_arguments_when_comma_terminated = true
513
- split_before_arithmetic_operator = true
514
- split_before_bitwise_operator = true
515
- split_before_closing_bracket = false
516
- #split_before_dict_set_generator = true
517
- split_before_dot = true
518
- split_before_expression_after_opening_paren = true
519
- split_before_first_argument = true
520
- split_before_logical_operator = true
521
- #split_before_named_assigns = true
522
- split_complex_comprehension = true
523
- split_penalty_after_opening_bracket = 100 # default: 300
524
- #split_penalty_after_unary_operator = 10000
525
- #split_penalty_arithmetic_operator = 300
526
- #split_penalty_before_if_expr = 0
527
- #split_penalty_bitwise_operator = 300
528
- #split_penalty_comprehension = 80
529
- #split_penalty_excess_character = 7000
530
- #split_penalty_for_added_line_split = 30
531
- #split_penalty_import_names = 0
532
- #split_penalty_logical_operator = 300