wexample-wex-addon-dev-python 0.0.61__py3-none-any.whl → 0.0.63__py3-none-any.whl

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.
@@ -2,248 +2,49 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from wexample_filestate.config_value.readme_content_config_value import (
6
- ReadmeContentConfigValue,
7
- )
8
- from wexample_helpers.classes.field import public_field
9
5
  from wexample_helpers.decorator.base_class import base_class
6
+ from wexample_wex_addon_app.config_value.app_readme_config_value import (
7
+ AppReadmeConfigValue,
8
+ )
10
9
 
11
10
  if TYPE_CHECKING:
12
- from wexample_wex_addon_dev_python.workdir.python_package_workdir import (
13
- PythonPackageWorkdir,
14
- )
11
+ pass
15
12
 
16
13
 
17
14
  @base_class
18
- class PythonPackageReadmeContentConfigValue(ReadmeContentConfigValue):
19
- workdir: PythonPackageWorkdir = public_field(
20
- description="The python package workdir"
21
- )
22
-
23
- def get_templates(self) -> list[str] | None:
24
- # Prepare context for Jinja2 rendering
25
- context = self._get_template_context()
26
-
27
- # Define fixed order of README sections
28
- section_names = [
29
- "title",
30
- "table-of-contents",
31
- "status-compatibility",
32
- "prerequisites",
33
- "installation",
34
- "quickstart",
35
- "basic-usage",
36
- "configuration",
37
- "logging",
38
- "api-reference",
39
- "examples",
40
- "tests",
41
- "code-quality",
42
- "versioning",
43
- "changelog",
44
- "migration-notes",
45
- "roadmap",
46
- "troubleshooting",
47
- "security",
48
- "privacy",
49
- "support",
50
- "contribution-guidelines",
51
- "maintainers",
52
- "license",
53
- "useful-links",
54
- "suite-integration",
55
- "compatibility-matrix",
56
- "requirements",
57
- "dependencies",
58
- "links",
59
- "suite-signature",
60
- ]
61
-
62
- # First pass: collect available sections (excluding title and table-of-contents)
63
- available_sections = []
64
- for section_name in section_names:
65
- if section_name not in ["title", "table-of-contents"]:
66
- # Check if section exists
67
- if self._section_exists(section_name):
68
- available_sections.append(
69
- {
70
- "name": section_name,
71
- "title": self._section_name_to_title(section_name),
72
- "anchor": section_name.replace("_", "-"),
73
- }
74
- )
75
-
76
- # Add available sections to context for table-of-contents
77
- context["available_sections"] = available_sections
15
+ class PythonPackageReadmeContentConfigValue(AppReadmeConfigValue):
16
+ """README generation for Python packages."""
78
17
 
79
- # Render ordered sections (supports both .md and .md.j2)
80
- rendered_content = ""
81
- for section_name in section_names:
82
- section_content = self._render_readme_section(section_name, context)
83
- if section_content:
84
- rendered_content += f"{section_content}\n\n"
18
+ def _get_app_description(self) -> str:
19
+ """Extract description from pyproject.toml."""
20
+ return self.workdir.get_app_config().get("project", {}).get("description")
85
21
 
86
- return [rendered_content]
87
-
88
- def _get_template_context(self) -> dict:
89
- # Use TOMLDocument from the workdir
90
- doc = self.workdir.get_project_config()
91
- project = doc.get("project", {}) if isinstance(doc, dict) else {}
92
-
93
- # Extract information
94
- description = project.get("description", "")
95
- python_version = project.get("requires-python", "")
96
- dependencies = project.get("dependencies", [])
22
+ def _get_app_homepage(self) -> str:
23
+ """Extract homepage URL from pyproject.toml."""
24
+ project = self.workdir.get_app_config()
97
25
  urls = (
98
26
  project.get("urls", {}) if isinstance(project.get("urls", {}), dict) else {}
99
27
  )
