pants-pyrefly 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pants-pyrefly
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A Pants plugin that runs the Pyrefly type checker in the `check` goal.
5
5
  License: Apache-2.0
6
6
  Keywords: pants,pantsbuild,pants-plugin,pyrefly,typecheck
@@ -48,7 +48,7 @@ Add the plugin and enable its backend in `pants.toml`:
48
48
 
49
49
  ```toml
50
50
  [GLOBAL]
51
- plugins = ["pants-pyrefly==0.2.0"]
51
+ plugins = ["pants-pyrefly==0.3.0"]
52
52
  backend_packages.add = [
53
53
  "pants.backend.python",
54
54
  "pants_pyrefly",
@@ -69,6 +69,20 @@ backend_packages.add = ["pants.backend.python", "pants_pyrefly"]
69
69
  If you keep plugin code in a dedicated `pants-plugins` resolve, add it there and run
70
70
  `pants generate-lockfiles`.
71
71
 
72
+ ## Getting started
73
+
74
+ Bootstrap a Pyrefly config for the repo (wraps `pyrefly init`). If you already have a MyPy or
75
+ Pyright configuration, it is migrated into the new `pyrefly.toml`:
76
+
77
+ ```bash
78
+ pants pyrefly-init # create pyrefly.toml (auto-migrates mypy/pyright)
79
+ pants pyrefly-init --pyrefly-init-migrate-from=mypy # force migrating from a MyPy config
80
+ ```
81
+
82
+ It refuses to overwrite an existing `pyrefly.toml` (or a `[tool.pyrefly]` table in
83
+ `pyproject.toml`) — remove it first to regenerate. Then run `pants pyrefly-lsp-config` (see
84
+ [Editor / IDE](#editor--ide-lsp)) so your editor resolves first-party imports the way Pants does.
85
+
72
86
  ## Usage
73
87
 
74
88
  ```bash
@@ -160,10 +174,26 @@ pants pyrefly-coverage --pyrefly-coverage-fail-under=80 :: # also fails if bel
160
174
  Pyrefly's `--python-interpreter-path` at it, so Pyrefly discovers `site-packages` and the target
161
175
  Python version exactly as `import` would at runtime.
162
176
 
177
+ ## Diagnostics
178
+
179
+ When Pyrefly resolves imports or the interpreter differently than you expect, dump the effective
180
+ configuration Pants assembles — the first-party `search-path`s, the interpreter used for third-party
181
+ resolution, and the config file in effect:
182
+
183
+ ```bash
184
+ pants pyrefly-dump-config :: # whole repo
185
+ pants pyrefly-dump-config src/project:: # a subtree
186
+ ```
187
+
188
+ This runs Pyrefly's `dump-config` subcommand with exactly the arguments Pants passes to `check`, so
189
+ what you see is what `pants check` sees. It does not type-check. When targets span multiple resolves
190
+ or interpreter constraints, each partition's config is printed under its own heading.
191
+
163
192
  ## Pants compatibility
164
193
 
165
194
  | Plugin version | Pants | Pyrefly (default) |
166
195
  | --- | --- | --- |
196
+ | `0.3.0` | `2.27`–`2.32` | `1.1.1` |
167
197
  | `0.2.0` | `2.27`–`2.32` | `1.1.1` |
168
198
  | `0.1.0` | `2.27`–`2.32` | `1.1.1` |
169
199
 
@@ -184,6 +214,20 @@ pants test :: # run the integration tests
184
214
  pants package pants-plugins/pants_pyrefly:dist # build the wheel + sdist into dist/
