Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
532 lines (503 loc) · 21.5 KB
/
Copy pathpyproject.toml
File metadata and controls
532 lines (503 loc) · 21.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
[project]
name = "maptasker"
version = "14.1.1"
description = "Utility to display/edit/analyze your entire Android 'Tasker' configuration in your browser."
authors = [
{ name = "Michael Rubin", email = "mikrubin@gmail.com" },
]
license = {text = "MIT"}
readme = "README_PyPl.md"
requires-python = ">=3.11, <4.0"
keywords = [
"tasker",
"Tasker",
"map tasker",
"configuration",
"tasker configuration",
]
classifiers = [
# "Development Status :: 1 - Planning",
# "Development Status :: 2 - Pre-Alpha",
# "Development Status :: 3 - Alpha",
# "Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
# "Development Status :: 6 - Mature",
# "Development Status :: 7 - Inactive",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
'darkdetect>=0.8.0',
'defusedxml>=0.7.1',
# aiutils imports it directly. NiceGUI happens to bring it in, but that is not a promise.
'httpx>=0.28.1',
'keyring>=25.7.0',
'nicegui>=3.17.1',
'packaging>=26.3',
'pillow>=12.3.0',
"platformdirs>=4.12.3",
'requests>=2.34.2',
'tomli_w>=1.2.0',
]
[project.optional-dependencies]
# Only the AI analysis features (and the AI question box) use these. MapTasker never installs
# them itself: `pip install "maptasker[ai]"` (or `uv add "maptasker[ai]"`) is how they arrive,
# and maputil3.AI_EXTRA_INSTALL_COMMAND is what the program tells the user to run.
ai = [
'anthropic>=1.11.0',
'google-genai>=2.28.0',
'ollama>=0.6.3',
'openai>=3.24.0',
'psutil>=7.2.2',
]
[project.urls]
Homepage = "https://github.com/mctinker/Map-Tasker"
"Bug Tracker" = "https://github.com/mctinker/Map-Tasker/issues"
Changelog = "https://github.com/mctinker/Map-Tasker/CHANGELOG.md"
[project.scripts]
maptasker = "maptasker.main:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.sdist]
include = [
"LICENSE",
"README_PyPl.md",
"maptasker/**/*",
"pyproject.toml",
"sample.prj.xml",
]
# Explicitly exclude hidden files, virtual environments, and config files
exclude = [
"/.venv",
"/.vscode",
"/.github",
"/tests",
"/XML",
"/*.lock",
"/.gitignore",
"/docs",
".claude",
]
[tool.uv]
# `uv sync` installs these and no others: the development tools, without the AI libraries.
# ai-dev and translate (dependency-groups, below) are opt-in with --group.
default-groups = ["dev"]
[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple"
publish-url = "https://test.pypi.org/legacy/"
explicit = true
[tool.hatch.build.targets.wheel]
# Ensure only the maptasker folder is treated as the package
packages = ["maptasker"]
[dependency-groups]
test = [
"pytest>=7.3.2",
"pytest-mock>=3.15.1",
"pytest-asyncio>=1.4.0",
"pytest-cov>=7.0.0",
]
# 'black' was removed: [tool.ruff.format] is the formatter for this project, and
# running both against the same files makes them fight over the same lines.
dev = [
# The test group, once, rather than a second copy of it that can drift.
{include-group = "test"},
"deep-translator>=1.11.4",
"import-linter>=2.15",
"matchify>=0.3.0",
"pip-autoremove>=0.10.0",
"pyright[nodejs]>=1.1.414",
"pytest>=9.1.1",
"rich>=15.0.0",
"ruff>=0.16.10",
"vulture>=2.16",
]
# Groups that a plain `uv sync` does NOT install (see default-groups under [tool.uv]).
# MapTasker's AI libraries are the optional "ai" extra, and nothing here should bring them
# into an ordinary development environment.
#
# The AI libraries themselves, for whoever works on the AI features or runs the type check
# (which resolves the imports in cria.py): uv sync --group ai-dev
ai-dev = [
"maptasker[ai]",
]
# ai-translator, which pulls in openai. Nothing in the repository imports it.
translate = [
"ai-translator>=0.1.0",
]
[tool.ruff]
include = ["pyproject.toml", "maptasker/main.py", "maptasker/src/**/*.py", "scripts/**/*.py"]
respect-gitignore = true
line-length = 120
show-fixes = true
src = ['src',]
# editor.formatOnSaveMode = "modificationsIfAvailable"
[tool.ruff.lint]
# The project's one logger lives in sysconst and is imported everywhere else. Naming it
# here lets ruff see that 'logger.exception(...)' records the traceback -- without it, a
# broad except that does log its failure still trips BLE001.
logger-objects = ["maptasker.src.sysconst.logger"]
# PLW1514 (open() with no encoding=) is a preview rule. On Windows the default encoding is
# the locale's (cp1252), not UTF-8, so an open() with no encoding garbles or rejects a
# non-ASCII Tasker name there and works fine on a Mac -- the kind of bug a Mac never shows.
# explicit-preview-rules keeps every OTHER preview rule off: a preview rule is enabled only
# when it is named in full below, never by a prefix such as 'PL'.
preview = true
explicit-preview-rules = true
select = [
'A', # Builtins
'ANN', # Annotations
'ARG', # Unused arguments
'B', # Bugbear
'BLE', # Blind except
'C4', # Comprehensions
'C90', # mccabe
'COM', # Commas
'D1', # Undocumented public elements
# 'D2', # Docstring conventions
'D3', # Triple double quotes
# 'D4', # Docstring text format
'DTZ', # Datetimes
'EM', # Error messages
# 'ERA', # Commented-out code
# 'EXE', # Executable
'F', # Pyflakes
'FA', # __future__ annotations
'FLY', # F-strings
# 'FURB', # Refurb
'G', # Logging format
'I', # Isort
'ICN', # Import conventions
'INP', # Disallow PEP-420 (Implicit namespace packages)
'INT', # gettext
'ISC', # Implicit str concat
# 'LOG', # Logging
'N', # PEP-8 Naming
'NPY', # Numpy
'PERF', # Unnecessary performance costs
'PGH', # Pygrep hooks
'PIE', # Unnecessary code
'PL', # Pylint
'PLW1514', # open() without an explicit encoding (a preview rule; see explicit-preview-rules)
'PT', # Pytest
# 'PTH', # Use Pathlib
'PYI', # Stub files
'Q', # Quotes
'RET', # Return
'RUF', # Ruff
'RSE', # Raise
'S', # Bandit
'SIM', # Code simplification
'SLF', # Private member access
'SLOT', # __slots__
'T10', # Debugger
'T20', # Print
'TCH', # Type checking
'TID', # Tidy imports
'TRY', # Exception handling
'UP', # Pyupgrade
'W', # Warnings
'YTT', # sys.version
]
ignore = [
"PLR2004", # Constant value comparison
"SIM115", # Missing "with" on open file
"S606", # No shell
"B009", # Do not perform function calls in argument defaults
# -- House style, not debt -------------------------------------------------
# This project imports ElementTree as 'ET' (and as 'ETW' where a writer-side
# alias is wanted). Ruff wants one specific alias and objects to the casing of
# both. 16 occurrences, all deliberate.
"ICN001", # unconventional-import-alias
"N814", # camelcase-imported-as-constant
"N817", # camelcase-imported-as-acronym
# The logger is written to with f-strings (100+ calls), and an EXPECTED failure --
# an OSError writing a report, say -- is logged as one line with logger.error, not
# as a traceback. Both rules only woke up when logger-objects (above) let ruff
# recognise the logger at all; nothing they report was newly introduced.
"G004", # logging-f-string
"TRY400", # error-instead-of-exception
# -- Formatter conflict ----------------------------------------------------
# Ruff emits a startup warning when COM812 is selected alongside its formatter,
# because the two disagree about trailing commas. The formatter already adds
# them, so the lint rule is redundant as well as conflicting.
"COM812", # missing-trailing-comma
]
[tool.ruff.lint.pycodestyle]
ignore-overlong-task-comments = true
[tool.ruff.lint.flake8-quotes]
docstring-quotes = 'double'
multiline-quotes = 'double'
[tool.ruff.lint.mccabe]
# Unlike Flake8, default to a complexity level of 15.
max-complexity = 20
[tool.ruff.lint.per-file-ignores]
# https://docs.astral.sh/ruff/rules/
'__init__.py' = ['F401','F403','F405',]
'tests/*' = ['ANN', 'ARG', 'INP001', 'S101',]
# scripts/ holds maintainer command-line tools, not a package, and they report by printing.
'scripts/*' = ['INP001', 'T201']
# capture_screens.py imports MapTasker inside its functions on purpose: serve() puts the chosen
# checkout on sys.path (--root) and checks the keyring and HOME are isolated before anything of
# MapTasker's is loaded, and playwright is only there for the driver half of the one file.
# S603: the one subprocess it starts is this same script, from the project's own interpreter.
'scripts/capture_screens.py' = ['PLC0415', 'S603']
# ---------------------------------------------------------------------------
# Deliberate, not debt. These stay.
# ---------------------------------------------------------------------------
# bundle.py embeds EN DASH (U+2013) in user-facing description text. That is
# correct typography, not a typo for a hyphen. 68 occurrences.
# Q003: it is generated by bldbndle via json.dumps, which double-quotes every
# string and escapes any quote inside it. Rewriting those to single quotes is a
# hand edit to a generated file, undone by the next rebuild.
'maptasker/src/bundle.py' = ['RUF001', 'Q003']
# format.py parses CSS selectors; a local named 'token' holding ">" trips the
# hardcoded-password heuristic. False positive.
# (S105 is listed in the backlog block below alongside that file's real items.)
# ---------------------------------------------------------------------------
# LINT BACKLOG -- 195 pre-existing violations across 36 files.
#
# Ruff was configured in this project but never installed, so none of it had
# ever run. Rather than ignore the rules globally -- which would let new code
# introduce the same problems anywhere -- each rule is switched off only for the
# specific file that already violates it. Every other file, and every new file,
# is fully linted, so CI fails on any NEW violation.
#
# This list is a ratchet: fix a file, delete its line. Do not add to it.
# Regenerate/verify with: ruff check --statistics
# ---------------------------------------------------------------------------
'maptasker/src/aiutils.py' = ['ISC004']
'maptasker/src/bldargs.py' = ['PERF401']
'maptasker/src/clip.py' = ['PGH004']
'maptasker/src/colors.py' = ['D100', 'PLR1722']
'maptasker/src/condition.py' = ['RET504']
'maptasker/src/cria.py' = ['ANN401', 'B904', 'D101', 'D102', 'D103', 'D105', 'D107', 'EM101', 'PLW0108', 'RET504', 'S603', 'S607', 'SIM210', 'TRY003', 'TRY300']
'maptasker/src/deprecate.py' = ['D100']
'maptasker/src/deviceinv.py' = ['ANN201', 'F811']
'maptasker/src/diagintr.py' = ['PLW2901']
'maptasker/src/diagram.py' = ['PLW2901']
'maptasker/src/format.py' = ['ARG002', 'C901', 'PLR0912', 'S105']
'maptasker/src/getfile.py' = ['ANN201', 'ANN202', 'D101', 'D102', 'N801', 'PLC0415']
'maptasker/src/getputer.py' = ['PERF403']
'maptasker/src/guiwins.py' = ['ARG001', 'ISC004']
# guiwins.py was split by dialog family into the guiwins_* modules below. These are its
# own backlog entries following the code out of it, not new debt -- the same oversized
# dialog builders, now in the file they belong to. guiwins.py's own line shrank by three
# rules in the move (PLR0912, RUF100, SIM117), and guiwins_canvas.py needs no line at all.
# The second split, into guiwins_views/_search/_nav, took C901 and SLF001 with it: the
# view the private attributes belong to, and the one function too complex, both went.
'maptasker/src/guiwins_designer_v2.py' = ['C901', 'SIM117']
'maptasker/src/guiwins_profedit.py' = ['C901', 'PLR0912', 'PLR0915']
'maptasker/src/guiwins_search.py' = ['SLF001']
'maptasker/src/guiwins_taskedit.py' = ['C901']
'maptasker/src/guiwins_views.py' = ['C901', 'ISC004', 'SLF001']
'maptasker/src/mapai.py' = ['F811']
'maptasker/src/mapit.py' = ['ANN001', 'ARG001']
'maptasker/src/mapjump.py' = ['I001']
'maptasker/src/mapswap.py' = ['C901', 'PLR0912']
'maptasker/src/maputil2.py' = ['A004']
'maptasker/src/outline.py' = ['PLW2901', 'SIM102']
'maptasker/src/prefers.py' = ['ISC001']
'maptasker/src/profiles.py' = ['ARG001', 'PLC0415']
'maptasker/src/projects.py' = ['ISC004']
'maptasker/src/projedit.py' = ['ISC004']
'maptasker/src/roundtrip.py' = ['D105']
'maptasker/src/sceneedit.py' = ['I001']
# sceneedit.py's own backlog entries, following the code that has them into its two halves.
'maptasker/src/sceneedit_legacy.py' = ['ARG001']
'maptasker/src/sceneedit_v2.py' = ['D102', 'D105', 'D107']
'maptasker/src/sceneview.py' = ['D102', 'D107', 'F541', 'ISC001', 'RUF001', 'SIM117']
'maptasker/src/servicec.py' = ['D100']
'maptasker/src/shelsort.py' = ['D100']
'maptasker/src/taskedit.py' = ['ANN001', 'ARG001', 'SIM108', 'TC003']
'maptasker/src/taskerd.py' = ['PLC0415']
# userintr.py's two entries moved with the Android handlers they belong to (see the guiwins
# note above): userintr.py itself no longer violates either rule, so it has no line.
'maptasker/src/userintr_android.py' = ['F841', 'RUF059']
[tool.ruff.lint.pylint]
max-args = 15
max-branches = 20
max-returns = 10
max-statements = 120
max-locals = 20
max-nested-blocks = 10
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = 'all'
[tool.ruff.format]
# bundle.py is generated by bldbndle from a Tasker backup, and its strings are full of
# the double quotes Tasker's own xml uses. The formatter would rewrite those lines to
# single quotes to save the escaping -- a 448-line rewrite of a file no one edits, that
# the next rebuild undoes, and that would churn again on any ruff release that revisits
# the choice. How a generated file is quoted is the generator's business.
exclude = ["maptasker/src/bundle.py"]
quote-style = "double"
# indent-style = "tab"
docstring-code-format = true
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Stable formatter style. Ruff's preview style is unstable by design -- it can
# change in any minor release -- which is a poor fit for the enforced
# `ruff format --check` gate in CI. Its only visible effect on this project was
# to break the first call of a method chain onto its own line, leaving a bare
# receiver ('ui', 'clean_text') stranded above it.
preview = false
# NOTE: a local, git-ignored pytest.ini used to shadow this block entirely --
# pytest.ini outranks pyproject.toml, so these settings never took effect on a
# developer machine and CI (which has no pytest.ini, it is not in the repo) would
# have run under different settings. This block is now the single source of truth;
# delete any leftover local pytest.ini so both run the same way.
[tool.pytest.ini_options]
pythonpath = ["."]
testpaths = ["tests"]
# 52 tests are async and rely on the @pytest.mark.asyncio marker.
asyncio_mode = "strict"
asyncio_default_fixture_loop_scope = "function"
[tool.coverage.run]
source = ["maptasker"]
branch = true
omit = [
# Generated lookup table, not logic worth covering.
"maptasker/src/bundle.py",
]
[tool.coverage.report]
show_missing = true
skip_covered = true
# Set from what CI measures: a checkout with no XML/, since XML/ holds personal Tasker
# data and is not in the repository. Every test that reads sample data reads
# tests/data/synthetic_backup.xml, with any backups in XML/ only as extra material, so a
# developer machine now measures the same as CI -- 57.5% on Python 3.11, 3.12 and 3.13
# alike, with or without XML/. The floor is a little under that, so a per-Python-version
# difference cannot fail the build while a real regression does.
#
# Raise this as coverage improves -- it is meant to ratchet upward, never down.
#
# History: 36 at 37.58% (754 tests) -> 44 at 45.41%, when the XML-parsing core
# (taskerd/tasks/profiles/projects/scenes/action/actargs) gained direct tests and
# went from 43% to 75% between them -> 42, recalibrated to CI's environment while
# tests still skipped without XML/ -> 56 at 57.5% (1847 tests), once they no longer did
# -> 60 at 62.25% (2300 tests), once the installer, backup fetch, Preferences, Task flags and the
# Fix, Restore and Impact dialogs had direct tests. Set about two points under, as before.
fail_under = 60
exclude_also = [
"if TYPE_CHECKING:",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"@(abc\\.)?abstractmethod",
]
# Checks that the annotations ruff's ANN rules require are actually right. Enforced by
# scripts/typecheck_ratchet.py against scripts/pyright_baseline.json, so only new errors fail.
[tool.pyright]
include = ["maptasker/main.py", "maptasker/src", "scripts"]
typeCheckingMode = "basic"
# The oldest Python requires-python allows, so code that needs a newer one is an error.
pythonVersion = "3.11"
# Fixed rather than taken from the machine it runs on: platform-specific modules (winreg)
# check differently per OS, and the baseline has to count the same on a Mac as in CI.
pythonPlatform = "Linux"
venvPath = "."
venv = ".venv"
# Import layering, checked by import-linter ('lint-imports') in CI. Three layers, each
# allowed to import only the ones below it:
# entry points -- start the program and pick CLI or GUI
# GUI -- NiceGUI windows, dialogs and their event handlers
# core -- reading, analysing, editing and reporting on a Tasker configuration
# so a GUI module cannot be pulled into the core by accident, and the core stays usable by
# the CLI and the tests without a web server. Every module in maptasker.src must be in
# exactly one layer ("exhaustive"): a new module fails the check until it is placed.
# Import cycles, and the imports deferred into functions to break them, are ratcheted
# separately by tests/test_import_structure.py.
[tool.importlinter]
root_package = "maptasker"
include_external_packages = true
# An import under "if TYPE_CHECKING:" never runs, so it can neither pull a module in at
# runtime nor close a cycle -- it is the sanctioned way to name a type from a module that
# must not be imported for real.
exclude_type_checking_imports = true
[[tool.importlinter.contracts]]
name = "Entry points above the GUI, the GUI above the core"
type = "layers"
containers = ["maptasker.src"]
exhaustive = true
layers = [
"""
mapit : progargs : runcli : rungui : clireports
""",
"""
getfile : guistate : guiutils : guiwins :
guiwins2 : guiwins_canvas : guiwins_designer_legacy : guiwins_designer_v2 : guiwins_designers : guiwins_editor :
guiwins_firesim : guiwins_fix : guiwins_impact : guiwins_legacyarg : guiwins_nav : guiwins_profedit :
guiwins_refactor : guiwins_restore : guiwins_sceneprops : guiwins_search : guiwins_taskedit :
guiwins_views : mapai : sceneview :
userintr : userintr_ai : userintr_android : userintr_editors :
userintr_loading : userintr_reports : userintr_settings : webassets
""",
"""
actargs : action : caches : codelint : actionc : actiond : actione : actionr :
actiont : addcss : aiutils : apikeys : appinv : bildhtml :
bldargs : bldbndle : bundle : caveats : clip : clock :
colors : colrmode : condition : condjoin : config : console :
cria : debug : deprecate : deviceinv : diagcnst : diagintr :
diagram : diagutil : diffload : dirout : editcommon : elemvals : error :
format : frontmtr : getbakup : getputer : globalvr :
guiutil2 : healthck : impact : initparg : lineout : livevars :
mapask : mapcache : mapexport : mapfind : mapfix : mapfonts :
mapjump : mappdf : maprefac : maprestore : mapswap : maputil2 :
maputil3 : maputils : mtexcept : nameattr : objprops : outdir : outline :
firesim : folderwatch : parsearg : piiscan : plugchk : plugset : prefers : presave : primitem : proclist :
profedit : profiles : proflint : proginit : projects : projedit :
property : roundtrip : runcfg : sceneedit : sceneedit_legacy : sceneedit_v2 : scenemodel : scenes : servicec :
sessundo : share : shelsort : sysconst : taskactn : taskedit :
taskerd : taskertables : taskervars : taskflow : tasks : taskuniq :
timecomp : timeline : translator : twisty : userhelp : valcodes : varxref :
xmldata : xmldiff
""",
]
[[tool.importlinter.contracts]]
name = "Only the GUI and the entry points import NiceGUI"
type = "protected"
protected_modules = ["nicegui"]
allowed_importers = [
"maptasker.src.getfile",
"maptasker.src.guistate",
"maptasker.src.guiutils",
"maptasker.src.guiwins",
"maptasker.src.guiwins2",
"maptasker.src.guiwins_canvas",
"maptasker.src.guiwins_designer_legacy",
"maptasker.src.guiwins_designer_v2",
"maptasker.src.guiwins_editor",
"maptasker.src.guiwins_firesim",
"maptasker.src.guiwins_fix",
"maptasker.src.guiwins_impact",
"maptasker.src.guiwins_legacyarg",
"maptasker.src.guiwins_nav",
"maptasker.src.guiwins_profedit",
"maptasker.src.guiwins_refactor",
"maptasker.src.guiwins_restore",
"maptasker.src.guiwins_sceneprops",
"maptasker.src.guiwins_search",
"maptasker.src.guiwins_taskedit",
"maptasker.src.guiwins_views",
"maptasker.src.mapai",
"maptasker.src.mapit",
"maptasker.src.progargs",
"maptasker.src.runcli",
"maptasker.src.rungui",
"maptasker.src.sceneview",
"maptasker.src.userintr",
"maptasker.src.userintr_ai",
"maptasker.src.userintr_android",
"maptasker.src.userintr_editors",
"maptasker.src.userintr_loading",
"maptasker.src.userintr_reports",
"maptasker.src.userintr_settings",
"maptasker.src.webassets",
]
# restart_program_subprocess stops the GUI's own server when a restart is started from a
# button. It imports NiceGUI only when an event loop is running -- that is, only when the
# GUI is up -- so the core still runs without it.
ignore_imports = [
"maptasker.src.maputils -> nicegui",
]