pygitpub 0.0.59__tar.gz → 0.0.61__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.
@@ -1,8 +1,6 @@
1
1
  # generated
2
- /build/
3
2
  /toolchain/
4
3
  /dist/
5
- /target/
6
4
  /out/
7
5
  /debug/
8
6
  /download/
@@ -64,7 +62,7 @@ modules.order
64
62
 
65
63
  # sphinx
66
64
  /docs/_build/
67
- /docs/.doctrees/environment.pickle
65
+ /docs/.doctrees/
68
66
 
69
67
  # mdBook build output
70
68
  /docs/book/
@@ -77,8 +75,6 @@ modules.order
77
75
  # python
78
76
  __pycache__/
79
77
  *.py[oc]
80
- *.pyc
81
- *.pyo
82
78
  /wheel/
83
79
  /.venv/
84
80
  .coverage
@@ -87,7 +83,10 @@ coverage.xml
87
83
  *.egg-info/
88
84
 
89
85
  # mine
90
- /.pydmt.build.errors
86
+ # error files left by the bashy prompt plugins (.uv.sync.errors,
87
+ # .pydmt.build.errors, ...); unanchored because a sub-project's
88
+ # pyproject root can sit below the git root
89
+ .*.errors
91
90
  /.pydmt/
92
91
  *.stamp
93
92
  *.stamp.*
@@ -147,10 +146,13 @@ build/
147
146
  .ipynb_checkpoints/
148
147
 
149
148
  # c#
149
+ # **/obj/ and **/bin/ match at any depth, which is what .NET output needs
150
+ # (src/<proj>/bin, src/<proj>/obj). The un-globbed [Oo]bj/ and [Bb]in/ that
151
+ # used to follow were redundant with these two and are gone.
150
152
  **/obj/
151
153
  **/bin/
152
- [Oo]bj/
153
- [Bb]in/
154
+ # perl-meta keeps real Perl sources under perl/bin/Meta/, not build output.
155
+ !/perl/bin/
154
156
 
155
157
  # flash/air
156
158
  *.swf
@@ -175,7 +177,6 @@ bin-release/
175
177
  .project
176
178
  .project.xml
177
179
  .idea/
178
- /.idea/
179
180
  build.properties
180
181
 
181
182
  # spark
@@ -186,24 +187,26 @@ spark-warehouse/
186
187
  /.terraform/
187
188
  terraform.tfstate
188
189
  terraform.tfstate.backup
189
- /terraform.tfstate
190
- /terraform.tfstate.backup
191
190
  /terraform.lock.hcl
192
191
 
193
192
  # scala
194
- target/
195
- project/
193
+ # Anchored: sbt's build dir is at the repo root. Unanchored, this also
194
+ # swallowed source directories named project/ nested under src/.
195
+ /project/
196
196
 
197
197
  # rust
198
198
  **/*.rs.bk
199
199
  **/mutants.out*/
200
200
  *.pdb
201
- debug
201
+ # Anchored: this is the root debug build dir. Unanchored (and without a
202
+ # trailing slash) it matched any file or directory named debug at any depth,
203
+ # including authored example content.
204
+ /debug/
202
205
  /.rsconstruct_cache.json
203
206
 
204
207
  # claude
205
208
  /.claude/
206
- claude/
209
+ /claude/
207
210
  .claude/projects/
208
211
  .claude/settings.local.json
209
212
 
@@ -242,3 +245,36 @@ creds.txt
242
245
 
243
246
  # rsconstruct
244
247
  /.rsconstruct/
248
+
249
+ # generated website pieces (built into the Pages artifact by CI)
250
+ /docs/output/
251
+ /docs/index.html
252
+ # sphinx html output: the 14 py* repos render docs/ from sphinx/ and publish
253
+ # it as the Pages artifact, so none of it is committed. Listed path by path
254
+ # rather than as /docs/ because the rs* repos keep hand-written mdBook
255
+ # sources there, and several others keep authored html.
256
+ /docs/_modules/
257
+ /docs/_sources/
258
+ /docs/_static/
259
+ /docs/.buildinfo
260
+ /docs/objects.inv
261
+ /docs/searchindex.js
262
+ /docs/.nojekyll
263
+ # sphinx's top-level html. Not a blanket /docs/*.html: js-jschess and the
264
+ # site-personal-* repos keep hand-authored html under docs/. The fixed pages
265
+ # (genindex, modules, search) and the authored-rst pages some repos render
266
+ # (architecture, notes, ...) are listed by name; the one-page-per-module
267
+ # output is /docs/py*.html, since every package here is named py*.
268
+ /docs/architecture.html
269
+ /docs/cli-reference.html
270
+ /docs/development.html
271
+ /docs/genindex.html
272
+ /docs/modules.html
273
+ /docs/notes.html
274
+ /docs/overview.html
275
+ /docs/py*.html
276
+ /docs/quota-guide.html
277
+ /docs/search.html
278
+
279
+ # per-machine tool resolution cache (absolute paths, not portable)
280
+ /.tools.versions
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pygitpub
3
- Version: 0.0.59
3
+ Version: 0.0.61
4
4
  Summary: Pygitpub will help you work with github
