kiba-build 0.1.11.dev1__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.
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.2
2
+ Name: kiba-build
3
+ Version: 0.1.11.dev1
4
+ Summary: Kiba Labs' python building and testing utilities
5
+ Requires-Python: ~=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: asyncclick~=8.1.8
8
+ Requires-Dist: bandit~=1.8.3
9
+ Requires-Dist: bump-my-version>=0.29.0
10
+ Requires-Dist: click~=8.1.7
11
+ Requires-Dist: mypy~=1.15.0
12
+ Requires-Dist: pylint~=3.3.4
13
+ Requires-Dist: simple-chalk~=0.1.0
14
+ Requires-Dist: twine~=6.1.0
15
+ Requires-Dist: wheel~=0.45.1
16
+
17
+ # Build-Py
@@ -0,0 +1 @@
1
+ # Build-Py
File without changes
@@ -0,0 +1,61 @@
1
+ import os
2
+ from typing import List
3
+
4
+ import click
5
+ from pylint.lint import Run as run_pylint
6
+ from pylint.reporters import CollectingReporter
7
+
8
+ from buildpy.util import GitHubAnnotationsReporter
9
+ from buildpy.util import Message
10
+ from buildpy.util import MessageParser
11
+ from buildpy.util import PrettyReporter
12
+
13
+
14
+ class PylintMessageParser(CollectingReporter, MessageParser):
15
+
16
+ @staticmethod
17
+ def _get_error_level(pylintLevel: str) -> str:
18
+ pylintLevel = pylintLevel.lower()
19
+ if pylintLevel == 'info':
20
+ return 'notice'
21
+ if pylintLevel == 'warning':
22
+ return 'warning'
23
+ return 'error'
24
+
25
+ def parse_messages(self, rawMessages: List[str]) -> List[Message]:
26
+ raise NotImplementedError
27
+
28
+ def get_messages(self) -> List[Message]:
29
+ output: List[Message] = []
30
+ for rawMessage in self.messages:
31
+ output.append(Message(
32
+ path=os.path.relpath(rawMessage.abspath),
33
+ line=rawMessage.line,
34
+ column=rawMessage.column + 1,
35
+ code=rawMessage.symbol,
36
+ text=rawMessage.msg.strip() or '',
37
+ level=self._get_error_level(pylintLevel=rawMessage.category),
38
+ ))
39
+ return output
40
+
41
+
42
+ @click.command()
43
+ @click.argument('targets', nargs=-1)
44
+ @click.option('-o', '--output-file', 'outputFilename', required=False, type=str)
45
+ @click.option('-f', '--output-format', 'outputFormat', required=False, type=str, default='pretty')
46
+ @click.option('-c', '--config-file-path', 'configFilePath', required=False, type=str)
47
+ def run(targets: List[str], outputFilename: str, outputFormat: str, configFilePath: str) -> None:
48
+ currentDirectory = os.path.dirname(os.path.realpath(__file__))
49
+ pylintConfigFilePath = configFilePath or f'{currentDirectory}/pyproject.toml'
50
+ pylintMessageParser = PylintMessageParser()
51
+ run_pylint([f'--rcfile={pylintConfigFilePath}', f'--jobs=0'] + list(targets), reporter=pylintMessageParser, exit=False)
52
+ reporter = GitHubAnnotationsReporter() if outputFormat == 'annotations' else PrettyReporter()
53
+ output = reporter.create_output(messages=pylintMessageParser.get_messages())
54
+ if outputFilename:
55
+ with open(outputFilename, 'w') as outputFile:
56
+ outputFile.write(output)
57
+ else:
58
+ print(output)
59
+
60
+ if __name__ == '__main__':
61
+ run() # pylint: disable=no-value-for-parameter
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561 (https://www.python.org/dev/peps/pep-0561/). This package uses inline types.
@@ -0,0 +1,156 @@
1
+ import os
2
+ import subprocess
3
+ from typing import List
4
+
5
+ import click
6
+
7
+ from buildpy.util import GitHubAnnotationsReporter
8
+ from buildpy.util import Message
9
+ from buildpy.util import MessageParser
10
+ from buildpy.util import PrettyReporter
11
+
12
+ # TODO(krishan711): remove this once test_name is enabled https://github.com/PyCQA/bandit/issues/962
13
+ BANDIT_CODES = {
14
+ 'B101': 'assert_used',
15
+ 'B102': 'exec_used',
16
+ 'B103': 'set_bad_file_permissions',
17
+ 'B104': 'hardcoded_bind_all_interfaces',
18
+ 'B105': 'hardcoded_password_string',
19
+ 'B106': 'hardcoded_password_funcarg',
20
+ 'B107': 'hardcoded_password_default',
21
+ 'B108': 'hardcoded_tmp_directory',
22
+ 'B110': 'try_except_pass',
23
+ 'B112': 'try_except_continue',
24
+ 'B201': 'flask_debug_true',
25
+ 'B301': 'pickle',
26
+ 'B302': 'marshal',
27
+ 'B303': 'md5',
28
+ 'B304': 'ciphers',
29
+ 'B305': 'cipher_modes',
30
+ 'B306': 'mktemp_q',
31
+ 'B307': 'eval',
32
+ 'B308': 'mark_safe',
33
+ 'B309': 'httpsconnection',
34
+ 'B310': 'urllib_urlopen',
35
+ 'B311': 'random',
36
+ 'B312': 'telnetlib',
37
+ 'B313': 'xml_bad_cElementTree',
38
+ 'B314': 'xml_bad_ElementTree',
39
+ 'B315': 'xml_bad_expatreader',
40
+ 'B316': 'xml_bad_expatbuilder',
41
+ 'B317': 'xml_bad_sax',
42
+ 'B318': 'xml_bad_minidom',
43
+ 'B319': 'xml_bad_pulldom',
44
+ 'B320': 'xml_bad_etree',
45
+ 'B321': 'ftplib',
46
+ 'B323': 'unverified_context',
47
+ 'B324': 'hashlib_insecure_functions',
48
+ 'B325': 'tempnam',
49
+ 'B401': 'import_telnetlib',
50
+ 'B402': 'import_ftplib',
51
+ 'B403': 'import_pickle',
52
+ 'B404': 'import_subprocess',
53
+ 'B405': 'import_xml_etree',
54
+ 'B406': 'import_xml_sax',
55
+ 'B407': 'import_xml_expat',
56
+ 'B408': 'import_xml_minidom',
57
+ 'B409': 'import_xml_pulldom',
58
+ 'B410': 'import_lxml',
59
+ 'B411': 'import_xmlrpclib',
60
+ 'B412': 'import_httpoxy',
61
+ 'B413': 'import_pycrypto',
62
+ 'B415': 'import_pyghmi',
63
+ 'B501': 'request_with_no_cert_validation',
64
+ 'B502': 'ssl_with_bad_version',
65
+ 'B503': 'ssl_with_bad_defaults',
66
+ 'B504': 'ssl_with_no_version',
67
+ 'B505': 'weak_cryptographic_key',
68
+ 'B506': 'yaml_load',
69
+ 'B507': 'ssh_no_host_key_verification',
70
+ 'B508': 'snmp_insecure_version',
71
+ 'B509': 'snmp_weak_cryptography',
72
+ 'B601': 'paramiko_calls',
73
+ 'B602': 'subprocess_popen_with_shell_equals_true',
74
+ 'B603': 'subprocess_without_shell_equals_true',
75
+ 'B604': 'any_other_function_with_shell_equals_true',
76
+ 'B605': 'start_process_with_a_shell',
77
+ 'B606': 'start_process_with_no_shell',
78
+ 'B607': 'start_process_with_partial_path',
79
+ 'B608': 'hardcoded_sql_expressions',
80
+ 'B609': 'linux_commands_wildcard_injection',
81
+ 'B610': 'django_extra_used',
82
+ 'B611': 'django_rawsql_used',
83
+ 'B701': 'jinja2_autoescape_false',
84
+ 'B702': 'use_of_mako_templates',
85
+ 'B703': 'django_mark_safe',
86
+ }
87
+
88
+ class BanditMessageParser(MessageParser):
89
+
90
+ @staticmethod
91
+ def _get_error_level(banditLevel: str) -> str:
92
+ banditLevel = banditLevel.lower()
93
+ if banditLevel == 'low':
94
+ return 'notice'
95
+ if banditLevel == 'warning':
96
+ return 'warning'
97
+ return 'error'
98
+
99
+ def parse_messages(self, rawMessages: List[str]) -> List[Message]:
100
+ output: List[Message] = []
101
+ for rawMessage in rawMessages:
102
+ rawMessage = rawMessage.strip()
103
+ if len(rawMessage) == 0 or rawMessage.startswith('filename,test_name,'):
104
+ continue
105
+ print(rawMessage)
106
+ rawMessageParts = rawMessage.split('\t')
107
+ if len(rawMessageParts) == 1:
108
+ continue
109
+ if rawMessageParts[0] == '[tester]':
110
+ output.append(Message(
111
+ path='',
112
+ line=0,
113
+ column=0,
114
+ code='tester',
115
+ text=rawMessageParts[2],
116
+ level=self._get_error_level(banditLevel=rawMessageParts[1]),
117
+ ))
118
+ else:
119
+ output.append(Message(
120
+ path=rawMessageParts[0],
121
+ line=int(rawMessageParts[1]),
122
+ column=int(rawMessageParts[2]),
123
+ code=BANDIT_CODES[rawMessageParts[3]],
124
+ text=rawMessageParts[4],
125
+ level=self._get_error_level(banditLevel=rawMessageParts[5]),
126
+ ))
127
+ return output
128
+
129
+
130
+ @click.command()
131
+ @click.argument('targets', nargs=-1)
132
+ @click.option('-o', '--output-file', 'outputFilename', required=False, type=str)
133
+ @click.option('-f', '--output-format', 'outputFormat', required=False, type=str, default='pretty')
134
+ @click.option('-c', '--config-file-path', 'configFilePath', required=False, type=str)
135
+ def run(targets: List[str], outputFilename: str, outputFormat: str, configFilePath: str) -> None:
136
+ currentDirectory = os.path.dirname(os.path.realpath(__file__))
137
+ messages: List[str] = []
138
+ banditConfigFilePath = configFilePath or f'{currentDirectory}/pyproject.toml'
139
+ # TODO(krishan711): use test_name here once enabled https://github.com/PyCQA/bandit/issues/962
140
+ messageTemplate = '{abspath}\t{line}\t{col}\t{test_id}\t{msg}\t{severity}'
141
+ try:
142
+ subprocess.check_output(f'bandit --configfile {banditConfigFilePath} --silent --severity-level medium --confidence-level medium --format custom --msg-template \"{messageTemplate}\" --recursive {" ".join(targets)}', stderr=subprocess.STDOUT, shell=True) # nosec=subprocess_popen_with_shell_equals_true
143
+ except subprocess.CalledProcessError as exception:
144
+ messages = exception.output.decode().split('\n')
145
+ messageParser = BanditMessageParser()
146
+ parsedMessages = messageParser.parse_messages(rawMessages=messages)
147
+ reporter = GitHubAnnotationsReporter() if outputFormat == 'annotations' else PrettyReporter()
148
+ output = reporter.create_output(messages=parsedMessages)
149
+ if outputFilename:
150
+ with open(outputFilename, 'w') as outputFile:
151
+ outputFile.write(output)
152
+ else:
153
+ print(output)
154
+
155
+ if __name__ == '__main__':
156
+ run() # pylint: disable=no-value-for-parameter
@@ -0,0 +1,67 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+ from typing import List
5
+
6
+ import click
7
+
8
+ from buildpy.util import GitHubAnnotationsReporter
9
+ from buildpy.util import Message
10
+ from buildpy.util import MessageParser
11
+ from buildpy.util import PrettyReporter
12
+
13
+
14
+ class MypyMessageParser(MessageParser):
15
+
16
+ def parse_messages(self, rawMessages: List[str]) -> List[Message]:
17
+ output: List[Message] = []
18
+ for rawMessage in rawMessages:
19
+ rawMessage = rawMessage.strip()
20
+ if len(rawMessage) == 0 \
21
+ or ' note: ' in rawMessage \
22
+ or 'Use "-> None" if function does not return a value' in rawMessage:
23
+ continue
24
+ if 'Error importing plugin "pydantic.mypy":' in rawMessage:
25
+ # NOTE(krishan711): we use pydantic rules but it may not be installed (like in this repo)
26
+ continue
27
+ match1 = re.match(r'(.*):(\d*):(\d*): (.*): (.*) \[(.*)\]', rawMessage)
28
+ match2 = None
29
+ if not match1:
30
+ # match2 = match1 without column number
31
+ match2 = re.match(r'(.*):(\d*): (.*): (.*) \[(.*)\]', rawMessage)
32
+ output.append(Message(
33
+ path=match1.group(1) if match1 else (match2.group(1) if match2 else ''),
34
+ line=int(match1.group(2)) if match1 else (int(match2.group(2)) if match2 else 0),
35
+ column=int(match1.group(3)) if match1 else (0 if match2 else 0),
36
+ code=match1.group(6) if match1 else (match2.group(5) if match2 else 'unparsed'),
37
+ text=match1.group(5).strip() if match1 else (match2.group(4).strip() if match2 else rawMessage.strip()),
38
+ level=match1.group(4) if match1 else (match2.group(3) if match2 else 'error'),
39
+ ))
40
+ return output
41
+
42
+
43
+ @click.command()
44
+ @click.argument('targets', nargs=-1)
45
+ @click.option('-o', '--output-file', 'outputFilename', required=False, type=str)
46
+ @click.option('-f', '--output-format', 'outputFormat', required=False, type=str, default='pretty')
47
+ @click.option('-c', '--config-file-path', 'configFilePath', required=False, type=str)
48
+ def run(targets: List[str], outputFilename: str, outputFormat: str, configFilePath: str) -> None:
49
+ currentDirectory = os.path.dirname(os.path.realpath(__file__))
50
+ mypyConfigFilePath = configFilePath or f'{currentDirectory}/pyproject.toml'
51
+ messages: List[str] = []
52
+ try:
53
+ subprocess.check_output(f'mypy {" ".join(targets)} --config-file {mypyConfigFilePath} --no-color-output --hide-error-context --no-pretty --no-error-summary --show-error-codes --show-column-numbers', stderr=subprocess.STDOUT, shell=True) # nosec=subprocess_popen_with_shell_equals_true
54
+ except subprocess.CalledProcessError as exception:
55
+ messages = exception.output.decode().split('\n')
56
+ messageParser = MypyMessageParser()
57
+ parsedMessages = messageParser.parse_messages(rawMessages=messages)
58
+ reporter = GitHubAnnotationsReporter() if outputFormat == 'annotations' else PrettyReporter()
59
+ output = reporter.create_output(messages=parsedMessages)
60
+ if outputFilename:
61
+ with open(outputFilename, 'w') as outputFile:
62
+ outputFile.write(output)
63
+ else:
64
+ print(output)
65
+
66
+ if __name__ == '__main__':
67
+ run() # pylint: disable=no-value-for-parameter
@@ -0,0 +1,83 @@
1
+ import dataclasses
2
+ import json
3
+ import os
4
+ from collections import defaultdict
5
+ from typing import List
6
+
7
+ from simple_chalk import chalk # type: ignore
8
+
9
+
10
+ @dataclasses.dataclass
11
+ class Message:
12
+ path: str
13
+ line: int
14
+ column: int
15
+ code: str
16
+ text: str
17
+ level: str # (error, warning, notice)
18
+
19
+
20
+ class MessageParser:
21
+
22
+ def parse_messages(self, rawMessages: List[str]) -> List[Message]:
23
+ raise NotImplementedError
24
+
25
+
26
+ class KibaReporter:
27
+
28
+ def create_output(self, messages: List[Message]) -> str:
29
+ raise NotImplementedError
30
+
31
+
32
+ class GitHubAnnotationsReporter(KibaReporter):
33
+
34
+ def create_output(self, messages: List[Message]) -> str:
35
+ annotations = []
36
+ for message in messages:
37
+ annotation = {
38
+ 'path': os.path.relpath(message.path) if message.path else '<unknown>',
39
+ 'start_line': message.line,
40
+ 'end_line': message.line,
41
+ 'start_column': message.column,
42
+ 'end_column': message.column,
43
+ 'message': f'[{message.code}] {message.text}',
44
+ 'annotation_level': 'notice' if message.level == 'notice' else ('warning' if message.level == 'warning' else 'failure'),
45
+ }
46
+ annotations.append(annotation)
47
+ return json.dumps(annotations)
48
+
49
+
50
+ class PrettyReporter(KibaReporter):
51
+
52
+ @staticmethod
53
+ def get_summary(errorCount: int, warningCount: int) -> str:
54
+ summary = ''
55
+ if errorCount:
56
+ summary += chalk.red(f'{errorCount} errors')
57
+ if warningCount:
58
+ summary = f'{summary} and ' if summary else ''
59
+ summary += chalk.yellow(f'{warningCount} warnings')
60
+ return summary
61
+
62
+ def create_output(self, messages: List[Message]) -> str:
63
+ fileMessageMap = defaultdict(list)
64
+ for message in messages:
65
+ fileMessageMap[os.path.relpath(message.path) if message.path else '<unknown>'].append(message)
66
+ totalErrorCount = 0
67
+ totalWarningCount = 0
68
+ outputs = []
69
+ for (filePath, fileMessages) in fileMessageMap.items():
70
+ fileOutputs = []
71
+ for message in fileMessages:
72
+ location = chalk.grey(f'{filePath}:{message.line}:{message.column}')
73
+ color = chalk.white if message.level == 'notice' else (chalk.yellow if message.level == 'warning' else chalk.red)
74
+ fileOutputs.append(f'{location} [{color(message.code)}] {message.text}')
75
+ errorCount = sum(1 for message in fileMessages if message.level == 'error')
76
+ totalErrorCount += errorCount
77
+ warningCount = sum(1 for message in fileMessages if message.level == 'warning')
78
+ totalWarningCount += warningCount
79
+ fileOutputString = '\n'.join(fileOutputs)
80
+ outputs.append(f'{self.get_summary(errorCount, warningCount)} in {filePath}\n{fileOutputString}\n')
81
+ output = '\n'.join(outputs)
82
+ output += f'\nFailed due to {self.get_summary(totalErrorCount, totalWarningCount)}.' if (totalErrorCount or totalWarningCount) else chalk.green('Passed.')
83
+ return output
@@ -0,0 +1,29 @@
1
+ import importlib.metadata
2
+ import subprocess
3
+ import tomllib
4
+
5
+ import click
6
+
7
+
8
+ def generate_command(part: str) -> str:
9
+ with open('pyproject.toml', 'r') as file:
10
+ pyproject = tomllib.loads(file.read())
11
+ currentVersion = pyproject['project']['version']
12
+ return f"bump-my-version bump --allow-dirty --current-version \"{currentVersion}\" --parse '(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?' --serialize '{{major}}.{{minor}}.{{patch}}.dev{{dev}}' --serialize '{{major}}.{{minor}}.{{patch}}' {part} pyproject.toml"
13
+
14
+ @click.command()
15
+ @click.option('-p', '--part', 'part', required=True, type=str)
16
+ @click.option('-c', '--count', 'count', required=False, type=int, default=1)
17
+ def run(part: str, count: int) -> None:
18
+ print('here', part, count)
19
+ if count == 0:
20
+ print('No version bump required')
21
+ return
22
+ # NOTE(krishan711): for dev releases bump the patch first
23
+ if part == 'dev':
24
+ subprocess.check_output(generate_command(part='patch'), stderr=subprocess.STDOUT, shell=True) # nosec=subprocess_popen_with_shell_equals_true
25
+ for _ in range(count):
26
+ subprocess.check_output(generate_command(part=part), stderr=subprocess.STDOUT, shell=True) # nosec=subprocess_popen_with_shell_equals_true
27
+
28
+ if __name__ == '__main__':
29
+ run() # pylint: disable=no-value-for-parameter
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.2
2
+ Name: kiba-build
3
+ Version: 0.1.11.dev1
4
+ Summary: Kiba Labs' python building and testing utilities
5
+ Requires-Python: ~=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: asyncclick~=8.1.8
8
+ Requires-Dist: bandit~=1.8.3
9
+ Requires-Dist: bump-my-version>=0.29.0
10
+ Requires-Dist: click~=8.1.7
11
+ Requires-Dist: mypy~=1.15.0
12
+ Requires-Dist: pylint~=3.3.4
13
+ Requires-Dist: simple-chalk~=0.1.0
14
+ Requires-Dist: twine~=6.1.0
15
+ Requires-Dist: wheel~=0.45.1
16
+
17
+ # Build-Py
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ buildpy/__init__.py
4
+ buildpy/lint_check.py
5
+ buildpy/py.typed
6
+ buildpy/security_check.py
7
+ buildpy/type_check.py
8
+ buildpy/util.py
9
+ buildpy/version.py
10
+ kiba_build.egg-info/PKG-INFO
11
+ kiba_build.egg-info/SOURCES.txt
12
+ kiba_build.egg-info/dependency_links.txt
13
+ kiba_build.egg-info/entry_points.txt
14
+ kiba_build.egg-info/requires.txt
15
+ kiba_build.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ lint-check = buildpy.lint_check:run
3
+ security-check = buildpy.security_check:run
4
+ type-check = buildpy.type_check:run
5
+ version = buildpy.version:run
@@ -0,0 +1,9 @@
1
+ asyncclick~=8.1.8
2
+ bandit~=1.8.3
3
+ bump-my-version>=0.29.0
4
+ click~=8.1.7
5
+ mypy~=1.15.0
6
+ pylint~=3.3.4
7
+ simple-chalk~=0.1.0
8
+ twine~=6.1.0
9
+ wheel~=0.45.1
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "kiba-build"
3
+ version = "0.1.11.dev1"
4
+ description = "Kiba Labs' python building and testing utilities"
5
+ readme = "README.md"
6
+ requires-python = "~=3.11"
7
+ dependencies = [
8
+ "asyncclick~=8.1.8",
9
+ "bandit~=1.8.3",
10
+ "bump-my-version>=0.29.0",
11
+ "click~=8.1.7",
12
+ "mypy~=1.15.0",
13
+ "pylint~=3.3.4",
14
+ "simple-chalk~=0.1.0",
15
+ "twine~=6.1.0",
16
+ "wheel~=0.45.1",
17
+ ]
18
+
19
+ [project.scripts]
20
+ lint-check = "buildpy.lint_check:run"
21
+ type-check = "buildpy.type_check:run"
22
+ security-check = "buildpy.security_check:run"
23
+ version = "buildpy.version:run"
24
+
25
+ [tool.uv]
26
+ package = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+