Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>PyriteNew to Visual Studio Code? Get it now.
Pyrite

Pyrite

daniel driechlinger

|
3 installs
| (0) | Free
Read Python as if it were Java: mirrors a Python project into a read-only, Java-flavored view for Java developers.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Pyrite

Read Python as if it were Java.

Developers today, Java developers included, increasingly write code with AI. A Java developer dropped into a Python codebase no longer has to close the language gap before contributing, because the AI can write the Python. The real risk is the opposite: shipping code you cannot read. That is vibe coding, and it leaves the developer unable to review, reason about, or take responsibility for what the application does.

Pyrite closes that gap on the reading side, so a Java developer on a Python project can practise Supervised Development instead of vibe coding: read the AI's output at a developer level, understand the design, review changes, and steer the application deliberately.

The name is the disclaimer. Pyrite is fool's gold: it looks like Java but is not, and it is never meant to compile. It is a faithful reading view of the Python underneath.

Pyrite mirrors every .py file in your workspace into a .java-view/ folder with the same directory structure, translating each module into a Java-flavored reading view. The view keeps Python's names, order, comments and docstrings, but uses Java syntax: braces, semicolons, explicit types, for (var x : xs), throw new ..., streams instead of comprehensions, and so on. It is meant to be read, not compiled.

my-project/                          my-project/.java-view/
├── main.py                          ├── main.java
└── inventory/                       └── inventory/
    ├── models.py            ==>         ├── models.java
    └── services/                        └── services/
        └── order_service.py                 └── order_service.java

Features (Phase 1: read-only mirror)

  • Generate Java view for the workspace – one command translates the whole tree.
  • Live sync – saving a Python file re-translates just that file (pyrite.watch).
  • Status bar report – the Pyrite logo sits in the status bar; hovering it shows how many files, classes, methods and fields the Java view holds, plus any failures, Python syntax errors and warnings, with buttons to regenerate or delete the view. Clicking the icon opens the same report.
  • Two-way navigation – Ctrl+Alt+J jumps from a Python line to the matching Java line and back, using a per-file line map.
  • Go to Definition – F12 / Ctrl+Click / right-click on a class, method or field name inside the Java view jumps to where it's declared, even in another mirrored file.
  • rules engine: deterministic, offline, instant. No API key, no network call.
  • CLI for CI or quick checks: npx pyrite <project-root>.

See architecture/ for design notes, including architecture/llm-engine.md: a second, LLM-backed engine used to live here and was pulled out to keep Pyrite offline and deterministic; its design is preserved there rather than deleted.

What the rules engine does