5
5
  Project-URL: Homepage, https://github.com/veltzer/pygitpub
6
6
  Project-URL: Bug Tracker, https://github.com/veltzer/pygitpub/issues
@@ -19,9 +19,10 @@ Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python
20
20
  Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
- Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Utilities
24
- Requires-Python: >=3.12
24
+ Requires-Python: >=3.14
25
+ Requires-Dist: lupa
25
26
  Requires-Dist: pyapikey
26
27
  Requires-Dist: pygithub
27
28
  Requires-Dist: pylogconf
@@ -29,6 +30,9 @@ Requires-Dist: pytconf
29
30
  Requires-Dist: pyyaml
30
31
  Description-Content-Type: text/markdown
31
32
 
33
+ <!-- This file is generated. Do not edit it by hand: your changes will be
34
+ lost on the next build. Edit the template it is rendered from, or the
35
+ values that template reads, and build again. -->
32
36
  # *pygitpub* project by Mark Veltzer
33
37
 
34
38
  description: Pygitpub will help you work with github
@@ -37,7 +41,7 @@ project website: https://veltzer.github.io/pygitpub
37
41
 
38
42
  author: Mark Veltzer
39
43
 
40
- version: 0.0.59
44
+ version: 0.0.61
41
45
 
42
46
  ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)
43
47
 
@@ -66,7 +70,6 @@ version: 0.0.59
66
70
  ## contact me
67
71
 
68
72
  [mailto](mailto:mark.veltzer@gmail.com)