100
- # Accept both lowercase and capitalized homepage key variants
101
- homepage = urls.get("homepage") or urls.get("Homepage") or ""
28
+ return urls.get("homepage") or urls.get("Homepage") or ""
29
+
30
+ def _get_project_license(self) -> str | None:
31
+ """Extract license information from pyproject.toml."""
32
+ project = self.workdir.get_app_config()
102
33
  license_field = project.get("license", {})
103
34
  if isinstance(license_field, dict):
104
- license_info = license_field.get("text", "") or license_field.get(
105
- "file", ""
106
- )
107
- else:
108
- license_info = str(license_field) if license_field else ""
109
-
110
- # Format dependencies list
111
- deps_list = "\n".join([f"- {dep}" for dep in dependencies])
112
-
113
- return {
114
- "package_name": self.workdir.get_package_name(),
115
- "version": self.workdir.get_project_version(),
116
- "description": description,
117
- "python_version": python_version,
118
- "dependencies": dependencies,
119
- "deps_list": deps_list,
120
- "homepage": homepage,
121
- "license_info": license_info,
122
- "workdir": self.workdir,
123
- }
124
-
125
- def _render_readme_section(self, section_name: str, context: dict) -> str | None:
126
- """
127
- Render a README section from .md or .md.j2 file with Jinja2 support.
128
-
129
- Searches in three levels (in order):
130
- 1. Package-level templates
131
- 2. Suite-level templates
132
- 3. Default templates (bundled with the module)
133
-
134
- Tries .md.j2 first, then .md. Both formats support Jinja2 variables.
135
-
136
- Args:
137
- section_name: Name of the section (without extension)
138
- context: Jinja2 context variables for rendering
139
-
140
- Returns:
141
- Rendered content or None if section file not found
142
- """
143
- from pathlib import Path
144
-
145
- from jinja2 import Environment, FileSystemLoader, TemplateNotFound
146
- from wexample_app.const.globals import WORKDIR_SETUP_DIR
147
-
148
- workdir_path = self.workdir.get_path()
149
-
150
- search_paths = [
151
- workdir_path / WORKDIR_SETUP_DIR / "knowledge" / "readme", # Package-level
152
- ]
35
+ return license_field.get("text", "") or license_field.get("file", "")
36
+ return str(license_field) if license_field else ""
153
37
 
154
- # Package may have a suite.
155
- suite_path = self.workdir.find_suite_workdir_path()
156
- if suite_path is not None:
157
- search_paths.append(
158
- suite_path
159
- / WORKDIR_SETUP_DIR
160
- / "knowledge"
161
- / "package-readme", # Suite-level
162
- )
163
-
164
- # Add default templates path (bundled with the module)
165
- default_templates_path = (
166
- Path(__file__).parent.parent / "resources" / "readme_templates"
167
- )
168
- search_paths.append(default_templates_path)
169
-
170
- # Try .md.j2 first (Jinja2 template)
171
- for search_path in search_paths:
172
- if not search_path.exists():
173
- continue
174
-
175
- env = Environment(loader=FileSystemLoader(str(search_path)))
176
- try:
177
- template = env.get_template(f"{section_name}.md.j2")
178
- return template.render(context)
179
- except TemplateNotFound:
180
- pass
181
-
182
- # Try .md (static markdown, still rendered with Jinja2)
183
- for search_path in search_paths:
184
- md_path = search_path / f"{section_name}.md"
185
- if md_path.exists():
186
- content = md_path.read_text(encoding="utf-8")
187
- env = Environment(loader=FileSystemLoader(str(search_path)))
188
- template = env.from_string(content)
189
- return template.render(context)
190
-
191
- return None
192
-
193
- def _section_exists(self, section_name: str) -> bool:
194
- """
195
- Check if a section file exists (.md or .md.j2).
196
-
197
- Searches in three levels:
198
- 1. Package-level templates
199
- 2. Suite-level templates
200
- 3. Default templates (bundled with the module)
201
-
202
- Args:
203
- section_name: Name of the section (without extension)
38
+ def _get_template_context(self) -> dict:
39
+ """Build template context with Python-specific variables.
204
40
 