Python Java view
module order_service.py public final class OrderService { ... }
class Foo(Base): public static class Foo extends Base {
@dataclass fields, self.x = ... in __init__ field declarations with inferred types
def f(self, a: int, b="x") -> bool: public boolean f(int a, String b /* = "x" */) {
__init__, __str__, __eq__, __len__ ... constructor, toString(), equals(), size() ...
if __name__ == "__main__": public static void main(String[] args) {
for i in range(n) / for k, v in d.items() for (int i = 0; i < n; i++) / entrySet() loop
try/except X as e/finally, raise X(...) try/catch (X e)/finally, throw new X(...)
with open(p) as f: try (var f = open(p)) {
[f(x) for x in xs if c] xs.stream().filter(x -> c).map(x -> f(x)).toList()
sum, sorted(key=..), any/all, next, round(x, 2), map/filter, isinstance(x, (A, B)), open(p, "w"), ... .mapToDouble(..).sum(), .sorted(Comparator.comparing(..)), .anyMatch(..), it.next(), Math.round(x * 100.0) / 100.0, .map(..)/.filter(..), x instanceof A \|\| .., new PrintWriter(p), ...
value = a or default Objects.requireNonNullElse(a, default) (in a condition a or b stays a \|\| b)
@property def total(self) and order.total total() and order.total(); a trivial getter/setter pair in Lombok style becomes @Getter/@Setter on a field named after the property, read as order.getTotal()
x = repo.order(1) where order has a -> Order hint, for line in self.lines with lines: List[OrderLine] Order x = ..., for (OrderLine line : this.lines) - types follow hints on functions, methods (also from other files and base classes), fields and parameters
f"Hi {name}, {total:.2f}" "Hi " + name + ", " + String.format("%.2f", total)
x if c else y, and/or/not, None/True/False c ? x : y, &&/\|\|/!, null/true/false
Optional[T], list[int], dict[str, Any] T /* nullable */, List<Integer>, Map<String, Object>
ValueError, KeyError, ... IllegalArgumentException, NoSuchElementException, ...
docstrings / # comments Javadoc / // comments
inventory/__init__.py with only a docstring / __all__ nothing: Java packages are plain folders
inventory/__init__.py with real content (constants, re-exports) public final class Inventory, Javadoc names the original __init__.py; written to inventory/inventoryInit.java, the Init suffix hinting at __init__.py and keeping the file distinct from a sibling inventory.py on case-insensitive filesystems

Anything without a clean equivalent is kept and annotated with a /* ... */ comment rather than dropped, so the view is always complete.

A file with a Python syntax error is still translated, but its view starts with a // WARNING block naming the error, and a file the engine cannot handle at all gets a TRANSLATION FAILED placeholder rather than an outdated view. Syntax is checked with a real Python parser (tree-sitter, WebAssembly, offline).

Testing

npm test runs the unit tests, a snapshot of the sample project (src/test/snapshots/, refresh with UPDATE_SNAPSHOTS=1 npm test after an intended change) and a corpus run over the first 300 files of the local Python standard library: the engine must not throw, must emit balanced braces, must stay fast, and every class and function the real parser sees must come out as a symbol. Point it elsewhere with PYRITE_CORPUS=dir1:dir2, widen it with PYRITE_CORPUS_ALL=1. CI runs the full standard library on every push to main.

Getting started (development)

git clone https://github.com/danielonet/pyrite.git && cd pyrite
npm install
npm run compile
npm test                       # unit tests (node --test)
npm run cli -- sample-python-project   # writes sample-python-project/.java-view/

Open this folder in VS Code and press F5 to launch an Extension Development Host; open sample-python-project there and run Pyrite: Generate Java View for Workspace.

To build an installable package: npx @vscode/vsce package (produces a .vsix).

To publish a release to the Visual Studio Marketplace: npm run publish:marketplace (runs scripts/publish-marketplace.sh - builds, tests, packages and publishes). It reads the publisher id and Marketplace personal access token from a plain text file outside this repo (~/.config/pyrite/marketplace.env by default, never committed); the script prints a template and instructions the first time it's run. Use --dry-run to build and package without publishing.

Settings

Setting Default Meaning
pyrite.engine rules rules, or hybrid (rules + a local Ollama model for hard functions, see architecture/phase-2.md)
pyrite.ollama.url / .model / .timeoutSeconds / .maxFunctionsPerFile http://localhost:11434 / qwen2.5-coder:7b / 300 / 10 settings for the hybrid engine
pyrite.outputFolder .java-view where the mirror is written (relative to the workspace root)
pyrite.exclude venv, node_modules, ... glob patterns to skip
pyrite.watch true re-translate on save
pyrite.javadoc docstringOnly Javadoc generation: always (every class/method, extrapolated when there's no docstring), docstringOnly (only where a docstring exists), or none (no Javadoc; docstrings kept as plain comments)
pyrite.javadocTestCode false Document test code too, using the same pyrite.javadoc rules as production code. When unchecked, test files (test_*.py, *_test.py, conftest.py, or anything under a test/tests folder) never get Javadoc
pyrite.lombok true Use Lombok-style annotations instead of spelling out boilerplate: a plain self.x = x __init__ becomes @AllArgsConstructor, a trivial __str__/__repr__ becomes @ToString, a trivial __eq__/__hash__ becomes @EqualsAndHashCode, a @property/@x.setter pair that just wraps a field becomes @Getter/@Setter on that field, and a dataclass-like class becomes @Data. Methods that don't match these simple shapes are left spelled out
pyrite.lineWidth 120 Maximum line length of the generated Java. Longer lines wrap like IntelliJ IDEA and palantir-java-format by default: a signature or call that does not fit gets one parameter per line with an 8-space continuation indent, conditions break before &&/\|\|, concatenation before +, call chains before each .call(). Long string literals are never split. 0 turns wrapping off

The generated folder contains its own .gitignore so it is never committed.

Roadmap (Phase 2: edit the Java view, sync back)

Not implemented yet. See architecture/phase-2.md for the intended design (function-level, approval-gated, LLM-translated with the original Python as context) and open questions.

Limitations

  • The rules engine is line-oriented. Deeply nested comprehensions, exotic decorators, metaclasses and dynamic attribute tricks are kept verbatim with a comment.
  • Python truthiness (if items:) is annotated, not rewritten.
  • Types are inferred from hints, defaults and simple literals only; unknown types show as Object/var.
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft