abstract-pypit 0.0.1__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,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: abstract_pypit
3
+ Version: 0.0.1
4
+ Summary: One-command PyPI publisher + GitHub pusher. Version-bumps, builds, uploads to PyPI, commits and pushes to GitHub, and syncs the local install — all in one call.
5
+ Home-page: https://github.com/AbstractEndeavors/abstract_pypit
6
+ Author: putkoff
7
+ Author-email: putkoff <partners@abstractendeavors.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/AbstractEndeavors/abstract_pypit
10
+ Keywords: pypi,publish,release,github,automation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: requests
20
+ Dynamic: author
21
+ Dynamic: home-page
22
+ Dynamic: requires-python
23
+
24
+ # abstract_pypit
25
+
26
+ One-command PyPI publisher + GitHub pusher.
27
+
28
+ Finds the next free version above whatever is on PyPI, bumps `setup.py` and
29
+ `pyproject.toml`, builds sdist + wheel, uploads to PyPI via twine, commits and
30
+ pushes to GitHub, then syncs the local install — all in one call.
31
+
32
+ Zero dependencies outside the stdlib except `requests`.
33
+
34
+ ## Install
35
+
36
+ ```sh
37
+ pip install abstract_pypit
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```python
43
+ # from any package directory that has setup.py + pyproject.toml:
44
+ from pypit import runit
45
+ runit()
46
+ ```
47
+
48
+ ```sh
49
+ # or from the command line:
50
+ abstract-pypit
51
+ ```
52
+
53
+ ## Credentials
54
+
55
+ **PyPI:** twine reads `~/.pypirc` or `TWINE_USERNAME` / `TWINE_PASSWORD` env vars.
56
+
57
+ **GitHub:** create `pypit/src/envs/.env` on the machine running pypit:
58
+
59
+ ```
60
+ GITHUB_OWNER_1=your-username
61
+ GITPASS_1=<your-github-pat>
62
+ GITHUB_OWNER_2=your-org
63
+ GITPASS_2=<org-github-pat>
64
+ ```
65
+
66
+ SSH key at `~/.ssh/github/githubssh_nopass` must be registered with GitHub.
67
+
68
+ ## Per-package config
69
+
70
+ Add to the package's own `pyproject.toml`:
71
+
72
+ ```toml
73
+ [tool.pypit]
74
+ github_owner = "your-org-or-username" # which org/user owns the repo
75
+ github_push = true # set false to skip GitHub entirely
76
+ ```
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,57 @@
1
+ # abstract_pypit
2
+
3
+ One-command PyPI publisher + GitHub pusher.
4
+
5
+ Finds the next free version above whatever is on PyPI, bumps `setup.py` and
6
+ `pyproject.toml`, builds sdist + wheel, uploads to PyPI via twine, commits and
7
+ pushes to GitHub, then syncs the local install — all in one call.
8
+
9
+ Zero dependencies outside the stdlib except `requests`.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ pip install abstract_pypit
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```python
20
+ # from any package directory that has setup.py + pyproject.toml:
21
+ from pypit import runit
22
+ runit()
23
+ ```
24
+
25
+ ```sh
26
+ # or from the command line:
27
+ abstract-pypit
28
+ ```
29
+
30
+ ## Credentials
31
+
32
+ **PyPI:** twine reads `~/.pypirc` or `TWINE_USERNAME` / `TWINE_PASSWORD` env vars.
33
+
34
+ **GitHub:** create `pypit/src/envs/.env` on the machine running pypit:
35
+
36
+ ```
37
+ GITHUB_OWNER_1=your-username
38
+ GITPASS_1=<your-github-pat>
39
+ GITHUB_OWNER_2=your-org
40
+ GITPASS_2=<org-github-pat>
41
+ ```
42
+
43
+ SSH key at `~/.ssh/github/githubssh_nopass` must be registered with GitHub.
44
+
45
+ ## Per-package config
46
+
47
+ Add to the package's own `pyproject.toml`:
48
+
49
+ ```toml
50
+ [tool.pypit]
51
+ github_owner = "your-org-or-username" # which org/user owns the repo
52
+ github_push = true # set false to skip GitHub entirely
53
+ ```
54
+
55
+ ## License
56
+
57
+ MIT
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "abstract_pypit"
7
+ version = "0.0.1"
8
+ description = "One-command PyPI publisher + GitHub pusher. Version-bumps, builds, uploads to PyPI, commits and pushes to GitHub, and syncs the local install — all in one call."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "putkoff", email = "partners@abstractendeavors.com" }]
12
+ requires-python = ">=3.8"
13
+ keywords = ["pypi", "publish", "release", "github", "automation"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Operating System :: OS Independent",
20
+ "Topic :: Software Development :: Build Tools",
21
+ ]
22
+ dependencies = [
23
+ "requests",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/AbstractEndeavors/abstract_pypit"
28
+
29
+ [project.scripts]
30
+ abstract-pypit = "pypit.main:runPypit"
31
+ pypit-github = "pypit.github_only:runGithubOnly"
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [tool.setuptools.package-dir]
37
+ "" = "src"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="abstract_pypit",
5
+ version='0.0.1',
6
+ description="One-command PyPI publisher + GitHub pusher.",
7
+ author="putkoff",
8
+ author_email="partners@abstractendeavors.com",
9
+ license="MIT",
10
+ python_requires=">=3.8",
11
+ package_dir={"": "src"},
12
+ packages=find_packages(where="src"),
13
+ install_requires=["requests"],
14
+ entry_points={
15
+ "console_scripts": [
16
+ "abstract-pypit = pypit.main:runPypit",
17
+ "pypit-github = pypit.github_only:runGithubOnly",
18
+ ],
19
+ },
20
+ url="https://github.com/AbstractEndeavors/abstract_pypit",
21
+ )
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: abstract_pypit
3
+ Version: 0.0.1
4
+ Summary: One-command PyPI publisher + GitHub pusher. Version-bumps, builds, uploads to PyPI, commits and pushes to GitHub, and syncs the local install — all in one call.
5
+ Home-page: https://github.com/AbstractEndeavors/abstract_pypit
6
+ Author: putkoff
7
+ Author-email: putkoff <partners@abstractendeavors.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/AbstractEndeavors/abstract_pypit
10
+ Keywords: pypi,publish,release,github,automation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: requests
20
+ Dynamic: author
21
+ Dynamic: home-page
22
+ Dynamic: requires-python
23
+
24
+ # abstract_pypit
25
+
26
+ One-command PyPI publisher + GitHub pusher.
27
+
28
+ Finds the next free version above whatever is on PyPI, bumps `setup.py` and
29
+ `pyproject.toml`, builds sdist + wheel, uploads to PyPI via twine, commits and
30
+ pushes to GitHub, then syncs the local install — all in one call.
31
+
32
+ Zero dependencies outside the stdlib except `requests`.
33
+
34
+ ## Install
35
+
36
+ ```sh
37
+ pip install abstract_pypit
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```python
43
+ # from any package directory that has setup.py + pyproject.toml:
44
+ from pypit import runit
45
+ runit()
46
+ ```
47
+
48
+ ```sh
49
+ # or from the command line:
50
+ abstract-pypit
51
+ ```
52
+
53
+ ## Credentials
54
+
55
+ **PyPI:** twine reads `~/.pypirc` or `TWINE_USERNAME` / `TWINE_PASSWORD` env vars.
56
+
57
+ **GitHub:** create `pypit/src/envs/.env` on the machine running pypit:
58
+
59
+ ```
60
+ GITHUB_OWNER_1=your-username
61
+ GITPASS_1=<your-github-pat>
62
+ GITHUB_OWNER_2=your-org
63
+ GITPASS_2=<org-github-pat>
64
+ ```
65
+
66
+ SSH key at `~/.ssh/github/githubssh_nopass` must be registered with GitHub.
67
+
68
+ ## Per-package config
69
+
70
+ Add to the package's own `pyproject.toml`:
71
+
72
+ ```toml
73
+ [tool.pypit]
74
+ github_owner = "your-org-or-username" # which org/user owns the repo
75
+ github_push = true # set false to skip GitHub entirely
76
+ ```
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,21 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ src/abstract_pypit.egg-info/PKG-INFO
5
+ src/abstract_pypit.egg-info/SOURCES.txt
6
+ src/abstract_pypit.egg-info/dependency_links.txt
7
+ src/abstract_pypit.egg-info/entry_points.txt
8
+ src/abstract_pypit.egg-info/requires.txt
9
+ src/abstract_pypit.egg-info/top_level.txt
10
+ src/pypit/__init__.py
11
+ src/pypit/__main__.py
12
+ src/pypit/clean_the_repos.py
13
+ src/pypit/env_utils.py
14
+ src/pypit/github_auth.py
15
+ src/pypit/github_only.py
16
+ src/pypit/main.py
17
+ src/pypit/pypit_utils.py
18
+ src/pypit/imports/__init__.py
19
+ src/pypit/imports/init_imports.py
20
+ src/pypit/imports/paths.py
21
+ src/pypit/imports/utils.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ abstract-pypit = pypit.main:runPypit
3
+ pypit-github = pypit.github_only:runGithubOnly
@@ -0,0 +1,19 @@
1
+ """
2
+ pypit (published as abstract_pypit)
3
+ =====================================
4
+ One-command PyPI publisher + GitHub pusher.
5
+
6
+ Usage as a library:
7
+ from pypit import runit, runGithubOnly
8
+ runit()
9
+
10
+ Usage from the command line (after pip install abstract_pypit):
11
+ python -m pypit
12
+ """
13
+
14
+ __version__ = "0.0.1"
15
+
16
+ from .main import runit, runPypit # noqa: F401
17
+ from .github_only import runGithubOnly # noqa: F401
18
+
19
+ __all__ = ["runit", "runPypit", "runGithubOnly", "__version__"]
@@ -0,0 +1,5 @@
1
+ """python -m pypit → runPypit()"""
2
+ from .main import runPypit
3
+
4
+ if __name__ == "__main__":
5
+ runPypit()
@@ -0,0 +1,125 @@
1
+ """
2
+ pypit.clean_the_repos
3
+ ======================
4
+ Conflict-marker guard using git-tracked file list (not filesystem walk).
5
+ """
6
+ import os
7
+ import re
8
+ import subprocess
9
+
10
+ from .imports import * # noqa: F401,F403
11
+
12
+ # ------------------------------------------------------------------------------
13
+ # A real git conflict block is anchored lines in order:
14
+ # <<<<<<< <label>
15
+ # [||||||| <label>] (diff3 style, optional)
16
+ # =======
17
+ # >>>>>>> <label>
18
+ # Anchoring at line start + exact 7 chars kills Cython comments and string
19
+ # literals that are not conflict markers.
20
+ # ------------------------------------------------------------------------------
21
+ _RE_OURS = re.compile(r"^<{7}(?: |$)")
22
+ _RE_BASE = re.compile(r"^\|{7}(?: |$)")
23
+ _RE_SPLIT = re.compile(r"^={7}$")
24
+ _RE_THEIRS = re.compile(r"^>{7}(?: |$)")
25
+
26
+ _SKIP_SUFFIXES = {
27
+ ".whl", ".gz", ".zip", ".tar", ".png", ".jpg", ".jpeg", ".pdf", ".so",
28
+ ".pyc", ".pyo", ".c", ".cpp", ".cc", ".h", ".hpp", ".pyx", ".pxd",
29
+ }
30
+ _MAX_BYTES = 5_000_000
31
+
32
+
33
+ def _git_root(start="."):
34
+ out = subprocess.run(
35
+ ["git", "rev-parse", "--show-toplevel"],
36
+ cwd=os.path.abspath(start), capture_output=True, text=True,
37
+ )
38
+ if out.returncode != 0:
39
+ return None
40
+ return out.stdout.strip() or None
41
+
42
+
43
+ def _repo_files(root):
44
+ found = []
45
+ for args in (
46
+ ["git", "ls-files", "-z"],
47
+ ["git", "ls-files", "--others", "--exclude-standard", "-z"],
48
+ ):
49
+ out = subprocess.run(args, cwd=root, capture_output=True, text=True)
50
+ found += [f for f in out.stdout.split("\0") if f]
51
+ return list(dict.fromkeys(found))
52
+
53
+
54
+ def _looks_text(path):
55
+ try:
56
+ if os.path.isdir(path):
57
+ return False
58
+ if os.path.splitext(path)[1].lower() in _SKIP_SUFFIXES:
59
+ return False
60
+ if os.path.getsize(path) > _MAX_BYTES:
61
+ return False
62
+ with open(path, "rb") as f:
63
+ return b"\x00" not in f.read(2048)
64
+ except OSError:
65
+ return False
66
+
67
+
68
+ def _scan_for_conflicts(path):
69
+ try:
70
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
71
+ text = f.read()
72
+ except OSError:
73
+ return []
74
+ if "<<<<<<<" not in text or ">>>>>>>" not in text:
75
+ return []
76
+ hits, block, state = [], [], "clean"
77
+ for ln, line in enumerate(text.splitlines(), 1):
78
+ if _RE_OURS.match(line):
79
+ block = [(ln, line.rstrip())]
80
+ state = "ours"
81
+ elif state == "ours" and _RE_BASE.match(line):
82
+ block.append((ln, line.rstrip()))
83
+ state = "base"
84
+ elif state in ("ours", "base") and _RE_SPLIT.match(line):
85
+ block.append((ln, line.rstrip()))
86
+ state = "split"
87
+ elif state == "split" and _RE_THEIRS.match(line):
88
+ block.append((ln, line.rstrip()))
89
+ hits.extend(block)
90
+ block, state = [], "clean"
91
+ return hits[:10]
92
+
93
+
94
+ def ensure_clean_repo(where="(unspecified)", *, require_clean_git=False, root="."):
95
+ git_root = _git_root(root)
96
+ if git_root is None:
97
+ print(f"ℹ️ ensure_clean_repo[{where}]: {os.path.abspath(root)} is not a git "
98
+ f"repo — skipping conflict scan")
99
+ return
100
+ offenders = {}
101
+ for rel in _repo_files(git_root):
102
+ p = os.path.join(git_root, rel)
103
+ if not _looks_text(p):
104
+ continue
105
+ hits = _scan_for_conflicts(p)
106
+ if hits:
107
+ offenders[p] = hits
108
+ if offenders:
109
+ lines = [f"\n🚫 Merge conflict markers detected {where}. Resolve before continuing:"]
110
+ for path, hits in offenders.items():
111
+ lines.append(f" - {path}")
112
+ for ln, t in hits:
113
+ lines.append(f" L{ln:>4}: {t}")
114
+ if len(hits) == 10:
115
+ lines.append(" ... (more lines truncated)")
116
+ raise RuntimeError("\n".join(lines))
117
+ if require_clean_git:
118
+ for args, err_msg in (
119
+ (["git", "diff", "--quiet"], "Unstaged changes in working tree."),
120
+ (["git", "diff", "--cached", "--quiet"], "Staged but uncommitted changes in index."),
121
+ ):
122
+ rc = subprocess.call(args, cwd=git_root,
123
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
124
+ if rc != 0:
125
+ raise RuntimeError(err_msg)
@@ -0,0 +1,120 @@
1
+ """
2
+ pypit.env_utils
3
+ ================
4
+ Standalone .env cascade reader — no third-party dependencies.
5
+
6
+ Inlined from abstract_security.envs; the dotenv/bcrypt/jwt surface of that
7
+ package is irrelevant here. Search order:
8
+ supplied path → cwd → home → ~/.envy_all → ~/envy_all
9
+ """
10
+ import os
11
+ import sys
12
+
13
+ _DEFAULT_FILE = ".env"
14
+ _DEFAULT_KEY = "MY_PASSWORD"
15
+
16
+
17
+ def _split_eq(line):
18
+ """Split ``KEY=VALUE`` at the first '=' and strip whitespace."""
19
+ if "=" in line:
20
+ key, _, value = line.partition("=")
21
+ return key.strip(), value.strip()
22
+ return line.strip(), None
23
+
24
+
25
+ def _search_file(key, path, deep_scan=False):
26
+ """Return the value of *key* in the env file at *path*, or None."""
27
+ if not (path and os.path.isfile(path)):
28
+ return None
29
+ best_value, best_score = None, 0
30
+ with open(path, "r", encoding="utf-8", errors="replace") as fh:
31
+ for line in fh:
32
+ line_key, line_value = _split_eq(line)
33
+ if line_key == key:
34
+ return line_value
35
+ if deep_scan and line_key and key:
36
+ matched = sum(len(p) for p in key.split("_") if p and p in line_key)
37
+ if matched / len(key) >= 0.5 and matched > best_score:
38
+ best_value, best_score = line_value, matched
39
+ return best_value if deep_scan else None
40
+
41
+
42
+ def _candidate_dirs(start_path):
43
+ """Return de-duplicated, existing directories to search."""
44
+ home = os.path.expanduser("~")
45
+ candidates = [
46
+ start_path,
47
+ os.getcwd(),
48
+ home,
49
+ os.path.join(home, ".envy_all"),
50
+ os.path.join(home, "envy_all"),
51
+ ]
52
+ seen, result = set(), []
53
+ for d in candidates:
54
+ if d and d not in seen and os.path.isdir(d):
55
+ seen.add(d)
56
+ result.append(d)
57
+ return result
58
+
59
+
60
+ def get_env_value(key=_DEFAULT_KEY, path=None, file_name=_DEFAULT_FILE,
61
+ deep_scan=False):
62
+ """
63
+ Read *key* from a .env-style file.
64
+
65
+ If *path* points directly to a file, that file is tried first.
66
+ Otherwise the cascade: supplied dir → cwd → home → ~/.envy_all.
67
+ """
68
+ key = key or _DEFAULT_KEY
69
+ file_name = file_name or _DEFAULT_FILE
70
+
71
+ if path and os.path.isfile(path):
72
+ # direct file path given — search it first, then cascade from its dir
73
+ value = _search_file(key, path, deep_scan)
74
+ if value is not None:
75
+ return value
76
+ path = os.path.dirname(path)
77
+
78
+ for directory in _candidate_dirs(path or os.getcwd()):
79
+ value = _search_file(key, os.path.join(directory, file_name), deep_scan)
80
+ if value is not None:
81
+ return value
82
+ return None
83
+
84
+
85
+ def get_env_path(key=_DEFAULT_KEY, path=None, file_name=_DEFAULT_FILE,
86
+ deep_scan=False):
87
+ """Return the path of the first .env file that contains *key*, or None."""
88
+ key = key or _DEFAULT_KEY
89
+ file_name = file_name or _DEFAULT_FILE
90
+
91
+ if path and os.path.isfile(path):
92
+ if _search_file(key, path, deep_scan) is not None:
93
+ return path
94
+ path = os.path.dirname(path)
95
+
96
+ for directory in _candidate_dirs(path or os.getcwd()):
97
+ env_path = os.path.join(directory, file_name)
98
+ if _search_file(key, env_path, deep_scan) is not None:
99
+ return env_path
100
+ return None
101
+
102
+
103
+ def get_initial_caller():
104
+ """Return the path of the original entry-point script (sys.argv[0])."""
105
+ entry = sys.argv[0] if sys.argv else None
106
+ if entry:
107
+ return os.path.realpath(entry)
108
+ return None
109
+
110
+
111
+ def get_initial_caller_dir():
112
+ """Return the directory of the original entry-point script."""
113
+ caller = get_initial_caller()
114
+ return os.path.dirname(caller) if caller else None
115
+
116
+
117
+ __all__ = [
118
+ "get_env_value", "get_env_path",
119
+ "get_initial_caller", "get_initial_caller_dir",
120
+ ]