69
- ![gitter](https://img.shields.io/gitter/room/veltzer/mark.veltzer)
70
73
  ![discord](https://img.shields.io/discord/719336281624281119)
71
74
  ![discord](https://img.shields.io/discord/719336282194444302)
72
75
 
@@ -1,3 +1,6 @@
1
+ <!-- This file is generated. Do not edit it by hand: your changes will be
2
+ lost on the next build. Edit the template it is rendered from, or the
3
+ values that template reads, and build again. -->
1
4
  # *pygitpub* project by Mark Veltzer
2
5
 
3
6
  description: Pygitpub will help you work with github
@@ -6,7 +9,7 @@ project website: https://veltzer.github.io/pygitpub
6
9
 
7
10
  author: Mark Veltzer
8
11
 
9
- version: 0.0.59
12
+ version: 0.0.61
10
13
 
11
14
  ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)
12
15
 
@@ -35,7 +38,6 @@ version: 0.0.59
35
38
  ## contact me
36
39
 
37
40
  [mailto](mailto:mark.veltzer@gmail.com)
38
- ![gitter](https://img.shields.io/gitter/room/veltzer/mark.veltzer)
39
41
  ![discord](https://img.shields.io/discord/719336281624281119)
40
42
  ![discord](https://img.shields.io/discord/719336282194444302)
41
43
 
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pygitpub"
7
- version = "0.0.59"
8
- requires-python = ">=3.12"
7
+ version = "0.0.61"
8
+ requires-python = ">=3.14"
9
9
  authors = [
10
10
  { name = "Mark Veltzer", email = "mark.veltzer@gmail.com" }
11
11
  ]
@@ -26,7 +26,7 @@ classifiers = [
26
26
  "Programming Language :: Python",
27
27
  "Programming Language :: Python :: 3",
28
28
  "Programming Language :: Python :: 3 :: Only",
29
- "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.14",
30
30
  "Topic :: Utilities",
31
31
  "License :: OSI Approved :: MIT License",
32
32
  ]
@@ -36,6 +36,16 @@ dependencies = [
36
36
  "PyGithub",
37
37
  "pyapikey",
38
38
  "pyyaml",
39
+ "lupa",
40
+ ]
41
+
42
+ [dependency-groups]
43
+ dev = [
44
+ "mypy",
45
+ "pylint",
46
+ "pytest",
47
+ "ruff",
48
+ "sphinx",
39
49
  ]
40
50
 
41
51
  [project.urls]
@@ -74,10 +84,23 @@ exclude = [
74
84
  ]
75
85
 
76
86
  [tool.mypy]
77
- python_version = "3.12"
87
+ python_version = "3.14"
78
88
  warn_return_any = true
79
89
  warn_unused_configs = true
90
+ mypy_path = "src:python:scripts"
91
+ explicit_package_bases = true
92
+ namespace_packages = true
80
93
 
94
+ # Third-party libraries without type stubs; `foo.*` also matches `foo` itself.
95
+ [[tool.mypy.overrides]]
96
+ module = [
97
+ "github.*",
98
+ "lupa.*",
99
+ "pyapikey.*",
100
+ "pylogconf.*",
101
+ "pytconf.*",
102
+ ]
103
+ ignore_missing_imports = true
81
104
  [tool.hatch.envs.default]
82
105
  installer = "uv"
83
106
 
@@ -16,12 +16,13 @@ from pytconf.registry import the_registry
16
16
 
17
17
  import pygitpub.static
18
18
  from pygitpub.configs import AFFILIATIONS, ConfigAlgo, ConfigGithub, ConfigOutput
19
- from pygitpub.utils.importlib import import_file
19
+ from pygitpub.utils.lua import config_path, load_lua_file
20
20
  from pygitpub.utils.misc import delete, get_all_git_repos
21
21
 
22
22
 
23
23
  def get_base_dir() -> str:
24
- return os.path.expanduser(ConfigAlgo.base_dir)
24
+ base_dir: str = ConfigAlgo.base_dir
25
+ return os.path.expanduser(base_dir)
25
26
 
26
27
 
27
28
  def get_repo_folder(repo) -> str:
@@ -84,25 +85,25 @@ def fix_metadata() -> None:
84
85
  continue
85
86
  os.chdir(folder)
86
87
  # print(f"doing [{folder}]...")
87
- file_path = "config/project.py"
88
+ file_path = config_path("project")
88
89
  if not os.path.isfile(file_path):
89
90
  os.chdir(orig_folder)
90
91
  continue
91
- mod = import_file(file_path)
92
- if not hasattr(mod, "description_short"):
92
+ project = load_lua_file(file_path)
93
+ if "DESCRIPTION_SHORT" not in project:
93
94
  os.chdir(orig_folder)
94
95
  continue
95
- description_short = mod.description_short
96
+ description_short = project["DESCRIPTION_SHORT"]
96
97
  if description_short != repo.description:
97
98
  print(f"in {folder}...")
98
99
  print(f"description_short is [{description_short}]")
99
100
  print(f"repo.description is [{repo.description}]")
100
101
  repo.edit(description=description_short)
101
- if not hasattr(mod, "keywords"):
102
+ if "KEYWORDS" not in project:
102
103
  print(f"{folder} no keywords")
103
104
  os.chdir(orig_folder)
104
105
  continue
105
- keywords = mod.keywords
106
+ keywords = project["KEYWORDS"]
106
107
  if set(keywords) != set(repo.get_topics()):
107
108
  print(f"in {folder}...")
108
109
  print(f"keywords is [{keywords}]")
@@ -193,15 +194,17 @@ def repos_list() -> None:
193
194
 
194
195
 
195
196
  @register_endpoint(
196
- description="Cleanup old failing or un-needed runs in all workflows in all repositories",
197
+ description="Cleanup old failing or un-needed runs, deployments, and releases in all repositories",
197
198
  configs=[
198
199
  ConfigGithub,
199
200
  ConfigOutput,
200
201
  ConfigAlgo,
201
202
  ],
202
203
  )
203
- def runs_cleanup() -> None:
204
+ def cleanup() -> None:
205
+ # pylint: disable=too-many-branches
204
206
  for repo in yield_repos():
207
+ # 1. Cleanup workflow runs
205
208
  for workflow in repo.get_workflows():
206
209
  existing = 0
207
210
  for run in workflow.get_runs():
@@ -215,7 +218,9 @@ def runs_cleanup() -> None:
215
218
  if run.head_branch != "master":
216
219
  delete_it = True
217
220
  # if its not a paged build and it failed then delete it
218
- if workflow.name != "pages-build-deployment" and run.conclusion == "failure":
221
+ if workflow.name != "pages-build-deployment" and run.conclusion in (
222
+ "failure", "cancelled", "timed_out", "startup_failure", "action_required"
223
+ ):
219
224
  delete_it = True
220
225
  if existing >= 4:
221
226
  delete_it = True
@@ -225,6 +230,46 @@ def runs_cleanup() -> None:
225
230
  else:
226
231
  existing += 1
227
232
 
233
+ # 2. Cleanup deployments
234
+ existing_deployments = 0
235
+ for deployment in repo.get_deployments():
236
+ state = None
237
+ for status in deployment.get_statuses():
238
+ state = status.state
239
+ break
240
+
241
+ delete_it = False
242
+ if state in ("failure", "error") or existing_deployments >= 4:
243
+ delete_it = True
244
+ else:
245
+ existing_deployments += 1
246
+
247
+ if delete_it:
248
+ print(f"deleting deployment {repo.name} {deployment.id}")
249
+ try:
250
+ deployment.create_status("inactive")
251
+ except github.GithubException as e:
252
+ print(f"Failed to deactivate deployment: {e}")
253
+ try:
254
+ # pylint: disable=protected-access
255
+ status, _, _ = deployment._requester.requestJson("DELETE", deployment.url)
256
+ if status != 204:
257
+ print(f"Failed to delete deployment: HTTP {status}")
258
+ except github.GithubException as e:
259
+ print(f"Failed to delete deployment: {e}")
260
+
261
+ # 3. Cleanup releases
262
+ existing_releases = 0
263
+ for release in repo.get_releases():
264
+ if existing_releases >= 4:
265
+ print(f"deleting release {repo.name} {release.title}")
266
+ try:
267
+ release.delete_release()
268
+ except github.GithubException as e:
269
+ print(f"Failed to delete release: {e}")
270
+ else:
271
+ existing_releases += 1
272
+
228
273
 
229
274
  @register_endpoint(
230
275
  description="Show all runs in all workflows in all repos",
@@ -1,5 +1,5 @@
1
1
  """ version which can be consumed from within the module """
2
- VERSION_STR = "0.0.59"
2
+ VERSION_STR = "0.0.61"
3
3
  DESCRIPTION = "Pygitpub will help you work with github"
4
4
  APP_NAME = "pygitpub"
5
5
  LOGGER_NAME = "pygitpub"
@@ -0,0 +1,68 @@
1
+ """
2
+ Load "config/*.lua" data files.
3
+
4
+ The config files used to be python modules loaded via importlib; they are now
5
+ lua data files (config/project.lua and friends). A file is run in a fresh lua
6
+ runtime and the globals it declares are returned as a plain dict.
7
+ """
8
+
9
+ import os.path
10
+ from typing import Any
11
+
12
+ # lupa exposes LuaRuntime through a module level __getattr__ that points at the
13
+ # bundled lua version (lupa.lua54, lupa.lua55, ...), so it cannot be imported
14
+ # by name. Going through the package attribute works for any bundled version.
15
+ import lupa
16
+
17
+ CONFIG_FOLDER = "config"
18
+
19
+
20
+ def lua_to_python(value: Any) -> Any:
21
+ """
22
+ Recursively convert a lua value into its python equivalent.
23
+
24
+ Lua has a single table type, so a table is a list if its keys are exactly
25
+ 1..n and a dict otherwise. An empty table becomes an empty list, which
26
+ matches how the config files use them.
27
+ """
28
+ if not hasattr(value, "values"):
29
+ return value
30
+ keys = list(value.keys())
31
+ if keys == list(range(1, len(keys) + 1)):
32
+ return [lua_to_python(x) for x in value.values()]
33
+ return {k: lua_to_python(value[k]) for k in keys}
34
+
35
+
36
+ def config_path(name: str) -> str:
37
+ """ return the path of a config file by its short name ("project") """
38
+ return os.path.join(CONFIG_FOLDER, f"{name}.lua")
39
+
40
+
41
+ def load_lua_file(path: str) -> dict[str, Any]:
42
+ """
43
+ Run a lua file and return the globals it declared as a dict.
44
+
45
+ Raises FileNotFoundError if the file does not exist.
46
+ """
47
+ if not os.path.isfile(path):
48
+ raise FileNotFoundError(path)
49
+ runtime = lupa.LuaRuntime()
50
+ with open(path, encoding="utf-8") as stream:
51
+ runtime.execute(stream.read())
52
+ globals_table = runtime.globals()
53
+ values = {}
54
+ for key in globals_table:
55
+ # skip the lua standard library, we only want what the file declared
56
+ if key in _LUA_BUILTINS:
57
+ continue
58
+ values[key] = lua_to_python(globals_table[key])
59
+ return values
60
+
61
+
62
+ _LUA_BUILTINS = frozenset({
63
+ "_G", "_VERSION", "assert", "collectgarbage", "coroutine", "debug", "dofile",
64
+ "error", "getmetatable", "io", "ipairs", "load", "loadfile", "math", "next",
65
+ "os", "package", "pairs", "pcall", "print", "python", "rawequal", "rawget",
66
+ "rawlen", "rawset", "require", "select", "setmetatable", "string", "table",
67
+ "tonumber", "tostring", "type", "utf8", "warn", "xpcall",
68
+ })
@@ -1,16 +0,0 @@
1
- """ importlib.py """
2
-
3
- import importlib.util
4
-
5
-
6
- def module_exists(module_name):
7
- """ return if a module exists or not """
8
- spec = importlib.util.find_spec(module_name)
9
- return spec is not None
10
-
11
-
12
- def import_file(file_path):
13
- spec = importlib.util.spec_from_file_location("foo", file_path)
14
- mod = importlib.util.module_from_spec(spec)
15
- spec.loader.exec_module(mod)
16
- return mod
File without changes