logrunner 0.0.4__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2026] [Chris 'KageKirin' Helmich]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: logrunner
3
+ Version: 0.0.4
4
+ Summary: logrunner.run([cmd]) wraps subprocess.run([cmd]) to allow continuous output as well as capture of stdout
5
+ Author-email: kagekirin <kagekirin@gmail.com>
6
+ Maintainer-email: kagekirin <kagekirin@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/KageKirin/py-logrunner
9
+ Project-URL: Documentation, https://github.com/KageKirin/py-logrunner
10
+ Project-URL: Repository, https://github.com/KageKirin/py-logrunner.git
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=9.0; extra == "dev"
21
+ Requires-Dist: ruff; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # Logrunner
25
+
26
+ `logrunner.run([cmd])` wraps `subprocess.run([cmd])` to allow continuous output as well as capture of stdout and stderr.
27
+
28
+ This function is intended for usage in CI and other process monitoring applications where
29
+ the process outputs need to be written for real-time error tracking
30
+ and captured for post-processing text analysis.
31
+
32
+ ## ⚡ Usage
33
+
34
+ ```python
35
+ import logrunner
36
+
37
+ (ret, out, err) = logrunner.run(["echo", "hello"])
38
+ # returns (0, "hello", "")
39
+
40
+ (ret, out, err) = logrunner.run(["ls", "/nonexistentpath"])
41
+ # return (1, "", "No such file or directory")
42
+ ```
43
+
44
+ ## 🔧 Building
45
+
46
+ ```shell
47
+ uv build
48
+ ```
49
+
50
+ ## 🤝 Collaborate with My Project
51
+
52
+ Please refer to the [collaboration guidelines](./COLLABORATION.md) for details.
@@ -0,0 +1,29 @@
1
+ # Logrunner
2
+
3
+ `logrunner.run([cmd])` wraps `subprocess.run([cmd])` to allow continuous output as well as capture of stdout and stderr.
4
+
5
+ This function is intended for usage in CI and other process monitoring applications where
6
+ the process outputs need to be written for real-time error tracking
7
+ and captured for post-processing text analysis.
8
+
9
+ ## ⚡ Usage
10
+
11
+ ```python
12
+ import logrunner
13
+
14
+ (ret, out, err) = logrunner.run(["echo", "hello"])
15
+ # returns (0, "hello", "")
16
+
17
+ (ret, out, err) = logrunner.run(["ls", "/nonexistentpath"])
18
+ # return (1, "", "No such file or directory")
19
+ ```
20
+
21
+ ## 🔧 Building
22
+
23
+ ```shell
24
+ uv build
25
+ ```
26
+
27
+ ## 🤝 Collaborate with My Project
28
+
29
+ Please refer to the [collaboration guidelines](./COLLABORATION.md) for details.
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["wheel", "setuptools", "attrs>=17.1", "versioningit"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "logrunner"
7
+ dynamic = ["version"]
8
+ description = "logrunner.run([cmd]) wraps subprocess.run([cmd]) to allow continuous output as well as capture of stdout"
9
+ readme = "README.md"
10
+ requires-python = ">= 3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+
14
+ authors = [
15
+ {name = "kagekirin", email = "kagekirin@gmail.com"},
16
+ ]
17
+ maintainers = [
18
+ {name = "kagekirin", email = "kagekirin@gmail.com"},
19
+ ]
20
+
21
+ classifiers = [
22
+ "Programming Language :: Python :: 3",
23
+ "Operating System :: OS Independent",
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "Topic :: Software Development :: Libraries",
27
+ ]
28
+
29
+
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/KageKirin/py-logrunner"
33
+ Documentation = "https://github.com/KageKirin/py-logrunner"
34
+ Repository = "https://github.com/KageKirin/py-logrunner.git"
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=9.0",
39
+ "ruff",
40
+ ]
41
+
42
+ [tool.pytest]
43
+ minversion = "9.0"
44
+ addopts = ["-ra", "-q"]
45
+ pythonpath = ["src"]
46
+ testpaths = [
47
+ "tests",
48
+ ]
49
+
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["src/"]
53
+ include = ["logrunner"]
54
+
55
+ [tool.versioningit.next-version]
56
+ method = "smallest"
57
+
58
+ [tool.versioningit.format]
59
+ distance = "{next_version}.dev{distance}+{vcs}{rev}"
60
+ dirty = "{base_version}+d{build_date:%Y%m%d}"
61
+ distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.d{build_date:%Y%m%d}"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,90 @@
1
+ ## logrunner
2
+
3
+ # namespace: logrunner
4
+
5
+ import io
6
+ import sys
7
+ import selectors
8
+ import subprocess
9
+
10
+
11
+ def run(
12
+ args,
13
+ *,
14
+ stdin=None,
15
+ cwd=None,
16
+ check=False,
17
+ encoding=None,
18
+ errors=None,
19
+ text=None,
20
+ env=None,
21
+ **other_popen_kwargs,
22
+ ):
23
+
24
+ stdoutbuf = io.StringIO()
25
+ stderrbuf = io.StringIO()
26
+
27
+ kwargs = {
28
+ "stdout": subprocess.PIPE,
29
+ "stderr": subprocess.PIPE, # STDOUT to pipe STDERR into STDOUT for simultaneous capture
30
+ "bufsize": 1, # bufsize = 1 means output is line buffered
31
+ "universal_newlines": True, # required for line buffering
32
+ ## forwarding the remaining params
33
+ "stdin": stdin,
34
+ "cwd": cwd,
35
+ # timeout=timeout,
36
+ "errors": errors,
37
+ "text": text,
38
+ "env": env,
39
+ **other_popen_kwargs,
40
+ }
41
+
42
+ # Start subprocess
43
+ process = subprocess.Popen(args, **kwargs)
44
+
45
+ # Callback function for process STDOUT
46
+ def handle_stdout(stream, mask):
47
+ # NOTE: Because the process' output is line buffered, there's only ever one line to read when this function is called
48
+ line = stream.readline()
49
+ stdoutbuf.write(line)
50
+ sys.stdout.write(line)
51
+
52
+ # Callback function for process STDERR
53
+ def handle_stderr(stream, mask):
54
+ # NOTE: Because the process' output is line buffered, there's only ever one line to read when this function is called
55
+ line = stream.readline()
56
+ stderrbuf.write(line)
57
+ sys.stderr.write(line)
58
+
59
+ # Register callback for an "available for read" event from subprocess' stdout stream
60
+ selector = selectors.DefaultSelector()
61
+ selector.register(process.stdout, selectors.EVENT_READ, handle_stdout)
62
+ selector.register(process.stderr, selectors.EVENT_READ, handle_stderr)
63
+
64
+ # Loop until subprocess is terminated
65
+ while process.poll() is None:
66
+ # Wait for events and handle them with their registered callbacks
67
+ events = selector.select()
68
+ for key, mask in events:
69
+ callback = key.data
70
+ callback(key.fileobj, mask)
71
+
72
+ # Get process return code
73
+ returncode = process.wait()
74
+ selector.close()
75
+
76
+ # Store buffered output
77
+ stdoutput = stdoutbuf.getvalue()
78
+ stdoutbuf.close()
79
+ stderrput = stderrbuf.getvalue()
80
+ stderrbuf.close()
81
+
82
+ if check and returncode != 0:
83
+ raise subprocess.CalledProcessError(
84
+ returncode=returncode,
85
+ cmd=" ".join(args),
86
+ output=stdoutput,
87
+ stderr=stderrput,
88
+ )
89
+
90
+ return (returncode, stdoutput, stderrput)
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: logrunner
3
+ Version: 0.0.4
4
+ Summary: logrunner.run([cmd]) wraps subprocess.run([cmd]) to allow continuous output as well as capture of stdout
5
+ Author-email: kagekirin <kagekirin@gmail.com>
6
+ Maintainer-email: kagekirin <kagekirin@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/KageKirin/py-logrunner
9
+ Project-URL: Documentation, https://github.com/KageKirin/py-logrunner
10
+ Project-URL: Repository, https://github.com/KageKirin/py-logrunner.git
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=9.0; extra == "dev"
21
+ Requires-Dist: ruff; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # Logrunner
25
+
26
+ `logrunner.run([cmd])` wraps `subprocess.run([cmd])` to allow continuous output as well as capture of stdout and stderr.
27
+
28
+ This function is intended for usage in CI and other process monitoring applications where
29
+ the process outputs need to be written for real-time error tracking
30
+ and captured for post-processing text analysis.
31
+
32
+ ## ⚡ Usage
33
+
34
+ ```python
35
+ import logrunner
36
+
37
+ (ret, out, err) = logrunner.run(["echo", "hello"])
38
+ # returns (0, "hello", "")
39
+
40
+ (ret, out, err) = logrunner.run(["ls", "/nonexistentpath"])
41
+ # return (1, "", "No such file or directory")
42
+ ```
43
+
44
+ ## 🔧 Building
45
+
46
+ ```shell
47
+ uv build
48
+ ```
49
+
50
+ ## 🤝 Collaborate with My Project
51
+
52
+ Please refer to the [collaboration guidelines](./COLLABORATION.md) for details.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/logrunner/__init__.py
5
+ src/logrunner.egg-info/PKG-INFO
6
+ src/logrunner.egg-info/SOURCES.txt
7
+ src/logrunner.egg-info/dependency_links.txt
8
+ src/logrunner.egg-info/requires.txt
9
+ src/logrunner.egg-info/top_level.txt
10
+ tests/test_logrunner.py
@@ -0,0 +1,4 @@
1
+
2
+ [dev]
3
+ pytest>=9.0
4
+ ruff
@@ -0,0 +1 @@
1
+ logrunner
@@ -0,0 +1,17 @@
1
+ import logrunner
2
+ import pytest
3
+
4
+ def test():
5
+ (ret, out, err) = logrunner.run(["echo", "hello"])
6
+ assert ret == 0
7
+ assert out.strip() == "hello"
8
+ assert err == ""
9
+
10
+ def test_error():
11
+ (ret, out, err) = logrunner.run(["ls", "/nonexistentpath"])
12
+ assert ret != 0
13
+ assert out == ""
14
+ assert "No such file or directory" in err
15
+
16
+ if __name__ == "__main__":
17
+ pytest.main()