185
215
  ```
186
216
 
217
+ ### Bumping the pinned Pyrefly version
218
+
219
+ The four `default_known_versions` pins in `subsystems.py` (`<version>|<platform>|<sha256>|<size>`)
220
+ are generated, not hand-edited. To move to a new Pyrefly release:
221
+
222
+ ```bash
223
+ python3 build-support/bin/generate_known_versions.py --version <new> --write
224
+ ```
225
+
226
+ It reads the URL template and platform mapping straight from `subsystems.py`, fetches each asset's
227
+ published `.sha256` sidecar and size from the GitHub release, and rewrites `default_version` + the
228
+ pins. CI runs the same script with `--check` and fails if the committed pins drift from what the
229
+ release actually publishes. (Set `GITHUB_TOKEN` to avoid GitHub API rate limits.)
230
+
187
231
  ## Releasing
188
232
 
189
233
  Push a `vX.Y.Z` tag. The [release workflow](.github/workflows/release.yml) builds the wheel and
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import json
7
7
  import logging
8
+ import os
8
9
  from collections.abc import Iterable
9
10
 
10
11
  import toml # pants: no-infer-dep (provided by the Pants runtime)
@@ -42,12 +43,14 @@ from pants.engine.intrinsics import (
42
43
  merge_digests,
43
44
  path_globs_to_digest,
44
45
  )
46
+ from pants.core.util_rules.external_tool import download_external_tool
45
47
  from pants.engine.platform import Platform
46
- from pants.engine.process import ProcessCacheScope
48
+ from pants.engine.process import Process, ProcessCacheScope
47
49
  from pants.engine.rules import Rule, collect_rules, concurrently, goal_rule, implicitly
48
50
  from pants.engine.target import AllTargets, Targets
49
51
  from pants.engine.unions import UnionRule
50
- from pants.option.option_types import BoolOption, FloatOption
52
+ from pants.option.option_types import BoolOption, FloatOption, StrOption
53
+ from pants.util.logging import LogLevel
51
54
  from pants.util.strutil import softwrap
52
55
 
53
56
  logger = logging.getLogger(__name__)
@@ -406,5 +409,204 @@ async def pyrefly_suppress(
406
409
  return PyreflySuppress(exit_code=0)
407
410
 
408
411
 
412
+ # ---
413
+ # `pyrefly-init`
414
+ # ---
415
+
416
+ _MIGRATE_FROM_CHOICES = ("auto", "mypy", "pyright")
417
+
418
+
419
+ class PyreflyInitSubsystem(GoalSubsystem):
420
+ name = "pyrefly-init"
421
+ help = softwrap(
422
+ """
423
+ Bootstrap a Pyrefly config for this repo by running `pyrefly init`. Writes a `pyrefly.toml`
424
+ in the build root, migrating an existing MyPy or Pyright configuration when one is found.
425
+ Run once when adopting Pyrefly. After it writes the config, run `pants pyrefly-lsp-config`
426
+ to add Pants's source roots so the editor/LSP resolves first-party imports.
427
+ """
428
+ )
429
+
430
+ migrate_from = StrOption(
431
+ default=None,
432
+ help=softwrap(
433
+ """
434
+ Which existing type-checker config to migrate from: `auto` (try MyPy, then Pyright),
435
+ `mypy`, or `pyright`. When unset, Pyrefly's own default (`auto`) is used.
436
+ """
437
+ ),
438
+ )
439
+
440
+
441
+ class PyreflyInit(Goal):
442
+ subsystem_cls = PyreflyInitSubsystem
443
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
444
+
445
+
446
+ @goal_rule
447
+ async def pyrefly_init(
448
+ init_subsystem: PyreflyInitSubsystem,
449
+ pyrefly: Pyrefly,
450
+ workspace: Workspace,
451
+ platform: Platform,
452
+ ) -> PyreflyInit:
453
+ if (
454
+ init_subsystem.migrate_from is not None
455
+ and init_subsystem.migrate_from not in _MIGRATE_FROM_CHOICES
456
+ ):
457
+ logger.error(
458
+ f"`--pyrefly-init-migrate-from` must be one of {', '.join(_MIGRATE_FROM_CHOICES)}; "
459
+ f"got `{init_subsystem.migrate_from}`."
460
+ )
461
+ return PyreflyInit(exit_code=1)
462
+
463
+ # `pyrefly init` reads any existing type-checker config from the working directory; materialize
464
+ # the candidates into the sandbox (no source files are needed — init does not type-check).
465
+ downloaded_pyrefly, config_digest = await concurrently(
466
+ download_external_tool(pyrefly.get_request(platform)),
467
+ path_globs_to_digest(
468
+ PathGlobs(
469
+ ["pyproject.toml", "mypy.ini", "setup.cfg", "pyrefly.toml"],
470
+ glob_match_error_behavior=GlobMatchErrorBehavior.ignore,
471
+ )
472
+ ),
473
+ )
474
+ input_files = {fc.path: fc.content for fc in await get_digest_contents(config_digest)}
475
+
476
+ # `pyrefly init` refuses to overwrite an existing config; surface that as a clear message
477
+ # rather than a bare nonzero exit. The `b"[tool.pyrefly"` sentinel matches `config_request()`.
478
+ already_configured = "pyrefly.toml" in input_files or (
479
+ b"[tool.pyrefly" in input_files.get("pyproject.toml", b"")
480
+ )
481
+ if already_configured:
482
+ logger.error(
483
+ softwrap(
484
+ """
485
+ A Pyrefly config already exists (`pyrefly.toml`, or `[tool.pyrefly]` in
486
+ `pyproject.toml`). Remove it and re-run `pants pyrefly-init` to regenerate.
487
+ """
488
+ )
489
+ )
490
+ return PyreflyInit(exit_code=1)
491
+
492
+ tool_key = "__pyrefly_tool"
493
+ exe_path = os.path.normpath(os.path.join(tool_key, downloaded_pyrefly.exe))
494
+ argv = [exe_path, "init", "--non-interactive"]
495
+ if init_subsystem.migrate_from is not None:
496
+ argv += ["--migrate-from", init_subsystem.migrate_from]
497
+ argv.append(".")
498
+
499
+ result = await execute_process(
500
+ Process(
501
+ argv=tuple(argv),
502
+ input_digest=config_digest,
503
+ immutable_input_digests={tool_key: downloaded_pyrefly.digest},
504
+ # Capture both possible targets; a nonexistent output file is simply omitted, so this
505
+ # grabs whichever file `init` writes without us needing to predict it.
506
+ output_files=("pyrefly.toml", "pyproject.toml"),
507
+ description="Bootstrap a Pyrefly config (`pyrefly init`).",
508
+ level=LogLevel.DEBUG,
509
+ cache_scope=ProcessCacheScope.PER_SESSION,
510
+ ),
511
+ **implicitly(),
512
+ )
513
+ if result.exit_code != 0:
514
+ logger.error(
515
+ f"`pyrefly init` failed (exit {result.exit_code}):\n"
516
+ f"{result.stderr.decode(errors='replace')}"
517
+ )
518
+ return PyreflyInit(exit_code=result.exit_code)
519
+
520
+ # Write back only the files `init` actually created or changed (it also captures an untouched
521
+ # pyproject.toml as a maybe-target — don't rewrite it needlessly).
522
+ changed = [
523
+ fc
524
+ for fc in await get_digest_contents(result.output_digest)
525
+ if input_files.get(fc.path) != fc.content
526
+ ]
527
+ if not changed:
528
+ logger.warning("`pyrefly init` produced no config changes.")
529
+ return PyreflyInit(exit_code=0)
530
+
531
+ output_digest = await create_digest(CreateDigest(changed))
532
+ workspace.write_digest(output_digest)
533
+ logger.info(f"Wrote Pyrefly config: {', '.join(sorted(fc.path for fc in changed))}.")
534
+ return PyreflyInit(exit_code=0)
535
+
536
+
537
+ # ---
538
+ # `pyrefly-dump-config`
539
+ # ---
540
+
541
+
542
+ class PyreflyDumpConfigSubsystem(GoalSubsystem):
543
+ name = "pyrefly-dump-config"
544
+ help = softwrap(
545
+ """
546
+ Print the effective Pyrefly configuration Pants assembles for the targeted sources — the
547
+ first-party `search-path`s, the interpreter Pyrefly resolves third-party imports from, and
548
+ the config file in effect — by running Pyrefly's `dump-config` subcommand. Diagnostic only;
549
+ it does not type-check. Use it to debug import or interpreter resolution.
550
+ """
551
+ )
552
+
553
+
554
+ class PyreflyDumpConfig(Goal):
555
+ subsystem_cls = PyreflyDumpConfigSubsystem
556
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
557
+
558
+
559
+ @goal_rule
560
+ async def pyrefly_dump_config(
561
+ targets: Targets,
562
+ pyrefly: Pyrefly,
563
+ console: Console,
564
+ platform: Platform,
565
+ python_setup: PythonSetup,
566
+ ) -> PyreflyDumpConfig:
567
+ field_sets = tuple(
568
+ PyreflyFieldSet.create(tgt)
569
+ for tgt in targets
570
+ if PyreflyFieldSet.is_applicable(tgt) and not PyreflyFieldSet.opt_out(tgt)
571
+ )
572
+ if not field_sets:
573
+ logger.warning("No Pyrefly-applicable targets in scope.")
574
+ return PyreflyDumpConfig(exit_code=0)
575
+
576
+ partitions = list(
577
+ await pyrefly_determine_partitions(PyreflyRequest(field_sets), **implicitly())
578
+ )
579
+ processes = await concurrently(
580
+ _setup_pyrefly_process(
581
+ partition,
582
+ pyrefly,
583
+ platform,
584
+ python_setup,
585
+ # `dump-config` reports the config it would apply to the given files, mirroring what
586
+ # `check` sees; the argfile of files is passed just as it is for `check`.
587
+ subcommand=("dump-config",),
588
+ cache_scope=ProcessCacheScope.PER_SESSION,
589
+ )
590
+ for partition in partitions
591
+ )
592
+ results = await concurrently(execute_process(process, **implicitly()) for process in processes)
593
+
594
+ label_partitions = len(partitions) > 1
595
+ exit_code = 0
596
+ for partition, result in zip(partitions, results):
597
+ if result.exit_code != 0:
598
+ # `dump-config` is expected to exit 0; a nonzero code is a Pyrefly tool failure.
599
+ logger.error(
600
+ f"Pyrefly `dump-config` failed (exit {result.exit_code}) on partition "
601
+ f"({partition.description()}):\n{result.stderr.decode(errors='replace')}"
602
+ )
603
+ exit_code = result.exit_code
604
+ continue
605
+ if label_partitions:
606
+ console.print_stdout(f"# {partition.description()}")
607
+ console.print_stdout(result.stdout.decode(errors="replace").rstrip())
608
+ return PyreflyDumpConfig(exit_code=exit_code)
609
+
610
+
409
611
  def rules() -> Iterable[Rule | UnionRule]:
410
612
  return (*collect_rules(),)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pants-pyrefly
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A Pants plugin that runs the Pyrefly type checker in the `check` goal.
5
5
  License: Apache-2.0
6
6
  Keywords: pants,pantsbuild,pants-plugin,pyrefly,typecheck
@@ -48,7 +48,7 @@ Add the plugin and enable its backend in `pants.toml`:
48
48
 
49
49
  ```toml
