pyproj-inspector


Namepyproj-inspector JSON
Version 0.1.2 PyPI version JSON
download
home_pageNone
SummaryAnalyze and package Python projects
upload_time2025-09-11 03:25:21
maintainerNone
docs_urlNone
authorAvi Twil
requires_python>=3.8
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            
# pyproj_inspector

Analyze a Python script or project, classify imports, reconstruct sources, and quickly package into distributables.
Authored by **Avi Twil**.

> Version: `0.1.2` • License: MIT • Python: 3.8+ • OS: Windows, macOS, Linux

---

## Table of Contents

- [Overview](#overview)
- [Key Features](#key-features)
- [Installation](#installation)
- [Quick Start](#quick-start)
  - [Analyze a single script](#analyze-a-single-script)
  - [Analyze a project directory](#analyze-a-project-directory)
- [Python API](#python-api)
  - [`PythonProject`](#pythonproject)
  - [`ProjectParseResult`](#projectparseresult)
  - [High-level Methods](#high-level-methods)
    - [`moduls()`](#moduls)
    - [`restore_to(target)`](#restore_totarget)
    - [`run_in_tmp_env(...)`](#run_in_tmp_env)
  - [Build Utilities](#build-utilities)
    - [`create_binary(...)`](#create_binary)
  - [Packaging Utilities](#packaging-utilities)
    - [`create_pypi_package(...)`](#create_pypi_package)
    - [`plan_pypi_version(...)`](#plan_pypi_version)
    - [`create_debian_package(...)`](#create_debian_package)
- [CLI Usage](#cli-usage)
- [How it works (Design Notes)](#how-it-works-design-notes)
- [Limitations & Notes](#limitations--notes)
- [Troubleshooting](#troubleshooting)
- [Development](#development)
  - [Project Layout](#project-layout)
  - [Run Tests](#run-tests)
- [Changelog](#changelog)
- [Author](#author)
- [License](#license)

---

## Overview

`pyproj_inspector` ingests either a **single Python file** or a **project directory**, parses all `.py` files, and builds a structured view of your codebase:

- Built-in (stdlib) imports
- External imports (PyPI distributions mapped from import names)
- Internal modules (top-level packages/modules contained in your project)
- A map of `relative_path -> source_code` for every file
- An optional entry script when a single file is analyzed

It also ships with utilities to:

- Materialize the analyzed project into a temporary or target directory
- Create binaries via **PyInstaller** or **Nuitka**
- Generate a ready-to-edit **`pyproject.toml`** for packaging to PyPI
- Build a Debian `.deb` package (when `dpkg-deb` is available)

---

## Key Features

- ⚙️ **Static analysis** via `ast` – resilient even if some files have syntax errors
- 🏷️ **Import classification**:
  - **Builtins** using `sys.stdlib_module_names` (fallback list included)
  - **External** mapped via `importlib.metadata.packages_distributions()` with a fallback **PyPI Simple** `HEAD` probe per import
  - **Internal** detected from **actual files** (even if not imported) and from relative imports
- 🧳 **Rehydration** – write all sources back to disk with original relative paths
- 🚀 **Ephemeral execution** – run your entry script in a fresh venv, optionally installing external deps
- 📦 **Packaging helpers** – binary via **PyInstaller**/**Nuitka**, **PyPI** metadata scaffold, **Debian** packages
- 🧪 **Comprehensive tests** – unit & edge-case coverage

---

## Installation

```bash
# From your project (editable install)
pip install -e .

# Or install from a wheel/sdist you build later
pip install pyproj_inspector-*.whl
```

> For **binary creation**, ensure you have the chosen tool installed in your environment:
> - PyInstaller: `pip install pyinstaller`
> - Nuitka: `pip install nuitka`
>
> For **Debian packaging**, you need `dpkg-deb` available on your system.

---

## Quick Start

### Analyze a single script

```python
from pyproj_inspector import PythonProject

proj = PythonProject("path/to/app.py")
print(proj.result.builtins)         # {'os', 'json', ...}
print(proj.result.external_imports) # {'requests': {'requests'}, ...}
print(proj.result.internal_modules) # {'app'}
print(proj.result.entry_relpath)    # 'app.py'
```

### Analyze a project directory

```python
from pyproj_inspector import PythonProject

proj = PythonProject("path/to/project_dir")
print(sorted(proj.moduls()))        # e.g. ['pkg', 'utils']
print(len(proj.result.files_code))  # number of .py files discovered
```

---

## Python API

### `PythonProject`

```python
PythonProject(path: str | os.PathLike)
```
- **`path`**: A `.py` file or a project directory.
- If a single file is passed, `entry_relpath` is set to its filename.

> Parsing is robust: files with syntax errors are still captured in `files_code` and simply skipped for AST import extraction.

### `ProjectParseResult`

```python
@dataclass
class ProjectParseResult:
    root: Path
    builtins: Set[str]
    external_imports: Dict[str, Set[str]]  # distribution -> {import names}
    internal_modules: Set[str]
    files_code: Dict[str, str]             # 'relative/path.py' -> source
    entry_relpath: Optional[str]           # when analyzing a single file
```
- **Builtins**: stdlib modules detected via `sys.stdlib_module_names` or a curated fallback set.
- **External imports**: resolved via `packages_distributions()`. Any unmapped names are *probingly* tested against PyPI’s simple index (`HEAD`) to guess a matching distribution (best effort).
- **Internal modules**: determined by the project’s file layout (top-level names from `*.py` and package dirs) + relative-import hints. For packages, `pkg/__init__.py` is reported as `pkg`.

### High-level Methods

#### `moduls()`

```python
proj.moduls() -> List[str]
```
Returns a **sorted** list of internal module names. This reflects the top-level modules/packages detected in your project’s tree (e.g., `['app', 'utils']`).

#### `restore_to(target)`

```python
proj.restore_to("out/dir") -> pathlib.Path
```
Writes every captured file from `files_code` to the given directory, preserving relative paths. It **overwrites** existing files.

#### `run_in_tmp_env(...)`

```python
proj.run_in_tmp_env(
    entry: Optional[str] = None,
    install: bool = True,
    env: Optional[Dict[str, str]] = None,
    args: Optional[List[str]] = None,
    python: Optional[str] = None,
) -> subprocess.CompletedProcess
```
- Creates a temp directory, restores all sources, bootstraps a virtual environment and **optionally installs external distributions** (keys from `external_imports`).
- **`entry`**: by default uses `entry_relpath` (if analyzing a single file). If missing, falls back to `__main__.py` or `main.py` when present.
- Returns a `CompletedProcess` with `stdout`, `stderr`, and `returncode`.

> Useful for quick smoke tests in isolation.

### Build Utilities

#### `create_binary(...)`

```python
from pyproj_inspector import create_binary

create_binary(
    project_root: str | os.PathLike,
    entry: str,
    mode: Literal["pyinstaller", "nuitka"] = "pyinstaller",
    onefile: bool = True,
    output_dir: Optional[str | os.PathLike] = None,
    extra_args: Optional[list[str]] = None,
) -> pathlib.Path
```
- Builds a standalone binary of `entry` using **PyInstaller** or **Nuitka**.
- Returns the path to the produced artifact.
- **Requirements**: the chosen backend must be installed and available in the current Python environment.

### Packaging Utilities

#### `create_pypi_package(...)`

```python
from pyproj_inspector import create_pypi_package

create_pypi_package(
    project_root: str | Path,
    package_name: str,
    version: Optional[str] = None,
    new: bool = True,
    creator_name: str = "Unknown",
    description: str = "Auto-generated package",
    homepage: str = "",
) -> Path
```
- Writes a **`pyproject.toml`** scaffold (PEP 621) and creates a package directory with `__init__.py`.
- When `new=True`, checks PyPI for name availability; if taken, raises `ValueError`.
- When `new=False`, fetches the latest published version and **bumps the patch** (e.g., `1.0.0 -> 1.0.1`), unless you pass a **higher** `version`, which takes precedence.

> The template is rendered using `string.Template` to avoid brace-related issues.

#### `plan_pypi_version(...)`

```python
from pyproj_inspector.packaging_utils import plan_pypi_version

plan = plan_pypi_version(name, version, new)
print(plan.name, plan.version, plan.is_new_project)
```
- Returns the chosen name/version and whether this is treated as a new project.

#### `create_debian_package(...)`

```python
from pyproj_inspector import packaging_utils
packaging_utils.create_debian_package(
    project_root: str | Path,
    package_name: str,
    version: str = "0.1.0",
    creator_name: str = "Unknown",
    entry: Optional[str] = None,
) -> Path
```
- Produces a Debian `.deb` by staging the project under `/usr/local/lib/<name>` with a basic `DEBIAN/control` file.
- If `entry` is provided, a launcher script is placed under `/usr/local/bin/<name>`.
- **Requirement**: `dpkg-deb` must be available.

---

## CLI Usage

```bash
# Print JSON analysis (builtins/external/internal/files)
pyproj_inspector <PATH> --json

# Build a binary
pyproj_inspector <PATH> binary --entry main.py --mode pyinstaller --onefile

# Scaffold PyPI packaging (pyproject.toml)
pyproj_inspector <PATH> pypi --name my_project --new --creator "Avi Twil"

# Build a Debian package
pyproj_inspector <PATH> deb --name my_project --version 0.1.0 --creator "Avi Twil" --entry main.py
```

> On Windows via PyCharm’s terminal, ensure the active interpreter has the required backend (e.g., `pip install pyinstaller`).

---

## How it works (Design Notes)

1. **Discovery**  
   - If `path` is a file: analyze just that file; set `entry_relpath` to its name.
   - If `path` is a directory: recursively collect `*.py` files.

2. **File capture**  
   - Read each file as UTF‑8, falling back to Latin‑1 when needed. All sources are stored in `files_code`.

3. **Internal modules**  
   - Derived from file layout (**top-level** names of `*.py` and package directories).  
   - `pkg/__init__.py` normalizes to `pkg`.  
   - Relative imports (e.g. `from . import x`) mark the current package as internal too.

4. **Import classification**  
   - Parse each file’s AST; collect top-level import names.  
   - Classify priority: **internal (if a file/dir exists)** → **stdlib** → **external**.

5. **External mapping**  
   - Use `packages_distributions()` when available.  
   - For unmapped names, probe **PyPI Simple** `HEAD` (`https://pypi.org/simple/<name>/`) to infer a plausible distribution name.

6. **Execution sandbox**  
   - `run_in_tmp_env()` creates an isolated venv, installs external distributions (if any), and executes the chosen entry script.

---

## Limitations & Notes

- **Namespace packages (PEP 420)**: currently not fully supported. A top-level directory without `__init__.py` may not always be recognized as a package.  
  _Planned_: heuristic support to treat existing directories as internal packages when imported.
- **External mapping is best-effort**: PyPI probing is a heuristic; unusual naming may require manual intervention.
- **Binary size/behavior**: depends on the chosen backend (PyInstaller/Nuitka) and your project’s specifics.
- **Network access**: PyPI checks require connectivity unless you inject your own mapping logic.

---

## Troubleshooting

- **`ValueError: Project name 'X' already exists on PyPI`**  
  Use a different `--name` or set `new=False` and pass a higher `--version`.
- **`FileNotFoundError` when running**  
  Ensure `entry` exists **in the analyzed sources** (`files_code`); if you analyze a directory, add the script first.
- **Binary build fails**  
  Verify the backend is installed: `pip show pyinstaller` / `pip show nuitka`. Check platform-specific notes of those tools.
- **Imports misclassified**  
  If you intentionally shadow stdlib (e.g., `json.py`), the tool prioritizes **internal** over stdlib—this is by design.

---

## Development

### Project Layout

```
pyproj_inspector/
  __init__.py
  inspector.py         # analysis core
  build_utils.py       # binary builders
  packaging_utils.py   # PyPI/DEB helpers
  cli.py               # command-line interface
tests/
  ...                  # unit & edge-case tests
pyproject.toml
README.md
```

### Run Tests

```bash
python -m pip install -U pip pytest
pytest -q
# or on Windows:
run_tests.bat
```

---

## Changelog

### 0.1.2
- Fix `pyproject.toml` template rendering using `string.Template`
- Normalize `pkg/__init__.py` → `pkg`
- Prefer **internal** over stdlib for shadowed names (e.g., local `json.py`)
- Register internal modules from file layout (even if not imported)
- CLI imports modules for easier monkeypatching in tests

---

## Author

**Avi Twil**

---

## License

MIT © Avi Twil

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pyproj-inspector",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": null,
    "author": "Avi Twil",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/5b/7a/0903fa37262476a4906db2979178f3ad1ebad911c6e0252a6964b4330956/pyproj_inspector-0.1.2.tar.gz",
    "platform": null,
    "description": "\r\n# pyproj_inspector\r\n\r\nAnalyze a Python script or project, classify imports, reconstruct sources, and quickly package into distributables.\r\nAuthored by **Avi Twil**.\r\n\r\n> Version: `0.1.2` \u2022 License: MIT \u2022 Python: 3.8+ \u2022 OS: Windows, macOS, Linux\r\n\r\n---\r\n\r\n## Table of Contents\r\n\r\n- [Overview](#overview)\r\n- [Key Features](#key-features)\r\n- [Installation](#installation)\r\n- [Quick Start](#quick-start)\r\n  - [Analyze a single script](#analyze-a-single-script)\r\n  - [Analyze a project directory](#analyze-a-project-directory)\r\n- [Python API](#python-api)\r\n  - [`PythonProject`](#pythonproject)\r\n  - [`ProjectParseResult`](#projectparseresult)\r\n  - [High-level Methods](#high-level-methods)\r\n    - [`moduls()`](#moduls)\r\n    - [`restore_to(target)`](#restore_totarget)\r\n    - [`run_in_tmp_env(...)`](#run_in_tmp_env)\r\n  - [Build Utilities](#build-utilities)\r\n    - [`create_binary(...)`](#create_binary)\r\n  - [Packaging Utilities](#packaging-utilities)\r\n    - [`create_pypi_package(...)`](#create_pypi_package)\r\n    - [`plan_pypi_version(...)`](#plan_pypi_version)\r\n    - [`create_debian_package(...)`](#create_debian_package)\r\n- [CLI Usage](#cli-usage)\r\n- [How it works (Design Notes)](#how-it-works-design-notes)\r\n- [Limitations & Notes](#limitations--notes)\r\n- [Troubleshooting](#troubleshooting)\r\n- [Development](#development)\r\n  - [Project Layout](#project-layout)\r\n  - [Run Tests](#run-tests)\r\n- [Changelog](#changelog)\r\n- [Author](#author)\r\n- [License](#license)\r\n\r\n---\r\n\r\n## Overview\r\n\r\n`pyproj_inspector` ingests either a **single Python file** or a **project directory**, parses all `.py` files, and builds a structured view of your codebase:\r\n\r\n- Built-in (stdlib) imports\r\n- External imports (PyPI distributions mapped from import names)\r\n- Internal modules (top-level packages/modules contained in your project)\r\n- A map of `relative_path -> source_code` for every file\r\n- An optional entry script when a single file is analyzed\r\n\r\nIt also ships with utilities to:\r\n\r\n- Materialize the analyzed project into a temporary or target directory\r\n- Create binaries via **PyInstaller** or **Nuitka**\r\n- Generate a ready-to-edit **`pyproject.toml`** for packaging to PyPI\r\n- Build a Debian `.deb` package (when `dpkg-deb` is available)\r\n\r\n---\r\n\r\n## Key Features\r\n\r\n- \u2699\ufe0f **Static analysis** via `ast` \u2013 resilient even if some files have syntax errors\r\n- \ud83c\udff7\ufe0f **Import classification**:\r\n  - **Builtins** using `sys.stdlib_module_names` (fallback list included)\r\n  - **External** mapped via `importlib.metadata.packages_distributions()` with a fallback **PyPI Simple** `HEAD` probe per import\r\n  - **Internal** detected from **actual files** (even if not imported) and from relative imports\r\n- \ud83e\uddf3 **Rehydration** \u2013 write all sources back to disk with original relative paths\r\n- \ud83d\ude80 **Ephemeral execution** \u2013 run your entry script in a fresh venv, optionally installing external deps\r\n- \ud83d\udce6 **Packaging helpers** \u2013 binary via **PyInstaller**/**Nuitka**, **PyPI** metadata scaffold, **Debian** packages\r\n- \ud83e\uddea **Comprehensive tests** \u2013 unit & edge-case coverage\r\n\r\n---\r\n\r\n## Installation\r\n\r\n```bash\r\n# From your project (editable install)\r\npip install -e .\r\n\r\n# Or install from a wheel/sdist you build later\r\npip install pyproj_inspector-*.whl\r\n```\r\n\r\n> For **binary creation**, ensure you have the chosen tool installed in your environment:\r\n> - PyInstaller: `pip install pyinstaller`\r\n> - Nuitka: `pip install nuitka`\r\n>\r\n> For **Debian packaging**, you need `dpkg-deb` available on your system.\r\n\r\n---\r\n\r\n## Quick Start\r\n\r\n### Analyze a single script\r\n\r\n```python\r\nfrom pyproj_inspector import PythonProject\r\n\r\nproj = PythonProject(\"path/to/app.py\")\r\nprint(proj.result.builtins)         # {'os', 'json', ...}\r\nprint(proj.result.external_imports) # {'requests': {'requests'}, ...}\r\nprint(proj.result.internal_modules) # {'app'}\r\nprint(proj.result.entry_relpath)    # 'app.py'\r\n```\r\n\r\n### Analyze a project directory\r\n\r\n```python\r\nfrom pyproj_inspector import PythonProject\r\n\r\nproj = PythonProject(\"path/to/project_dir\")\r\nprint(sorted(proj.moduls()))        # e.g. ['pkg', 'utils']\r\nprint(len(proj.result.files_code))  # number of .py files discovered\r\n```\r\n\r\n---\r\n\r\n## Python API\r\n\r\n### `PythonProject`\r\n\r\n```python\r\nPythonProject(path: str | os.PathLike)\r\n```\r\n- **`path`**: A `.py` file or a project directory.\r\n- If a single file is passed, `entry_relpath` is set to its filename.\r\n\r\n> Parsing is robust: files with syntax errors are still captured in `files_code` and simply skipped for AST import extraction.\r\n\r\n### `ProjectParseResult`\r\n\r\n```python\r\n@dataclass\r\nclass ProjectParseResult:\r\n    root: Path\r\n    builtins: Set[str]\r\n    external_imports: Dict[str, Set[str]]  # distribution -> {import names}\r\n    internal_modules: Set[str]\r\n    files_code: Dict[str, str]             # 'relative/path.py' -> source\r\n    entry_relpath: Optional[str]           # when analyzing a single file\r\n```\r\n- **Builtins**: stdlib modules detected via `sys.stdlib_module_names` or a curated fallback set.\r\n- **External imports**: resolved via `packages_distributions()`. Any unmapped names are *probingly* tested against PyPI\u2019s simple index (`HEAD`) to guess a matching distribution (best effort).\r\n- **Internal modules**: determined by the project\u2019s file layout (top-level names from `*.py` and package dirs) + relative-import hints. For packages, `pkg/__init__.py` is reported as `pkg`.\r\n\r\n### High-level Methods\r\n\r\n#### `moduls()`\r\n\r\n```python\r\nproj.moduls() -> List[str]\r\n```\r\nReturns a **sorted** list of internal module names. This reflects the top-level modules/packages detected in your project\u2019s tree (e.g., `['app', 'utils']`).\r\n\r\n#### `restore_to(target)`\r\n\r\n```python\r\nproj.restore_to(\"out/dir\") -> pathlib.Path\r\n```\r\nWrites every captured file from `files_code` to the given directory, preserving relative paths. It **overwrites** existing files.\r\n\r\n#### `run_in_tmp_env(...)`\r\n\r\n```python\r\nproj.run_in_tmp_env(\r\n    entry: Optional[str] = None,\r\n    install: bool = True,\r\n    env: Optional[Dict[str, str]] = None,\r\n    args: Optional[List[str]] = None,\r\n    python: Optional[str] = None,\r\n) -> subprocess.CompletedProcess\r\n```\r\n- Creates a temp directory, restores all sources, bootstraps a virtual environment and **optionally installs external distributions** (keys from `external_imports`).\r\n- **`entry`**: by default uses `entry_relpath` (if analyzing a single file). If missing, falls back to `__main__.py` or `main.py` when present.\r\n- Returns a `CompletedProcess` with `stdout`, `stderr`, and `returncode`.\r\n\r\n> Useful for quick smoke tests in isolation.\r\n\r\n### Build Utilities\r\n\r\n#### `create_binary(...)`\r\n\r\n```python\r\nfrom pyproj_inspector import create_binary\r\n\r\ncreate_binary(\r\n    project_root: str | os.PathLike,\r\n    entry: str,\r\n    mode: Literal[\"pyinstaller\", \"nuitka\"] = \"pyinstaller\",\r\n    onefile: bool = True,\r\n    output_dir: Optional[str | os.PathLike] = None,\r\n    extra_args: Optional[list[str]] = None,\r\n) -> pathlib.Path\r\n```\r\n- Builds a standalone binary of `entry` using **PyInstaller** or **Nuitka**.\r\n- Returns the path to the produced artifact.\r\n- **Requirements**: the chosen backend must be installed and available in the current Python environment.\r\n\r\n### Packaging Utilities\r\n\r\n#### `create_pypi_package(...)`\r\n\r\n```python\r\nfrom pyproj_inspector import create_pypi_package\r\n\r\ncreate_pypi_package(\r\n    project_root: str | Path,\r\n    package_name: str,\r\n    version: Optional[str] = None,\r\n    new: bool = True,\r\n    creator_name: str = \"Unknown\",\r\n    description: str = \"Auto-generated package\",\r\n    homepage: str = \"\",\r\n) -> Path\r\n```\r\n- Writes a **`pyproject.toml`** scaffold (PEP 621) and creates a package directory with `__init__.py`.\r\n- When `new=True`, checks PyPI for name availability; if taken, raises `ValueError`.\r\n- When `new=False`, fetches the latest published version and **bumps the patch** (e.g., `1.0.0 -> 1.0.1`), unless you pass a **higher** `version`, which takes precedence.\r\n\r\n> The template is rendered using `string.Template` to avoid brace-related issues.\r\n\r\n#### `plan_pypi_version(...)`\r\n\r\n```python\r\nfrom pyproj_inspector.packaging_utils import plan_pypi_version\r\n\r\nplan = plan_pypi_version(name, version, new)\r\nprint(plan.name, plan.version, plan.is_new_project)\r\n```\r\n- Returns the chosen name/version and whether this is treated as a new project.\r\n\r\n#### `create_debian_package(...)`\r\n\r\n```python\r\nfrom pyproj_inspector import packaging_utils\r\npackaging_utils.create_debian_package(\r\n    project_root: str | Path,\r\n    package_name: str,\r\n    version: str = \"0.1.0\",\r\n    creator_name: str = \"Unknown\",\r\n    entry: Optional[str] = None,\r\n) -> Path\r\n```\r\n- Produces a Debian `.deb` by staging the project under `/usr/local/lib/<name>` with a basic `DEBIAN/control` file.\r\n- If `entry` is provided, a launcher script is placed under `/usr/local/bin/<name>`.\r\n- **Requirement**: `dpkg-deb` must be available.\r\n\r\n---\r\n\r\n## CLI Usage\r\n\r\n```bash\r\n# Print JSON analysis (builtins/external/internal/files)\r\npyproj_inspector <PATH> --json\r\n\r\n# Build a binary\r\npyproj_inspector <PATH> binary --entry main.py --mode pyinstaller --onefile\r\n\r\n# Scaffold PyPI packaging (pyproject.toml)\r\npyproj_inspector <PATH> pypi --name my_project --new --creator \"Avi Twil\"\r\n\r\n# Build a Debian package\r\npyproj_inspector <PATH> deb --name my_project --version 0.1.0 --creator \"Avi Twil\" --entry main.py\r\n```\r\n\r\n> On Windows via PyCharm\u2019s terminal, ensure the active interpreter has the required backend (e.g., `pip install pyinstaller`).\r\n\r\n---\r\n\r\n## How it works (Design Notes)\r\n\r\n1. **Discovery**  \r\n   - If `path` is a file: analyze just that file; set `entry_relpath` to its name.\r\n   - If `path` is a directory: recursively collect `*.py` files.\r\n\r\n2. **File capture**  \r\n   - Read each file as UTF\u20118, falling back to Latin\u20111 when needed. All sources are stored in `files_code`.\r\n\r\n3. **Internal modules**  \r\n   - Derived from file layout (**top-level** names of `*.py` and package directories).  \r\n   - `pkg/__init__.py` normalizes to `pkg`.  \r\n   - Relative imports (e.g. `from . import x`) mark the current package as internal too.\r\n\r\n4. **Import classification**  \r\n   - Parse each file\u2019s AST; collect top-level import names.  \r\n   - Classify priority: **internal (if a file/dir exists)** \u2192 **stdlib** \u2192 **external**.\r\n\r\n5. **External mapping**  \r\n   - Use `packages_distributions()` when available.  \r\n   - For unmapped names, probe **PyPI Simple** `HEAD` (`https://pypi.org/simple/<name>/`) to infer a plausible distribution name.\r\n\r\n6. **Execution sandbox**  \r\n   - `run_in_tmp_env()` creates an isolated venv, installs external distributions (if any), and executes the chosen entry script.\r\n\r\n---\r\n\r\n## Limitations & Notes\r\n\r\n- **Namespace packages (PEP 420)**: currently not fully supported. A top-level directory without `__init__.py` may not always be recognized as a package.  \r\n  _Planned_: heuristic support to treat existing directories as internal packages when imported.\r\n- **External mapping is best-effort**: PyPI probing is a heuristic; unusual naming may require manual intervention.\r\n- **Binary size/behavior**: depends on the chosen backend (PyInstaller/Nuitka) and your project\u2019s specifics.\r\n- **Network access**: PyPI checks require connectivity unless you inject your own mapping logic.\r\n\r\n---\r\n\r\n## Troubleshooting\r\n\r\n- **`ValueError: Project name 'X' already exists on PyPI`**  \r\n  Use a different `--name` or set `new=False` and pass a higher `--version`.\r\n- **`FileNotFoundError` when running**  \r\n  Ensure `entry` exists **in the analyzed sources** (`files_code`); if you analyze a directory, add the script first.\r\n- **Binary build fails**  \r\n  Verify the backend is installed: `pip show pyinstaller` / `pip show nuitka`. Check platform-specific notes of those tools.\r\n- **Imports misclassified**  \r\n  If you intentionally shadow stdlib (e.g., `json.py`), the tool prioritizes **internal** over stdlib\u2014this is by design.\r\n\r\n---\r\n\r\n## Development\r\n\r\n### Project Layout\r\n\r\n```\r\npyproj_inspector/\r\n  __init__.py\r\n  inspector.py         # analysis core\r\n  build_utils.py       # binary builders\r\n  packaging_utils.py   # PyPI/DEB helpers\r\n  cli.py               # command-line interface\r\ntests/\r\n  ...                  # unit & edge-case tests\r\npyproject.toml\r\nREADME.md\r\n```\r\n\r\n### Run Tests\r\n\r\n```bash\r\npython -m pip install -U pip pytest\r\npytest -q\r\n# or on Windows:\r\nrun_tests.bat\r\n```\r\n\r\n---\r\n\r\n## Changelog\r\n\r\n### 0.1.2\r\n- Fix `pyproject.toml` template rendering using `string.Template`\r\n- Normalize `pkg/__init__.py` \u2192 `pkg`\r\n- Prefer **internal** over stdlib for shadowed names (e.g., local `json.py`)\r\n- Register internal modules from file layout (even if not imported)\r\n- CLI imports modules for easier monkeypatching in tests\r\n\r\n---\r\n\r\n## Author\r\n\r\n**Avi Twil**\r\n\r\n---\r\n\r\n## License\r\n\r\nMIT \u00a9 Avi Twil\r\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Analyze and package Python projects",
    "version": "0.1.2",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4af26f90148362826a8a9fb8a36fd12ba29b9141c25cce42ac44c0227d90c857",
                "md5": "134fa7b4bbf27edbcaffc127db0c56ff",
                "sha256": "9f135f1f1c9acc09c46f06b296d776546746c0850b03b25a0ede2359644ab73f"
            },
            "downloads": -1,
            "filename": "pyproj_inspector-0.1.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "134fa7b4bbf27edbcaffc127db0c56ff",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 13129,
            "upload_time": "2025-09-11T03:25:19",
            "upload_time_iso_8601": "2025-09-11T03:25:19.991027Z",
            "url": "https://files.pythonhosted.org/packages/4a/f2/6f90148362826a8a9fb8a36fd12ba29b9141c25cce42ac44c0227d90c857/pyproj_inspector-0.1.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5b7a0903fa37262476a4906db2979178f3ad1ebad911c6e0252a6964b4330956",
                "md5": "3eb7e89237abf3a2db2b9d19de324203",
                "sha256": "b23c4e8671ff559cb95fbb4a592a4eece6a7b6899bbedbb724741dd3d320f324"
            },
            "downloads": -1,
            "filename": "pyproj_inspector-0.1.2.tar.gz",
            "has_sig": false,
            "md5_digest": "3eb7e89237abf3a2db2b9d19de324203",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 19118,
            "upload_time": "2025-09-11T03:25:21",
            "upload_time_iso_8601": "2025-09-11T03:25:21.419427Z",
            "url": "https://files.pythonhosted.org/packages/5b/7a/0903fa37262476a4906db2979178f3ad1ebad911c6e0252a6964b4330956/pyproj_inspector-0.1.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-11 03:25:21",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "pyproj-inspector"
}
        
Elapsed time: 0.66326s