python-package-folder 1.2.0__tar.gz → 1.2.2__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.
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/PKG-INFO +23 -10
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/README.md +22 -9
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/analyzer.py +43 -1
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/publisher.py +39 -34
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/python_package_folder.py +2 -1
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/subfolder_build.py +124 -6
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/utils.py +7 -5
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/test_subfolder_build.py +73 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/.copier-answers.yml +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/.github/workflows/ci.yml +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/.github/workflows/publish.yml +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/.gitignore +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/.vscode/settings.json +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/LICENSE +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/Makefile +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/coverage.svg +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/development.md +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/installation.md +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/publishing.md +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/pyproject.toml +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/manager.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/types.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/version.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/test_linting.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/test_publisher.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/test_utils.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/test_version_manager.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/tests.py +0 -0
- {python_package_folder-1.2.0 → python_package_folder-1.2.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
|
|
5
5
|
Project-URL: Repository, https://github.com/alelom/python-package-folder
|
|
6
6
|
Author-email: Alessio Lombardi <work@alelom.com>
|
|
@@ -229,8 +229,8 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
|
|
|
229
229
|
1. **Build Verification**: Ensures distribution files exist in the `dist/` directory
|
|
230
230
|
2. **File Filtering**: Automatically filters distribution files to only include those matching the current package name and version (prevents uploading old artifacts)
|
|
231
231
|
3. **Credential Management**:
|
|
232
|
-
- Prompts for credentials if not provided
|
|
233
|
-
-
|
|
232
|
+
- Prompts for credentials if not provided via command-line arguments
|
|
233
|
+
- Credentials are not stored - you'll be prompted each time (unless provided via `--username` and `--password`)
|
|
234
234
|
- Supports both username/password and API tokens
|
|
235
235
|
- Auto-detects API tokens and uses `__token__` as username
|
|
236
236
|
4. **Repository Configuration**: Configures the target repository (PyPI, TestPyPI, or Azure)
|
|
@@ -247,6 +247,7 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
|
|
|
247
247
|
- If found, copies the subfolder README to project root (backing up the original parent README)
|
|
248
248
|
- If not found, creates a minimal README with just the folder name
|
|
249
249
|
5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
|
|
250
|
+
- `[build-system]` section using hatchling (replaces any existing build-system configuration)
|
|
250
251
|
- Subfolder-specific package name (derived or custom)
|
|
251
252
|
- Specified version
|
|
252
253
|
- Correct package path for hatchling
|
|
@@ -278,8 +279,9 @@ The tool automatically:
|
|
|
278
279
|
- Uses the current directory as the source directory if it contains Python files
|
|
279
280
|
- Falls back to `project_root/src` if the current directory isn't suitable
|
|
280
281
|
- **For subfolder builds**: Handles `pyproject.toml` configuration:
|
|
281
|
-
- **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily)
|
|
282
|
+
- **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
|
|
282
283
|
- **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
|
|
284
|
+
- `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
|
|
283
285
|
- Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
|
|
284
286
|
- Version from `--version` argument (defaults to `0.0.0` with a warning if not provided)
|
|
285
287
|
- Proper package path configuration for hatchling
|
|
@@ -637,11 +639,22 @@ publisher.publish()
|
|
|
637
639
|
|
|
638
640
|
### Credential Storage
|
|
639
641
|
|
|
640
|
-
The package
|
|
642
|
+
**Note**: The package does not store credentials by default. Credentials must be provided via command-line arguments (`--username` and `--password`) or will be prompted each time you run the publish command. This ensures credentials are not persisted and must be entered fresh each time.
|
|
641
643
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
644
|
+
If you previously used an older version that stored credentials in keyring, you can clear them using:
|
|
645
|
+
|
|
646
|
+
```python
|
|
647
|
+
from python_package_folder import Publisher, Repository
|
|
648
|
+
|
|
649
|
+
publisher = Publisher(repository=Repository.AZURE)
|
|
650
|
+
publisher.clear_stored_credentials()
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
Or manually using Python:
|
|
654
|
+
```python
|
|
655
|
+
import keyring
|
|
656
|
+
keyring.delete_password("python-package-folder-azure", "username")
|
|
657
|
+
# Also delete the password if you know the username
|
|
645
658
|
```
|
|
646
659
|
|
|
647
660
|
## Command Line Options
|
|
@@ -811,11 +824,11 @@ config.restore()
|
|
|
811
824
|
```
|
|
812
825
|
|
|
813
826
|
**Methods:**
|
|
814
|
-
- `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists, otherwise create temporary `pyproject.toml` with subfolder-specific configuration
|
|
827
|
+
- `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists (adjusting package paths and ensuring `[build-system]` uses hatchling), otherwise create temporary `pyproject.toml` with subfolder-specific configuration including `[build-system]` section using hatchling
|
|
815
828
|
- `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
|
|
816
829
|
|
|
817
830
|
**Note**: This class automatically:
|
|
818
|
-
- **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily). Otherwise, creates a temporary one from the parent configuration.
|
|
831
|
+
- **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily with adjusted package paths). Otherwise, creates a temporary one from the parent configuration. In both cases, the `[build-system]` section is always set to use hatchling, replacing any existing build-system configuration.
|
|
819
832
|
- **README handling**: If a README exists in the subfolder, it will be used instead of the parent README. If no README exists in the subfolder, a minimal README with just the folder name will be created. The original parent README is backed up and restored after the build completes.
|
|
820
833
|
- **Package initialization**: Creates `__init__.py` files if needed to make subfolders valid Python packages.
|
|
821
834
|
|
|
@@ -209,8 +209,8 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
|
|
|
209
209
|
1. **Build Verification**: Ensures distribution files exist in the `dist/` directory
|
|
210
210
|
2. **File Filtering**: Automatically filters distribution files to only include those matching the current package name and version (prevents uploading old artifacts)
|
|
211
211
|
3. **Credential Management**:
|
|
212
|
-
- Prompts for credentials if not provided
|
|
213
|
-
-
|
|
212
|
+
- Prompts for credentials if not provided via command-line arguments
|
|
213
|
+
- Credentials are not stored - you'll be prompted each time (unless provided via `--username` and `--password`)
|
|
214
214
|
- Supports both username/password and API tokens
|
|
215
215
|
- Auto-detects API tokens and uses `__token__` as username
|
|
216
216
|
4. **Repository Configuration**: Configures the target repository (PyPI, TestPyPI, or Azure)
|
|
@@ -227,6 +227,7 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
|
|
|
227
227
|
- If found, copies the subfolder README to project root (backing up the original parent README)
|
|
228
228
|
- If not found, creates a minimal README with just the folder name
|
|
229
229
|
5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
|
|
230
|
+
- `[build-system]` section using hatchling (replaces any existing build-system configuration)
|
|
230
231
|
- Subfolder-specific package name (derived or custom)
|
|
231
232
|
- Specified version
|
|
232
233
|
- Correct package path for hatchling
|
|
@@ -258,8 +259,9 @@ The tool automatically:
|
|
|
258
259
|
- Uses the current directory as the source directory if it contains Python files
|
|
259
260
|
- Falls back to `project_root/src` if the current directory isn't suitable
|
|
260
261
|
- **For subfolder builds**: Handles `pyproject.toml` configuration:
|
|
261
|
-
- **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily)
|
|
262
|
+
- **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
|
|
262
263
|
- **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
|
|
264
|
+
- `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
|
|
263
265
|
- Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
|
|
264
266
|
- Version from `--version` argument (defaults to `0.0.0` with a warning if not provided)
|
|
265
267
|
- Proper package path configuration for hatchling
|
|
@@ -617,11 +619,22 @@ publisher.publish()
|
|
|
617
619
|
|
|
618
620
|
### Credential Storage
|
|
619
621
|
|
|
620
|
-
The package
|
|
622
|
+
**Note**: The package does not store credentials by default. Credentials must be provided via command-line arguments (`--username` and `--password`) or will be prompted each time you run the publish command. This ensures credentials are not persisted and must be entered fresh each time.
|
|
621
623
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
624
|
+
If you previously used an older version that stored credentials in keyring, you can clear them using:
|
|
625
|
+
|
|
626
|
+
```python
|
|
627
|
+
from python_package_folder import Publisher, Repository
|
|
628
|
+
|
|
629
|
+
publisher = Publisher(repository=Repository.AZURE)
|
|
630
|
+
publisher.clear_stored_credentials()
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Or manually using Python:
|
|
634
|
+
```python
|
|
635
|
+
import keyring
|
|
636
|
+
keyring.delete_password("python-package-folder-azure", "username")
|
|
637
|
+
# Also delete the password if you know the username
|
|
625
638
|
```
|
|
626
639
|
|
|
627
640
|
## Command Line Options
|
|
@@ -791,11 +804,11 @@ config.restore()
|
|
|
791
804
|
```
|
|
792
805
|
|
|
793
806
|
**Methods:**
|
|
794
|
-
- `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists, otherwise create temporary `pyproject.toml` with subfolder-specific configuration
|
|
807
|
+
- `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists (adjusting package paths and ensuring `[build-system]` uses hatchling), otherwise create temporary `pyproject.toml` with subfolder-specific configuration including `[build-system]` section using hatchling
|
|
795
808
|
- `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
|
|
796
809
|
|
|
797
810
|
**Note**: This class automatically:
|
|
798
|
-
- **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily). Otherwise, creates a temporary one from the parent configuration.
|
|
811
|
+
- **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily with adjusted package paths). Otherwise, creates a temporary one from the parent configuration. In both cases, the `[build-system]` section is always set to use hatchling, replacing any existing build-system configuration.
|
|
799
812
|
- **README handling**: If a README exists in the subfolder, it will be used instead of the parent README. If no README exists in the subfolder, a minimal README with just the folder name will be created. The original parent README is backed up and restored after the build completes.
|
|
800
813
|
- **Package initialization**: Creates `__init__.py` files if needed to make subfolders valid Python packages.
|
|
801
814
|
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/analyzer.py
RENAMED
|
@@ -45,13 +45,55 @@ class ImportAnalyzer:
|
|
|
45
45
|
"""
|
|
46
46
|
Recursively find all Python files in a directory.
|
|
47
47
|
|
|
48
|
+
Excludes common directories like .venv, venv, __pycache__, etc.
|
|
49
|
+
|
|
48
50
|
Args:
|
|
49
51
|
directory: Directory to search for Python files
|
|
50
52
|
|
|
51
53
|
Returns:
|
|
52
54
|
List of paths to all .py files found in the directory tree
|
|
53
55
|
"""
|
|
54
|
-
|
|
56
|
+
exclude_patterns = {
|
|
57
|
+
".venv",
|
|
58
|
+
"venv",
|
|
59
|
+
"__pycache__",
|
|
60
|
+
".git",
|
|
61
|
+
".pytest_cache",
|
|
62
|
+
".mypy_cache",
|
|
63
|
+
"node_modules",
|
|
64
|
+
".tox",
|
|
65
|
+
"dist",
|
|
66
|
+
"build",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
python_files = []
|
|
70
|
+
for path in directory.rglob("*.py"):
|
|
71
|
+
if not path.is_file():
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# Check if any part of the path matches exclusion patterns
|
|
75
|
+
should_exclude = False
|
|
76
|
+
for part in path.parts:
|
|
77
|
+
# Check exact matches
|
|
78
|
+
if part in exclude_patterns:
|
|
79
|
+
should_exclude = True
|
|
80
|
+
break
|
|
81
|
+
# Check if part starts with excluded pattern or contains .egg-info
|
|
82
|
+
for pattern in exclude_patterns:
|
|
83
|
+
if part.startswith(pattern):
|
|
84
|
+
should_exclude = True
|
|
85
|
+
break
|
|
86
|
+
# Also exclude .egg-info directories
|
|
87
|
+
if ".egg-info" in part:
|
|
88
|
+
should_exclude = True
|
|
89
|
+
break
|
|
90
|
+
if should_exclude:
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
if not should_exclude:
|
|
94
|
+
python_files.append(path)
|
|
95
|
+
|
|
96
|
+
return python_files
|
|
55
97
|
|
|
56
98
|
def extract_imports(self, file_path: Path) -> list[ImportInfo]:
|
|
57
99
|
"""
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/publisher.py
RENAMED
|
@@ -41,12 +41,15 @@ class Publisher:
|
|
|
41
41
|
This class manages the publishing process, including credential handling
|
|
42
42
|
and repository configuration. It uses twine under the hood for actual publishing.
|
|
43
43
|
|
|
44
|
+
Credentials are not stored - they must be provided via command-line arguments
|
|
45
|
+
or will be prompted each time. This ensures credentials are not persisted.
|
|
46
|
+
|
|
44
47
|
Attributes:
|
|
45
48
|
repository: Target repository for publishing
|
|
46
49
|
dist_dir: Directory containing built distribution files
|
|
47
50
|
repository_url: Custom repository URL (for Azure or custom PyPI servers)
|
|
48
|
-
username: Username for authentication (optional,
|
|
49
|
-
password: Password/token for authentication (optional,
|
|
51
|
+
username: Username for authentication (optional, will be prompted if not provided)
|
|
52
|
+
password: Password/token for authentication (optional, will be prompted if not provided)
|
|
50
53
|
"""
|
|
51
54
|
|
|
52
55
|
def __init__(
|
|
@@ -109,8 +112,9 @@ class Publisher:
|
|
|
109
112
|
"""
|
|
110
113
|
Get credentials for publishing.
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
Always prompts for username and password/token if not already provided.
|
|
116
|
+
Does not use keyring to store/retrieve credentials - credentials must be
|
|
117
|
+
provided via command-line arguments or will be prompted each time.
|
|
114
118
|
|
|
115
119
|
Returns:
|
|
116
120
|
Tuple of (username, password/token)
|
|
@@ -118,24 +122,8 @@ class Publisher:
|
|
|
118
122
|
username = self.username
|
|
119
123
|
password = self.password
|
|
120
124
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
try:
|
|
124
|
-
username = keyring.get_password(
|
|
125
|
-
f"python-package-folder-{self.repository.value}", "username"
|
|
126
|
-
)
|
|
127
|
-
except Exception:
|
|
128
|
-
pass
|
|
129
|
-
|
|
130
|
-
if keyring and not password:
|
|
131
|
-
try:
|
|
132
|
-
password = keyring.get_password(
|
|
133
|
-
f"python-package-folder-{self.repository.value}", username or "token"
|
|
134
|
-
)
|
|
135
|
-
except Exception:
|
|
136
|
-
pass
|
|
137
|
-
|
|
138
|
-
# Prompt if still not available
|
|
125
|
+
# Always prompt if not provided via command-line arguments
|
|
126
|
+
# We don't use keyring to avoid storing credentials
|
|
139
127
|
if not username:
|
|
140
128
|
username = input(f"Enter username for {self.repository.value}: ").strip()
|
|
141
129
|
if not username:
|
|
@@ -160,18 +148,7 @@ class Publisher:
|
|
|
160
148
|
)
|
|
161
149
|
username = "__token__"
|
|
162
150
|
|
|
163
|
-
#
|
|
164
|
-
if keyring:
|
|
165
|
-
try:
|
|
166
|
-
keyring.set_password(
|
|
167
|
-
f"python-package-folder-{self.repository.value}", "username", username
|
|
168
|
-
)
|
|
169
|
-
keyring.set_password(
|
|
170
|
-
f"python-package-folder-{self.repository.value}", username, password
|
|
171
|
-
)
|
|
172
|
-
except Exception:
|
|
173
|
-
# Keyring storage is optional, continue if it fails
|
|
174
|
-
pass
|
|
151
|
+
# Do not store in keyring - credentials are not persisted
|
|
175
152
|
|
|
176
153
|
return username, password
|
|
177
154
|
|
|
@@ -253,6 +230,9 @@ class Publisher:
|
|
|
253
230
|
cmd = ["twine", "upload"]
|
|
254
231
|
if skip_existing:
|
|
255
232
|
cmd.append("--skip-existing")
|
|
233
|
+
# Always use verbose for Azure Artifacts to get better error details
|
|
234
|
+
if self.repository == Repository.AZURE:
|
|
235
|
+
cmd.append("--verbose")
|
|
256
236
|
cmd.extend(["--repository-url", repo_url])
|
|
257
237
|
cmd.extend(["--username", username])
|
|
258
238
|
cmd.extend(["--password", password])
|
|
@@ -284,6 +264,31 @@ class Publisher:
|
|
|
284
264
|
self.password = None
|
|
285
265
|
self.publish(skip_existing=skip_existing)
|
|
286
266
|
|
|
267
|
+
def clear_stored_credentials(self) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Clear any stored credentials from keyring for this repository.
|
|
270
|
+
|
|
271
|
+
This method can be used to remove previously stored credentials.
|
|
272
|
+
Note: The current implementation does not store credentials, but this
|
|
273
|
+
method is provided for compatibility and to clear any old stored credentials.
|
|
274
|
+
"""
|
|
275
|
+
if keyring:
|
|
276
|
+
try:
|
|
277
|
+
service_name = f"python-package-folder-{self.repository.value}"
|
|
278
|
+
# Try to get and delete stored username
|
|
279
|
+
stored_username = keyring.get_password(service_name, "username")
|
|
280
|
+
if stored_username:
|
|
281
|
+
try:
|
|
282
|
+
keyring.delete_password(service_name, stored_username)
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
285
|
+
try:
|
|
286
|
+
keyring.delete_password(service_name, "username")
|
|
287
|
+
except Exception:
|
|
288
|
+
pass
|
|
289
|
+
except Exception:
|
|
290
|
+
pass
|
|
291
|
+
|
|
287
292
|
|
|
288
293
|
def get_repository_help() -> str:
|
|
289
294
|
"""
|
|
@@ -122,7 +122,8 @@ def main() -> int:
|
|
|
122
122
|
src_dir = Path(args.src_dir).resolve()
|
|
123
123
|
else:
|
|
124
124
|
# Auto-detect: use current directory if it has Python files, otherwise use project_root/src
|
|
125
|
-
|
|
125
|
+
current_dir = Path.cwd()
|
|
126
|
+
src_dir = find_source_directory(project_root, current_dir=current_dir)
|
|
126
127
|
if src_dir:
|
|
127
128
|
print(f"Auto-detected source directory: {src_dir}")
|
|
128
129
|
else:
|
|
@@ -30,8 +30,11 @@ class SubfolderBuildConfig:
|
|
|
30
30
|
Manages temporary build configuration for subfolder builds.
|
|
31
31
|
|
|
32
32
|
When building a subfolder as a separate package, this class:
|
|
33
|
-
- Uses the subfolder's pyproject.toml if it exists
|
|
33
|
+
- Uses the subfolder's pyproject.toml if it exists (adjusts package paths and ensures
|
|
34
|
+
[build-system] uses hatchling)
|
|
34
35
|
- Otherwise creates a temporary pyproject.toml with the appropriate package name and version
|
|
36
|
+
- Always ensures [build-system] section uses hatchling (replaces any existing build-system
|
|
37
|
+
configuration from parent or subfolder)
|
|
35
38
|
- Handles README files similarly (uses subfolder README if present)
|
|
36
39
|
"""
|
|
37
40
|
|
|
@@ -111,13 +114,96 @@ class SubfolderBuildConfig:
|
|
|
111
114
|
# If it's a package or has subpackages, return the path
|
|
112
115
|
return packages_path, [packages_path] if packages_path else []
|
|
113
116
|
|
|
117
|
+
def _adjust_subfolder_pyproject_packages_path(self, content: str) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Adjust packages path in subfolder pyproject.toml to be relative to project root.
|
|
120
|
+
|
|
121
|
+
When a subfolder's pyproject.toml is copied to project root, the packages path
|
|
122
|
+
needs to be adjusted to point to the subfolder relative to the project root.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
content: Content of the subfolder's pyproject.toml
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Adjusted content with correct packages path
|
|
129
|
+
"""
|
|
130
|
+
# Get the correct packages path relative to project root
|
|
131
|
+
_, package_dirs = self._get_package_structure()
|
|
132
|
+
if not package_dirs:
|
|
133
|
+
# No adjustment needed if we can't determine the path
|
|
134
|
+
return content
|
|
135
|
+
|
|
136
|
+
correct_packages_path = package_dirs[0]
|
|
137
|
+
lines = content.split("\n")
|
|
138
|
+
result = []
|
|
139
|
+
in_hatch_build = False
|
|
140
|
+
packages_set = False
|
|
141
|
+
|
|
142
|
+
for line in lines:
|
|
143
|
+
# Detect hatch build section
|
|
144
|
+
if line.strip().startswith("[tool.hatch.build.targets.wheel]"):
|
|
145
|
+
in_hatch_build = True
|
|
146
|
+
result.append(line)
|
|
147
|
+
continue
|
|
148
|
+
elif line.strip().startswith("[") and in_hatch_build:
|
|
149
|
+
# End of hatch build section, add packages if not set
|
|
150
|
+
if not packages_set and correct_packages_path:
|
|
151
|
+
packages_str = f'"{correct_packages_path}"'
|
|
152
|
+
result.append(f"packages = [{packages_str}]")
|
|
153
|
+
in_hatch_build = False
|
|
154
|
+
result.append(line)
|
|
155
|
+
elif in_hatch_build:
|
|
156
|
+
# Modify packages path if found
|
|
157
|
+
if re.match(r"^\s*packages\s*=", line):
|
|
158
|
+
packages_str = f'"{correct_packages_path}"'
|
|
159
|
+
result.append(f"packages = [{packages_str}]")
|
|
160
|
+
packages_set = True
|
|
161
|
+
continue
|
|
162
|
+
# Keep other lines in hatch build section
|
|
163
|
+
result.append(line)
|
|
164
|
+
else:
|
|
165
|
+
result.append(line)
|
|
166
|
+
|
|
167
|
+
# Add packages if we're still in hatch build section and haven't set it
|
|
168
|
+
if in_hatch_build and not packages_set and correct_packages_path:
|
|
169
|
+
packages_str = f'"{correct_packages_path}"'
|
|
170
|
+
result.append(f"packages = [{packages_str}]")
|
|
171
|
+
|
|
172
|
+
# Ensure build-system section exists (required for hatchling)
|
|
173
|
+
# Check if build-system section exists in the result
|
|
174
|
+
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
175
|
+
if not has_build_system:
|
|
176
|
+
# Insert build-system at the very beginning of the file
|
|
177
|
+
build_system_lines = [
|
|
178
|
+
"[build-system]",
|
|
179
|
+
'requires = ["hatchling"]',
|
|
180
|
+
'build-backend = "hatchling.build"',
|
|
181
|
+
"",
|
|
182
|
+
]
|
|
183
|
+
result = build_system_lines + result
|
|
184
|
+
|
|
185
|
+
# Ensure hatch build section exists if packages path is needed
|
|
186
|
+
if not packages_set and correct_packages_path:
|
|
187
|
+
# Check if we need to add the section
|
|
188
|
+
if "[tool.hatch.build.targets.wheel]" not in content:
|
|
189
|
+
result.append("")
|
|
190
|
+
result.append("[tool.hatch.build.targets.wheel]")
|
|
191
|
+
packages_str = f'"{correct_packages_path}"'
|
|
192
|
+
result.append(f"packages = [{packages_str}]")
|
|
193
|
+
|
|
194
|
+
return "\n".join(result)
|
|
195
|
+
|
|
114
196
|
def create_temp_pyproject(self) -> Path | None:
|
|
115
197
|
"""
|
|
116
198
|
Create a temporary pyproject.toml for the subfolder build.
|
|
117
199
|
|
|
118
|
-
If a pyproject.toml exists in the subfolder, it will be used
|
|
119
|
-
|
|
120
|
-
|
|
200
|
+
If a pyproject.toml exists in the subfolder, it will be used (copied to project root
|
|
201
|
+
with adjusted package paths and ensuring [build-system] uses hatchling). Otherwise,
|
|
202
|
+
creates a pyproject.toml in the project root based on the parent pyproject.toml with
|
|
203
|
+
the appropriate package name and version.
|
|
204
|
+
|
|
205
|
+
The [build-system] section is always set to use hatchling, even if the parent or
|
|
206
|
+
subfolder pyproject.toml uses a different build backend (e.g., setuptools).
|
|
121
207
|
|
|
122
208
|
Returns:
|
|
123
209
|
Path to the pyproject.toml file (either from subfolder or created temporary),
|
|
@@ -149,8 +235,13 @@ class SubfolderBuildConfig:
|
|
|
149
235
|
shutil.copy2(original_pyproject, backup_path)
|
|
150
236
|
self.original_pyproject_backup = backup_path
|
|
151
237
|
|
|
152
|
-
#
|
|
153
|
-
|
|
238
|
+
# Read and adjust the subfolder pyproject.toml
|
|
239
|
+
subfolder_content = subfolder_pyproject.read_text(encoding="utf-8")
|
|
240
|
+
# Adjust packages path to be relative to project root
|
|
241
|
+
adjusted_content = self._adjust_subfolder_pyproject_packages_path(subfolder_content)
|
|
242
|
+
|
|
243
|
+
# Write adjusted content to project root
|
|
244
|
+
original_pyproject.write_text(adjusted_content, encoding="utf-8")
|
|
154
245
|
self.temp_pyproject = original_pyproject
|
|
155
246
|
|
|
156
247
|
# Handle README file
|
|
@@ -259,6 +350,7 @@ class SubfolderBuildConfig:
|
|
|
259
350
|
skip_uv_dynamic = False
|
|
260
351
|
in_hatch_build = False
|
|
261
352
|
packages_set = False
|
|
353
|
+
build_system_set = False
|
|
262
354
|
|
|
263
355
|
# Get package structure
|
|
264
356
|
packages_path, package_dirs = self._get_package_structure()
|
|
@@ -266,6 +358,19 @@ class SubfolderBuildConfig:
|
|
|
266
358
|
package_dirs = []
|
|
267
359
|
|
|
268
360
|
for _i, line in enumerate(lines):
|
|
361
|
+
# Skip build-system section - we'll add our own for subfolder builds
|
|
362
|
+
if line.strip().startswith("[build-system]"):
|
|
363
|
+
build_system_set = True
|
|
364
|
+
continue # Skip the [build-system] line
|
|
365
|
+
elif build_system_set and line.strip().startswith("["):
|
|
366
|
+
# End of build-system section
|
|
367
|
+
build_system_set = False
|
|
368
|
+
result.append(line)
|
|
369
|
+
continue
|
|
370
|
+
elif build_system_set:
|
|
371
|
+
# Skip build-system content - we'll add our own
|
|
372
|
+
continue
|
|
373
|
+
|
|
269
374
|
# Skip hatch versioning and uv-dynamic-versioning sections
|
|
270
375
|
if line.strip().startswith("[tool.hatch.version]"):
|
|
271
376
|
skip_hatch_version = True
|
|
@@ -361,6 +466,19 @@ class SubfolderBuildConfig:
|
|
|
361
466
|
packages_str = ", ".join(f'"{p}"' for p in package_dirs)
|
|
362
467
|
result.append(f"packages = [{packages_str}]")
|
|
363
468
|
|
|
469
|
+
# Ensure build-system section exists (required for hatchling)
|
|
470
|
+
# Check if build-system section exists in the result
|
|
471
|
+
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
472
|
+
if not has_build_system:
|
|
473
|
+
# Insert build-system at the very beginning of the file
|
|
474
|
+
build_system_lines = [
|
|
475
|
+
"[build-system]",
|
|
476
|
+
'requires = ["hatchling"]',
|
|
477
|
+
'build-backend = "hatchling.build"',
|
|
478
|
+
"",
|
|
479
|
+
]
|
|
480
|
+
result = build_system_lines + result
|
|
481
|
+
|
|
364
482
|
# Ensure packages is always set for subfolder builds
|
|
365
483
|
if not packages_set and package_dirs:
|
|
366
484
|
# Add the section if it doesn't exist
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/utils.py
RENAMED
|
@@ -63,7 +63,8 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
|
|
|
63
63
|
project_root = project_root.resolve()
|
|
64
64
|
|
|
65
65
|
# Check if current directory is a subdirectory with Python files
|
|
66
|
-
if
|
|
66
|
+
# Prioritize current directory if it's within the project and has Python files
|
|
67
|
+
if current_dir.is_relative_to(project_root) and current_dir != project_root:
|
|
67
68
|
python_files = list(current_dir.glob("*.py"))
|
|
68
69
|
if python_files:
|
|
69
70
|
# Current directory has Python files, use it as source
|
|
@@ -74,10 +75,11 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
|
|
|
74
75
|
if src_dir.exists() and src_dir.is_dir():
|
|
75
76
|
return src_dir
|
|
76
77
|
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
# Only check project_root if current_dir is the project_root
|
|
79
|
+
if current_dir == project_root:
|
|
80
|
+
python_files = list(project_root.glob("*.py"))
|
|
81
|
+
if python_files:
|
|
82
|
+
return project_root
|
|
81
83
|
|
|
82
84
|
return None
|
|
83
85
|
|
|
@@ -584,6 +584,11 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
|
|
|
584
584
|
|
|
585
585
|
# Verify dynamic versioning is removed
|
|
586
586
|
assert 'dynamic = ["version"]' not in content
|
|
587
|
+
|
|
588
|
+
# Verify build-system section is added (required for hatchling)
|
|
589
|
+
assert "[build-system]" in content
|
|
590
|
+
assert 'requires = ["hatchling"]' in content
|
|
591
|
+
assert 'build-backend = "hatchling.build"' in content
|
|
587
592
|
assert "[tool.hatch.version]" not in content
|
|
588
593
|
assert "[tool.uv-dynamic-versioning]" not in content
|
|
589
594
|
|
|
@@ -648,3 +653,71 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
|
|
|
648
653
|
restored_content = (project_root / "pyproject.toml").read_text()
|
|
649
654
|
assert restored_content == original_content
|
|
650
655
|
assert 'name = "test-package"' in restored_content
|
|
656
|
+
|
|
657
|
+
def test_build_system_section_replaces_setuptools(
|
|
658
|
+
self, test_project_with_pyproject: Path
|
|
659
|
+
) -> None:
|
|
660
|
+
"""Test that build-system section replaces existing setuptools configuration."""
|
|
661
|
+
project_root = test_project_with_pyproject
|
|
662
|
+
subfolder = project_root / "subfolder"
|
|
663
|
+
|
|
664
|
+
# Modify parent pyproject.toml to have setuptools build-system
|
|
665
|
+
pyproject_path = project_root / "pyproject.toml"
|
|
666
|
+
original_content = pyproject_path.read_text()
|
|
667
|
+
modified_content = (
|
|
668
|
+
original_content
|
|
669
|
+
+ '\n[build-system]\nrequires = ["setuptools"]\nbuild-backend = "setuptools.build_meta"\n'
|
|
670
|
+
)
|
|
671
|
+
pyproject_path.write_text(modified_content)
|
|
672
|
+
|
|
673
|
+
try:
|
|
674
|
+
config = SubfolderBuildConfig(
|
|
675
|
+
project_root=project_root,
|
|
676
|
+
src_dir=subfolder,
|
|
677
|
+
version="1.0.0",
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
pyproject_path = config.create_temp_pyproject()
|
|
681
|
+
content = pyproject_path.read_text()
|
|
682
|
+
|
|
683
|
+
# Verify build-system section uses hatchling, not setuptools
|
|
684
|
+
assert "[build-system]" in content
|
|
685
|
+
assert 'requires = ["hatchling"]' in content
|
|
686
|
+
assert 'build-backend = "hatchling.build"' in content
|
|
687
|
+
assert "setuptools" not in content or 'build-backend = "setuptools' not in content
|
|
688
|
+
|
|
689
|
+
config.restore()
|
|
690
|
+
finally:
|
|
691
|
+
# Restore original content
|
|
692
|
+
pyproject_path.write_text(original_content)
|
|
693
|
+
|
|
694
|
+
def test_build_system_section_with_subfolder_pyproject(
|
|
695
|
+
self, test_project_with_pyproject: Path
|
|
696
|
+
) -> None:
|
|
697
|
+
"""Test that build-system section is added when using subfolder's pyproject.toml."""
|
|
698
|
+
project_root = test_project_with_pyproject
|
|
699
|
+
subfolder = project_root / "subfolder"
|
|
700
|
+
|
|
701
|
+
# Create pyproject.toml in subfolder without build-system
|
|
702
|
+
subfolder_pyproject_content = """[project]
|
|
703
|
+
name = "subfolder-package"
|
|
704
|
+
version = "3.0.0"
|
|
705
|
+
description = "Subfolder package"
|
|
706
|
+
"""
|
|
707
|
+
(subfolder / "pyproject.toml").write_text(subfolder_pyproject_content)
|
|
708
|
+
|
|
709
|
+
config = SubfolderBuildConfig(
|
|
710
|
+
project_root=project_root,
|
|
711
|
+
src_dir=subfolder,
|
|
712
|
+
version="1.0.0",
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
pyproject_path = config.create_temp_pyproject()
|
|
716
|
+
content = pyproject_path.read_text()
|
|
717
|
+
|
|
718
|
+
# Verify build-system section is added
|
|
719
|
+
assert "[build-system]" in content
|
|
720
|
+
assert 'requires = ["hatchling"]' in content
|
|
721
|
+
assert 'build-backend = "hatchling.build"' in content
|
|
722
|
+
|
|
723
|
+
config.restore()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/finder.py
RENAMED
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/manager.py
RENAMED
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/folder_structure/some_globals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-1.2.0 → python_package_folder-1.2.2}/tests/test_build_with_external_deps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|