50
50
  [GLOBAL]
51
- plugins = ["pants-pyrefly==0.2.0"]
51
+ plugins = ["pants-pyrefly==0.3.0"]
52
52
  backend_packages.add = [
53
53
  "pants.backend.python",
54
54
  "pants_pyrefly",
@@ -69,6 +69,20 @@ backend_packages.add = ["pants.backend.python", "pants_pyrefly"]
69
69
  If you keep plugin code in a dedicated `pants-plugins` resolve, add it there and run
70
70
  `pants generate-lockfiles`.
71
71
 
72
+ ## Getting started
73
+
74
+ Bootstrap a Pyrefly config for the repo (wraps `pyrefly init`). If you already have a MyPy or
75
+ Pyright configuration, it is migrated into the new `pyrefly.toml`:
76
+
77
+ ```bash
78
+ pants pyrefly-init # create pyrefly.toml (auto-migrates mypy/pyright)
79
+ pants pyrefly-init --pyrefly-init-migrate-from=mypy # force migrating from a MyPy config
80
+ ```
81
+
82
+ It refuses to overwrite an existing `pyrefly.toml` (or a `[tool.pyrefly]` table in
83
+ `pyproject.toml`) — remove it first to regenerate. Then run `pants pyrefly-lsp-config` (see
84
+ [Editor / IDE](#editor--ide-lsp)) so your editor resolves first-party imports the way Pants does.
85
+
72
86
  ## Usage
73
87
 
74
88
  ```bash
@@ -160,10 +174,26 @@ pants pyrefly-coverage --pyrefly-coverage-fail-under=80 :: # also fails if bel
160
174
  Pyrefly's `--python-interpreter-path` at it, so Pyrefly discovers `site-packages` and the target
161
175
  Python version exactly as `import` would at runtime.
162
176
 
177
+ ## Diagnostics
178
+
179
+ When Pyrefly resolves imports or the interpreter differently than you expect, dump the effective
180
+ configuration Pants assembles — the first-party `search-path`s, the interpreter used for third-party
181
+ resolution, and the config file in effect:
182
+
183
+ ```bash
184
+ pants pyrefly-dump-config :: # whole repo
185
+ pants pyrefly-dump-config src/project:: # a subtree
186
+ ```
187
+
188
+ This runs Pyrefly's `dump-config` subcommand with exactly the arguments Pants passes to `check`, so
189
+ what you see is what `pants check` sees. It does not type-check. When targets span multiple resolves
190
+ or interpreter constraints, each partition's config is printed under its own heading.
191
+
163
192
  ## Pants compatibility
164
193
 
165
194
  | Plugin version | Pants | Pyrefly (default) |
166
195
  | --- | --- | --- |
196
+ | `0.3.0` | `2.27`–`2.32` | `1.1.1` |
167
197
  | `0.2.0` | `2.27`–`2.32` | `1.1.1` |
168
198
  | `0.1.0` | `2.27`–`2.32` | `1.1.1` |
169
199
 
@@ -184,6 +214,20 @@ pants test :: # run the integration tests
184
214
  pants package pants-plugins/pants_pyrefly:dist # build the wheel + sdist into dist/
185
215
  ```
186
216
 
217
+ ### Bumping the pinned Pyrefly version
218
+
219
+ The four `default_known_versions` pins in `subsystems.py` (`<version>|<platform>|<sha256>|<size>`)
220
+ are generated, not hand-edited. To move to a new Pyrefly release:
221
+
222
+ ```bash
223
+ python3 build-support/bin/generate_known_versions.py --version <new> --write
224
+ ```
225
+
226
+ It reads the URL template and platform mapping straight from `subsystems.py`, fetches each asset's
227
+ published `.sha256` sidecar and size from the GitHub release, and rewrites `default_version` + the
228
+ pins. CI runs the same script with `--check` and fails if the committed pins drift from what the
229
+ release actually publishes. (Set `GITHUB_TOKEN` to avoid GitHub API rate limits.)
230
+
187
231
  ## Releasing
188
232
 
189
233
  Push a `vX.Y.Z` tag. The [release workflow](.github/workflows/release.yml) builds the wheel and
@@ -57,7 +57,7 @@ Add the plugin and enable its backend in `pants.toml`:
57
57
 
58
58
  ```toml
59
59
  [GLOBAL]
60
- plugins = ["pants-pyrefly==0.2.0"]
60
+ plugins = ["pants-pyrefly==0.3.0"]
61
61
  backend_packages.add = [
62
62
  "pants.backend.python",
63
63
  "pants_pyrefly",
@@ -78,6 +78,20 @@ backend_packages.add = ["pants.backend.python", "pants_pyrefly"]
78
78
  If you keep plugin code in a dedicated `pants-plugins` resolve, add it there and run
79
79
  `pants generate-lockfiles`.
80
80
 
81
+ ## Getting started
82
+
83
+ Bootstrap a Pyrefly config for the repo (wraps `pyrefly init`). If you already have a MyPy or
84
+ Pyright configuration, it is migrated into the new `pyrefly.toml`:
85
+
86
+ ```bash
87
+ pants pyrefly-init # create pyrefly.toml (auto-migrates mypy/pyright)
88
+ pants pyrefly-init --pyrefly-init-migrate-from=mypy # force migrating from a MyPy config
89
+ ```
90
+
91
+ It refuses to overwrite an existing `pyrefly.toml` (or a `[tool.pyrefly]` table in
92
+ `pyproject.toml`) — remove it first to regenerate. Then run `pants pyrefly-lsp-config` (see
93
+ [Editor / IDE](#editor--ide-lsp)) so your editor resolves first-party imports the way Pants does.
94
+
81
95
  ## Usage
82
96
 
83
97
  ```bash
@@ -169,10 +183,26 @@ pants pyrefly-coverage --pyrefly-coverage-fail-under=80 :: # also fails if bel
169
183
  Pyrefly's `--python-interpreter-path` at it, so Pyrefly discovers `site-packages` and the target
170
184
  Python version exactly as `import` would at runtime.
171
185
 
186
+ ## Diagnostics
187
+
188
+ When Pyrefly resolves imports or the interpreter differently than you expect, dump the effective
189
+ configuration Pants assembles — the first-party `search-path`s, the interpreter used for third-party
190
+ resolution, and the config file in effect:
191
+
192
+ ```bash
193
+ pants pyrefly-dump-config :: # whole repo
194
+ pants pyrefly-dump-config src/project:: # a subtree
195
+ ```
196
+
197
+ This runs Pyrefly's `dump-config` subcommand with exactly the arguments Pants passes to `check`, so
198
+ what you see is what `pants check` sees. It does not type-check. When targets span multiple resolves
199
+ or interpreter constraints, each partition's config is printed under its own heading.
200
+
172
201
  ## Pants compatibility
173
202
 
174
203
  | Plugin version | Pants | Pyrefly (default) |
175
204
  | --- | --- | --- |
205
+ | `0.3.0` | `2.27`–`2.32` | `1.1.1` |
176
206
  | `0.2.0` | `2.27`–`2.32` | `1.1.1` |
177
207
  | `0.1.0` | `2.27`–`2.32` | `1.1.1` |
178
208
 
@@ -193,6 +223,20 @@ pants test :: # run the integration tests
193
223
  pants package pants-plugins/pants_pyrefly:dist # build the wheel + sdist into dist/
194
224
  ```
195
225
 
226
+ ### Bumping the pinned Pyrefly version
227
+
228
+ The four `default_known_versions` pins in `subsystems.py` (`<version>|<platform>|<sha256>|<size>`)
229
+ are generated, not hand-edited. To move to a new Pyrefly release:
230
+
231
+ ```bash
232
+ python3 build-support/bin/generate_known_versions.py --version <new> --write
233
+ ```
234
+
235
+ It reads the URL template and platform mapping straight from `subsystems.py`, fetches each asset's
236
+ published `.sha256` sidecar and size from the GitHub release, and rewrites `default_version` + the
237
+ pins. CI runs the same script with `--check` and fails if the committed pins drift from what the
238
+ release actually publishes. (Set `GITHUB_TOKEN` to avoid GitHub API rate limits.)
239
+
196
240
  ## Releasing
197
241
 
198
242
  Push a `vX.Y.Z` tag. The [release workflow](.github/workflows/release.yml) builds the wheel and
@@ -213,5 +257,5 @@ no API tokens). Configure a PyPI trusted publisher for this repo + the `release.
213
257
  'pants_pyrefly',
214
258
  ),
215
259
  'python_requires': '>=3.11',
216
- 'version': '0.2.0',
260
+ 'version': '0.3.0',
217
261
  })
File without changes
File without changes