File: python.py

package info (click to toggle)
python-azure 20250603%2Bgit-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, trixie
  • size: 851,724 kB
  • sloc: python: 7,362,925; ansic: 804; javascript: 287; makefile: 195; sh: 145; xml: 109
file content (200 lines) | stat: -rw-r--r-- 8,760 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
from datetime import datetime
from collections import Counter
import re
import yaml
from typing import Any, List, Dict, Set

from github.Repository import Repository

from common import IssueProcess, Common, get_origin_link_and_tag, IssuePackage
from utils import AUTO_CLOSE_LABEL, get_last_released_date, record_release, get_python_release_pipeline, run_pipeline

# assignee dict which will be assigned to handle issues
_PYTHON_OWNER = {'azure-sdk', 'msyyc'}
_PYTHON_ASSIGNEE = {'ChenxiJiang333'}

# labels
_CONFIGURED = 'Configured'
_AUTO_ASK_FOR_CHECK = 'auto-ask-check'
_BRANCH_ATTENTION = 'base-branch-attention'
_MultiAPI = 'MultiAPI'
# record published issues
_FILE_OUT = 'published_issues_python.csv'


class IssueProcessPython(IssueProcess):

    def __init__(self, issue_package: IssuePackage, request_repo: Repository,
                 assignee_candidates: Set[str], language_owner: Set[str]):
        IssueProcess.__init__(self, issue_package, request_repo, assignee_candidates, language_owner)
        self.output_folder = '' # network of sdk/network/azure-mgmt-XXX
        self.delay_time = self.get_delay_time()
        self.python_tag = ''
        self.rest_repo_hash = ''
        self.language_name = 'python'

    def get_delay_time(self):
        q = [comment.updated_at
             for comment in self.issue_package.issue.get_comments() if comment.user.login not in self.language_owner]
        q.sort()
        return (datetime.now() - (self.created_time if not q else q[-1])).days

    @staticmethod
    def get_specefied_param(pattern: str, issue_body_list: List[str]) -> str:
        for line in issue_body_list:
            if pattern in line:
                return line.split(":", 1)[-1].strip()
        return ""

    def multi_api_policy(self) -> None:
        if self.has_label(_MultiAPI) and not self.has_label(_AUTO_ASK_FOR_CHECK):
            self.bot_advice.append(_MultiAPI)

    def get_edit_content(self) -> None:
        self.edit_content = f'\n{self.readme_link.replace("/readme.md", "")}\nReadme Tag: {self.target_readme_tag}'

    @property
    def is_multiapi(self):
        return self.has_label(_MultiAPI)

    @property
    def readme_comparison(self) -> bool:
        # to see whether need change readme
        if self.has_label(_CONFIGURED):
            return False
        if 'package-' not in self.target_readme_tag:
            return True
        contents = self.get_local_file_content()
        pattern_tag = re.compile(r'tag: package-[\w+-.]+')
        package_tags = pattern_tag.findall(contents)
        whether_same_tag = self.target_readme_tag in package_tags[0]
        whether_change_readme = not whether_same_tag or self.is_multiapi
        return whether_change_readme

    def auto_reply(self) -> None:
        if not self.has_label(_AUTO_ASK_FOR_CHECK) or self.has_label(_CONFIGURED):
            issue_number = self.issue_package.issue.number
            if not self.readme_comparison:
                try:
                    spec_readme = self.readme_link + ('' if self.has_typespec_folder else '/readme.md') 
                    issue_link = self.issue_package.issue.html_url
                    release_pipeline_url = get_python_release_pipeline(self.output_folder)
                    res_run = run_pipeline(issue_link=issue_link,
                                           pipeline_url=release_pipeline_url,
                                           spec_readme=spec_readme,
                                           python_tag=self.python_tag,
                                           rest_repo_hash=self.rest_repo_hash,
                                           target_date=self.target_date,
                                           issue_owner=self.owner,
                                           )
                    if res_run:
                        self.log(f'{issue_number} run pipeline successfully')
                    else:
                        self.log(f'{issue_number} run pipeline fail')
                except Exception as e:
                    self.comment(f'hi @{self.assignee}, please check release-helper: `{e}`')
                self.add_label(_AUTO_ASK_FOR_CHECK)
            else:
                self.log(f'issue {issue_number} need config readme')

            if self.has_label(_CONFIGURED):
                self.issue_package.issue.remove_from_labels(_CONFIGURED)

    def attention_policy(self):
        if self.has_label(_BRANCH_ATTENTION):
            self.bot_advice.append('new version is 0.0.0, please check base branch!')

    def auto_bot_advice(self):
        super().auto_bot_advice()
        self.multi_api_policy()
        self.attention_policy()

    def auto_close(self) -> None:
        if self.has_label(AUTO_CLOSE_LABEL):
            return
        last_version, last_time = get_last_released_date(self.package_name)
        if last_version and last_time > self.created_time:
            comment = f'Hi @{self.owner}, pypi link: https://pypi.org/project/{self.package_name}/{last_version}/'
            self.issue_package.issue.create_comment(body=comment)
            self.issue_package.issue.edit(state='closed')
            self.add_label(AUTO_CLOSE_LABEL)
            self.is_open = False
            self.log(f"{self.issue_package.issue.number} has been closed!")
            record_release(self.package_name, self.issue_package.issue, _FILE_OUT, last_version)

    def package_name_output_folder_from_readme(self):
        try:
            contents = self.get_local_file_content('readme.python.md')
        except Exception as e: # pylint: disable=too-broad-except
            print(f"fail to read readme.python.md: {e}")
            return
        pattern_package = re.compile(r'package-name: [\w+-.]+')
        pattern_output = re.compile(r'\$\(python-sdks-folder\)/(.*?)/azure-')
        self.package_name = pattern_package.search(contents).group().split(':')[-1].strip()
        self.output_folder = pattern_output.search(contents).group().split('/')[1]
        if 'multi-api' in contents:
            self.add_label(_MultiAPI)

    def package_name_output_folder_from_tspconfig(self):
        try:
            contents = self.get_local_file_content('tspconfig.yaml')
        except Exception as e: # pylint: disable=too-broad-except
            print(f"fail to read tspconfig.yaml: {e}")
            return
        yaml_contents = yaml.safe_load(contents)
        # tspconfig.yaml example: https://github.com/Azure/azure-rest-api-specs/blob/main/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml
        self.output_folder = yaml_contents.get("parameters", {}).get("service-dir", {}).get("default", "").split('/')[-1]
        emitters = yaml_contents.get("options", {})
        for emitter_name in emitters:
            if "/typespec-python" in emitter_name:
                self.package_name = emitters[emitter_name].get("package-dir", "")
                break

    @property
    def has_typespec_folder(self) -> bool:
        return self.local_file("tspconfig.yaml").exists()

    def auto_parse(self):
        super().auto_parse()
        issue_body_list = self.get_issue_body()
        self.readme_link = issue_body_list[0].strip("\r\n ")

        if not self.package_name and self.has_typespec_folder:
            self.package_name_output_folder_from_tspconfig()
        if not self.package_name and re.findall(".+/Azure/azure-rest-api-specs/.+/resource-manager", self.readme_link):
            self.package_name_output_folder_from_readme()
        if not self.package_name:
            raise Exception(f"package name not found in readme.python.md or tspconfig.yaml")

        # Get the specified tag and rest repo hash in issue body
        self.rest_repo_hash = self.get_specefied_param("->hash:", issue_body_list[:5])
        self.python_tag = self.get_specefied_param("->Readme Tag:", issue_body_list[:5])


    def run(self) -> None:
        super().run()
        self.auto_reply()
        self.auto_close()


class Python(Common):
    def __init__(self, issues, language_owner, sdk_assignees):
        super(Python, self).__init__(issues, language_owner, sdk_assignees)
        self.issue_process_function = IssueProcessPython
        if not self.for_test():
            self.file_out_name = 'release_python_status.md'

    def duplicated_policy(self):
        counter = Counter([item.package_name for item in self.result])
        for item in self.result:
            if counter[item.package_name] > 1:
                item.bot_advice.insert(0, 'duplicated issue  <br>')

    def run(self):
        self.proc_issue()
        self.duplicated_policy()
        self.output()


def python_process(issues: List[Any]) -> Python:
    return Python(issues, _PYTHON_OWNER, _PYTHON_ASSIGNEE)