205
- Returns:
206
- True if section file exists, False otherwise
41
+ Adds python_version to the base context.
207
42
  """
208
- from pathlib import Path
43
+ context = super()._get_template_context()
209
44
 
210
- from wexample_app.const.globals import WORKDIR_SETUP_DIR
211
-
212
- workdir_path = self.workdir.get_path()
213
-
214
- search_paths = [
215
- workdir_path / WORKDIR_SETUP_DIR / "knowledge" / "readme",
216
- ]
217
-
218
- # Package may have a suite.
219
- suite_path = self.workdir.find_suite_workdir_path()
220
- if suite_path is not None:
221
- search_paths.append(
222
- suite_path / WORKDIR_SETUP_DIR / "knowledge" / "package-readme",
223
- )
224
-
225
- # Add default templates path (bundled with the module)
226
- default_templates_path = (
227
- Path(__file__).parent.parent / "resources" / "readme_templates"
45
+ # Add Python-specific variable
46
+ context["python_version"] = (
47
+ self.workdir.get_app_config().get("project", {}).get("requires-python", "")
228
48
  )
229
- search_paths.append(default_templates_path)
230
-
231
- for search_path in search_paths:
232
- if (search_path / f"{section_name}.md.j2").exists():
233
- return True
234
- if (search_path / f"{section_name}.md").exists():
235
- return True
236
49
 
237
- return False
238
-
239
- def _section_name_to_title(self, section_name: str) -> str:
240
- """
241
- Convert section name to human-readable title.
242
-
243
- Args:
244
- section_name: Section name (e.g., "basic-usage")
245
-
246
- Returns:
247
- Human-readable title (e.g., "Basic Usage")
248
- """
249
- return section_name.replace("-", " ").replace("_", " ").title()
50
+ return context
File without changes
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Any, ClassVar
5
+
6
+ from wexample_app.item.file.iml_file import ImlFile
7
+
8
+
9
+ class PythonAppImlFile(ImlFile):
10
+ """
11
+ IntelliJ IDEA .iml helper tailored for Python apps (src/tests layout, python module type).
12
+ """
13
+
14
+ MODULE_TYPE: ClassVar[str] = "PYTHON_MODULE"
15
+
16
+ def _default_exclude_folders(self) -> Iterable[dict[str, Any]]:
17
+ return (
18
+ {
19
+ "@url": f"{self.MODULE_DIR_URL}/dist",
20
+ },
21
+ )
22
+
23
+ def _default_module_attributes(self) -> dict[str, str]:
24
+ attrs = super()._default_module_attributes()
25
+ attrs.setdefault("@type", self.MODULE_TYPE)
26
+ return attrs
27
+
28
+ def _default_order_entries(self) -> Iterable[dict[str, Any]]:
29
+ return ({"@type": "sourceFolder", "@forTests": "false"},)
30
+
31
+ def _default_source_folders(self) -> Iterable[dict[str, Any]]:
32
+ return (
33
+ {
34
+ "@url": f"{self.MODULE_DIR_URL}/src",
35
+ "@isTestSource": "false",
36
+ },
37
+ {
38
+ "@url": f"{self.MODULE_DIR_URL}/tests",
39
+ "@isTestSource": "true",
40
+ },
41
+ )
@@ -8,35 +8,47 @@ from wexample_wex_addon_app.const.path import APP_PATH_README
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from tomlkit import TOMLDocument
11
- from wexample_wex_addon_app.workdir.code_base_workdir import (
12
- CodeBaseWorkdir,
13
- )
14
11
 
15
12
 
16
13
  @base_class
17
- class PythonPackageTomlFile(TomlFile):
14
+ class PythonPyprojectTomlFile(TomlFile):
18
15
  def add_dependency(
19
- self, spec: str, optional: bool = False, group: str = "dev"
16
+ self,
17
+ package_name: str,
18
+ version: str,
19
+ operator: str = "==",
20
+ optional: bool = False,
21
+ group: None | str = None,
20
22
  ) -> bool:
21
23
  from packaging.requirements import Requirement
22
24
  from packaging.utils import canonicalize_name
23
25
  from wexample_filestate_python.helpers.toml import toml_sort_string_array
24
26
 
25
- deps = self._get_deps_array(optional=optional, group=group)
27
+ spec = f"{package_name}{operator}{version}"
26
28
  new_req = Requirement(spec)
27
29
  new_name = canonicalize_name(new_req.name)
28
30
 
29
- old_spec = None
30
- for dep in deps:
31
- if canonicalize_name(Requirement(dep).name) == new_name:
32
- old_spec = dep
33
- break
31
+ deps = self._get_deps_array(optional=optional, group=group)
34
32
 
35
- self.remove_dependency_by_name(new_name, optional=optional, group=group)
33
+ # Look for existing dependency
34
+ old_spec = next(
35
+ (d for d in deps if canonicalize_name(Requirement(d).name) == new_name),
36
+ None,
37
+ )
38
+
39
+ # Remove old dep if exists
40
+ if old_spec:
41
+ deps.remove(old_spec)
36
42
 
43
+ # Add new version
37
44
  deps.append(spec)
45
+
46
+ # Sort array
38
47
  toml_sort_string_array(deps)
39
48
 
49
+ # Save file
50
+ self.write_parsed()
51
+
40
52
  return old_spec != spec
41
53
 
42
54
  def dumps(self, content: TOMLDocument | dict | None = None) -> str:
@@ -47,10 +59,10 @@ class PythonPackageTomlFile(TomlFile):
47
59
 
48
60
  content = content or self.read_parsed()
49
61
 
50
- package = self.find_package_workdir()
51
- import_name = package.get_package_import_name() if package else None
52
- project_name = package.get_package_name() if package else None
53
- project_version = package.get_project_version() if package else None
62
+ workdir = self.get_parent_item()
63
+ import_name = workdir.get_package_import_name()
64
+ project_name = workdir.get_package_name()
65
+ project_version = workdir.get_project_version()
54
66
 
55
67
  self._enforce_build_system(content)
56
68
  self._enforce_pdm_build(content, import_name)
@@ -65,40 +77,21 @@ class PythonPackageTomlFile(TomlFile):
65
77
 
66
78
  return result
67
79
 
68
- def find_package_workdir(self) -> CodeBaseWorkdir | None:
69
- from wexample_wex_addon_app.workdir.code_base_workdir import CodeBaseWorkdir
70
-
71
- return self.find_closest(CodeBaseWorkdir)
72
-
73
- def list_dependencies(
80
+ def get_dependencies_versions(
74
81
  self, optional: bool = False, group: str = "dev"
75
- ) -> list[str]:
76
- deps = self._get_deps_array(optional=optional, group=group)
77
- return [str(x) for x in list(deps)]
78
-
79
- def list_dependency_names(
80
- self,
81
- canonicalize_names: bool = True,
82
- optional: bool = False,
83
- group: str = "dev",
84
- ) -> list[str]:
85
- """Return dependency package names derived from list_dependencies().
86
-
87
- If canonicalize_names is True, names are normalized using packaging's
88
- canonicalize_name for robust comparisons (dash/underscore, case, etc.).
89
- """
82
+ ) -> dict[str, str]:
90
83
  from packaging.requirements import Requirement
91
84
  from packaging.utils import canonicalize_name
92
85
 
93
- names: list[str] = []
94
- for spec in self.list_dependencies(optional=optional, group=group):
95
- try:
96
- name = Requirement(spec).name
97
- names.append(canonicalize_name(name) if canonicalize_names else name)
98
- except Exception:
99
- # Skip unparsable entries when deriving names
100
- continue
101
- return names
86
+ deps = self._get_deps_array(optional=optional, group=group)
87
+
88
+ map = {}
89
+ for spec in list(deps):
90
+ req = Requirement(spec)
91
+ # name: version
92
+ map[canonicalize_name(req.name)] = str(req.specifier)
93
+
94
+ return map
102
95
 
103
96
  def optional_group_array(self, group: str):
104
97
  """Ensure and return project.optional-dependencies[group] as multi-line array."""
@@ -197,34 +190,28 @@ class PythonPackageTomlFile(TomlFile):
197
190
  project_tbl["requires-python"] = ">=3.10"
198
191
 
199
192
  # Add description if available
200
- package = self.find_package_workdir()
201
- if package:
202
- description = package.get_config().search("global.description")
203
- if not description.is_none():
204
- project_tbl["description"] = description.get_str()
193
+ workdir = self.get_parent_item()
194
+ description = workdir.get_config().search("global.description")
195
+ if not description.is_none():
196
+ project_tbl["description"] = description.get_str()
205
197
 
206
198
  # Add authors if available
207
- if package:
208
- from tomlkit import array, inline_table
199
+ from tomlkit import array, inline_table
209
200
 
210
- author_name = package.search_in_package_or_suite_config(
211
- "global.authors.name"
212
- )
213
- author_email = package.search_in_package_or_suite_config(
214
- "global.authors.email"
215
- )
201
+ author_name = workdir.search_in_package_or_suite_config("global.authors.name")
202
+ author_email = workdir.search_in_package_or_suite_config("global.authors.email")
216
203
 
217
- if not author_name.is_none() or not author_email.is_none():
218
- authors_arr = array()
219
- author_tbl = inline_table()
204
+ if not author_name.is_none() or not author_email.is_none():
205
+ authors_arr = array()
206
+ author_tbl = inline_table()
220
207
 
221
- if not author_name.is_none():
222
- author_tbl["name"] = author_name.get_str()
223
- if not author_email.is_none():
224
- author_tbl["email"] = author_email.get_str()
208
+ if not author_name.is_none():
209
+ author_tbl["name"] = author_name.get_str()
210
+ if not author_email.is_none():
211
+ author_tbl["email"] = author_email.get_str()
225
212
 
226
- authors_arr.append(author_tbl)
227
- project_tbl["authors"] = authors_arr
213
+ authors_arr.append(author_tbl)
214
+ project_tbl["authors"] = authors_arr
228
215
 
229
216
  # Add classifiers (standard Python package metadata)
230
217
  project_tbl["classifiers"] = [
@@ -234,14 +221,11 @@ class PythonPackageTomlFile(TomlFile):
234
221
  ]
235
222
 
236
223
  # Add README if it exists
237
- if package:
238
- pass
239
-
240
- readme_file = package.find_by_name(APP_PATH_README)
241
- if readme_file:
242
- readme_tbl, _ = toml_ensure_table(project_tbl, ["readme"])
243
- readme_tbl["file"] = str(APP_PATH_README)
244
- readme_tbl["content-type"] = "text/markdown"
224
+ readme_file = workdir.find_by_name(APP_PATH_README)
225
+ if readme_file:
226
+ readme_tbl, _ = toml_ensure_table(project_tbl, ["readme"])
227
+ readme_tbl["file"] = str(APP_PATH_README)
228
+ readme_tbl["content-type"] = "text/markdown"
245
229
 
246
230
  # Add MIT license
247
231
  license_tbl, _ = toml_ensure_table(project_tbl, ["license"])
@@ -305,7 +289,6 @@ class PythonPackageTomlFile(TomlFile):
305
289
 
306
290
  toml_sort_string_array(dev_arr)
307
291
 
308
- # --- Unified dependency accessors (runtime vs optional) ---
309
292
  def _get_deps_array(self, optional: bool = False, group: str = "dev"):
310
293
  """Return TOML array for runtime deps or optional group (multiline)."""
311
294
  return (
File without changes