itw-python-builder 0.2.14__tar.gz → 0.2.15__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.
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/PKG-INFO +4 -4
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/README.md +3 -3
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/task_utils.py +93 -15
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/PKG-INFO +4 -4
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/pyproject.toml +1 -1
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/LICENSE +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/.pylintrc +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/__init__.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/_pyruntime/sitecustomize.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/cli.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/notify.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/ssr_tasks.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/tasks.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/templates/new_version_email.html +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/templates/server.sitemap.snippet.ts +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/templates/sitemap.routes.ts +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/templates/task_template.md +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/utils.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder/version.py +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/SOURCES.txt +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/dependency_links.txt +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/entry_points.txt +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/requires.txt +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/top_level.txt +0 -0
- {itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: itw_python_builder
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.15
|
|
4
4
|
Summary: Standardized Django deployment pipeline with Docker, testing, and SonarQube integration
|
|
5
5
|
Author-email: IT-Works <contact@it-works.io>
|
|
6
6
|
License: MIT
|
|
@@ -295,11 +295,11 @@ With `/repo`, leading and trailing slashes are stripped automatically, and full
|
|
|
295
295
|
|
|
296
296
|
### Milestones, Labels and Duplicates
|
|
297
297
|
|
|
298
|
-
Milestones, labels and issue titles are matched with a shared fuzzy comparison at 95% similarity, so small differences in spacing or casing still count as a match. The threshold is a single constant (`SIMILARITY_THRESHOLD` in `task_utils.py`) if you need to tune it.
|
|
298
|
+
Milestones, labels and issue titles are matched with a shared fuzzy comparison at 95% similarity, so small differences in spacing or casing still count as a match. Digit runs must be identical (`java 38` does not match `java 37`). The threshold is a single constant (`SIMILARITY_THRESHOLD` in `task_utils.py`) if you need to tune it.
|
|
299
299
|
|
|
300
|
-
- **Milestone** —
|
|
300
|
+
- **Milestone** — an existing project or parent-group milestone with the same title is reused; otherwise it is created (with the start/end dates when both are given).
|
|
301
301
|
- **Label** — a similar existing label is linked and logged in white; a new one is created and logged in green. An empty `/label` is never sent to GitLab.
|
|
302
|
-
- **Duplicate issues** — before creating, the target project is
|
|
302
|
+
- **Duplicate issues** — before creating, the target project is searched by title. A match in the same milestone, or an unassigned issue with the same title, is treated as a duplicate and skipped.
|
|
303
303
|
|
|
304
304
|
### Acceptance Criteria and Comments
|
|
305
305
|
|
|
@@ -266,11 +266,11 @@ With `/repo`, leading and trailing slashes are stripped automatically, and full
|
|
|
266
266
|
|
|
267
267
|
### Milestones, Labels and Duplicates
|
|
268
268
|
|
|
269
|
-
Milestones, labels and issue titles are matched with a shared fuzzy comparison at 95% similarity, so small differences in spacing or casing still count as a match. The threshold is a single constant (`SIMILARITY_THRESHOLD` in `task_utils.py`) if you need to tune it.
|
|
269
|
+
Milestones, labels and issue titles are matched with a shared fuzzy comparison at 95% similarity, so small differences in spacing or casing still count as a match. Digit runs must be identical (`java 38` does not match `java 37`). The threshold is a single constant (`SIMILARITY_THRESHOLD` in `task_utils.py`) if you need to tune it.
|
|
270
270
|
|
|
271
|
-
- **Milestone** —
|
|
271
|
+
- **Milestone** — an existing project or parent-group milestone with the same title is reused; otherwise it is created (with the start/end dates when both are given).
|
|
272
272
|
- **Label** — a similar existing label is linked and logged in white; a new one is created and logged in green. An empty `/label` is never sent to GitLab.
|
|
273
|
-
- **Duplicate issues** — before creating, the target project is
|
|
273
|
+
- **Duplicate issues** — before creating, the target project is searched by title. A match in the same milestone, or an unassigned issue with the same title, is treated as a duplicate and skipped.
|
|
274
274
|
|
|
275
275
|
### Acceptance Criteria and Comments
|
|
276
276
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import re
|
|
2
|
+
import unicodedata
|
|
2
3
|
from difflib import SequenceMatcher
|
|
4
|
+
from urllib.parse import quote
|
|
3
5
|
|
|
4
6
|
from itw_python_builder.utils import _gitlab_api_call
|
|
5
7
|
|
|
6
8
|
SIMILARITY_THRESHOLD = 0.95
|
|
9
|
+
GITLAB_PAGE_SIZE = 100
|
|
10
|
+
_RE_DIGITS = re.compile(r'\d+')
|
|
7
11
|
|
|
8
12
|
DATE_PLACEHOLDER = 'YYYY-MM-DD'
|
|
9
13
|
|
|
@@ -48,7 +52,12 @@ def log_info(msg: str) -> None:
|
|
|
48
52
|
|
|
49
53
|
|
|
50
54
|
def _normalize_title(value: str) -> str:
|
|
51
|
-
|
|
55
|
+
collapsed = ' '.join((value or '').lower().split())
|
|
56
|
+
return unicodedata.normalize('NFC', collapsed)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _digit_tokens(value: str) -> list:
|
|
60
|
+
return _RE_DIGITS.findall(value or '')
|
|
52
61
|
|
|
53
62
|
|
|
54
63
|
def is_similar(left: str, right: str, threshold: float = SIMILARITY_THRESHOLD) -> bool:
|
|
@@ -59,19 +68,64 @@ def is_similar(left: str, right: str, threshold: float = SIMILARITY_THRESHOLD) -
|
|
|
59
68
|
return False
|
|
60
69
|
if a == b:
|
|
61
70
|
return True
|
|
71
|
+
if _digit_tokens(a) != _digit_tokens(b):
|
|
72
|
+
return False
|
|
62
73
|
return SequenceMatcher(None, a, b).ratio() >= threshold
|
|
63
74
|
|
|
64
75
|
|
|
65
76
|
def find_similar(value: str, items: list, key: str = 'title', threshold: float = SIMILARITY_THRESHOLD):
|
|
66
|
-
"""Return
|
|
77
|
+
"""Return an exact normalized match if one exists, else the first ~threshold match."""
|
|
78
|
+
target = _normalize_title(value)
|
|
79
|
+
fuzzy = None
|
|
67
80
|
for item in items:
|
|
68
|
-
|
|
81
|
+
other = item.get(key, '')
|
|
82
|
+
if target and target == _normalize_title(other):
|
|
69
83
|
return item
|
|
70
|
-
|
|
84
|
+
if fuzzy is None and is_similar(value, other, threshold):
|
|
85
|
+
fuzzy = item
|
|
86
|
+
return fuzzy
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _gitlab_api_list(api_host: str, endpoint: str, token: str) -> list:
|
|
90
|
+
"""GET a GitLab collection, following pages until a short page is returned."""
|
|
91
|
+
joiner = '&' if '?' in endpoint else '?'
|
|
92
|
+
page = 1
|
|
93
|
+
collected = []
|
|
94
|
+
while True:
|
|
95
|
+
chunk = _gitlab_api_call(
|
|
96
|
+
api_host,
|
|
97
|
+
f'{endpoint}{joiner}per_page={GITLAB_PAGE_SIZE}&page={page}',
|
|
98
|
+
token,
|
|
99
|
+
)
|
|
100
|
+
if not isinstance(chunk, list):
|
|
101
|
+
raise RuntimeError(
|
|
102
|
+
f'GitLab API GET {endpoint} expected a list, got {type(chunk).__name__}'
|
|
103
|
+
)
|
|
104
|
+
collected.extend(chunk)
|
|
105
|
+
if len(chunk) < GITLAB_PAGE_SIZE:
|
|
106
|
+
break
|
|
107
|
+
page += 1
|
|
108
|
+
return collected
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _issue_milestone_title(issue: dict) -> str:
|
|
112
|
+
milestone = issue.get('milestone') or {}
|
|
113
|
+
if isinstance(milestone, dict):
|
|
114
|
+
return (milestone.get('title') or '').strip()
|
|
115
|
+
return ''
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _is_duplicate_issue(issue: dict, title: str, milestone: str = None) -> bool:
|
|
119
|
+
"""True when titles match and the issue is in the target milestone or has none."""
|
|
120
|
+
if not is_similar(title, issue.get('title', '')):
|
|
121
|
+
return False
|
|
122
|
+
existing = _issue_milestone_title(issue)
|
|
123
|
+
if milestone:
|
|
124
|
+
return (not existing) or is_similar(milestone, existing)
|
|
125
|
+
return not existing
|
|
71
126
|
|
|
72
127
|
|
|
73
128
|
def _list_group_projects(api_host: str, group_path: str, token: str) -> list:
|
|
74
|
-
from urllib.parse import quote
|
|
75
129
|
encoded = quote(group_path, safe='')
|
|
76
130
|
return _gitlab_api_call(
|
|
77
131
|
api_host,
|
|
@@ -113,27 +167,51 @@ def create_gitlab_issue(api_host: str, project_id: int, payload: dict, token: st
|
|
|
113
167
|
|
|
114
168
|
|
|
115
169
|
def get_project_by_path(api_host: str, project_path: str, token: str) -> dict:
|
|
116
|
-
from urllib.parse import quote
|
|
117
170
|
encoded = quote(project_path, safe='')
|
|
118
171
|
return _gitlab_api_call(api_host, f'/projects/{encoded}', token)
|
|
119
172
|
|
|
120
173
|
|
|
121
174
|
def find_project_milestone(api_host: str, project_id: int, title: str, token: str):
|
|
122
|
-
|
|
175
|
+
"""Find a project or ancestor-group milestone by title."""
|
|
176
|
+
encoded = quote(title, safe='')
|
|
177
|
+
base = f'/projects/{project_id}/milestones?include_ancestors=true'
|
|
178
|
+
exact = _gitlab_api_list(api_host, f'{base}&title={encoded}', token)
|
|
179
|
+
match = find_similar(title, exact)
|
|
180
|
+
if match:
|
|
181
|
+
return match
|
|
182
|
+
searched = _gitlab_api_list(api_host, f'{base}&search={encoded}', token)
|
|
183
|
+
return find_similar(title, searched)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _list_issues_for_milestone(api_host: str, project_id: int, milestone: str, token: str) -> list:
|
|
187
|
+
encoded = quote(milestone, safe='') if milestone else 'None'
|
|
188
|
+
return _gitlab_api_list(
|
|
123
189
|
api_host,
|
|
124
|
-
f'/projects/{project_id}/
|
|
190
|
+
f'/projects/{project_id}/issues?milestone={encoded}',
|
|
125
191
|
token,
|
|
126
192
|
)
|
|
127
|
-
return find_similar(title, results)
|
|
128
193
|
|
|
129
194
|
|
|
130
195
|
def find_project_issue(api_host: str, project_id: int, title: str, token: str, milestone: str = None):
|
|
131
|
-
"""Look for a ~matching title
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
196
|
+
"""Look for a ~matching title in this milestone, or among issues with no milestone."""
|
|
197
|
+
encoded = quote(title, safe='')
|
|
198
|
+
searched = _gitlab_api_list(
|
|
199
|
+
api_host,
|
|
200
|
+
f'/projects/{project_id}/issues?search={encoded}&in=title',
|
|
201
|
+
token,
|
|
202
|
+
)
|
|
203
|
+
for item in searched:
|
|
204
|
+
if _is_duplicate_issue(item, title, milestone):
|
|
205
|
+
return item
|
|
206
|
+
|
|
207
|
+
for item in _list_issues_for_milestone(api_host, project_id, milestone, token):
|
|
208
|
+
if _is_duplicate_issue(item, title, milestone):
|
|
209
|
+
return item
|
|
210
|
+
if milestone:
|
|
211
|
+
for item in _list_issues_for_milestone(api_host, project_id, None, token):
|
|
212
|
+
if _is_duplicate_issue(item, title, milestone):
|
|
213
|
+
return item
|
|
214
|
+
return None
|
|
137
215
|
|
|
138
216
|
|
|
139
217
|
def create_project_milestone(
|
{itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: itw_python_builder
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.15
|
|
4
4
|
Summary: Standardized Django deployment pipeline with Docker, testing, and SonarQube integration
|
|
5
5
|
Author-email: IT-Works <contact@it-works.io>
|
|
6
6
|
License: MIT
|
|
@@ -295,11 +295,11 @@ With `/repo`, leading and trailing slashes are stripped automatically, and full
|
|
|
295
295
|
|
|
296
296
|
### Milestones, Labels and Duplicates
|
|
297
297
|
|
|
298
|
-
Milestones, labels and issue titles are matched with a shared fuzzy comparison at 95% similarity, so small differences in spacing or casing still count as a match. The threshold is a single constant (`SIMILARITY_THRESHOLD` in `task_utils.py`) if you need to tune it.
|
|
298
|
+
Milestones, labels and issue titles are matched with a shared fuzzy comparison at 95% similarity, so small differences in spacing or casing still count as a match. Digit runs must be identical (`java 38` does not match `java 37`). The threshold is a single constant (`SIMILARITY_THRESHOLD` in `task_utils.py`) if you need to tune it.
|
|
299
299
|
|
|
300
|
-
- **Milestone** —
|
|
300
|
+
- **Milestone** — an existing project or parent-group milestone with the same title is reused; otherwise it is created (with the start/end dates when both are given).
|
|
301
301
|
- **Label** — a similar existing label is linked and logged in white; a new one is created and logged in green. An empty `/label` is never sent to GitLab.
|
|
302
|
-
- **Duplicate issues** — before creating, the target project is
|
|
302
|
+
- **Duplicate issues** — before creating, the target project is searched by title. A match in the same milestone, or an unassigned issue with the same title, is treated as a duplicate and skipped.
|
|
303
303
|
|
|
304
304
|
### Acceptance Criteria and Comments
|
|
305
305
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "itw_python_builder"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.15"
|
|
8
8
|
description = "Standardized Django deployment pipeline with Docker, testing, and SonarQube integration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
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
|
{itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/requires.txt
RENAMED
|
File without changes
|
{itw_python_builder-0.2.14 → itw_python_builder-0.2.15}/itw_python_builder.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|