osslag 1.0.0__py3-none-any.whl → 1.0.1__py3-none-any.whl

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.
osslag/metrics/pvac.py CHANGED
@@ -115,12 +115,8 @@ def version_delta(packages, major_weight, minor_weight, patch_weight):
115
115
  if semanticA == "Unknown" or semanticB == "Unknown":
116
116
  continue
117
117
 
118
- weighted_version_A = (
119
- (majorA * major_weight) + (minorA * minor_weight) + (patchA * patch_weight)
120
- )
121
- weighted_version_B = (
122
- (majorB * major_weight) + (minorB * minor_weight) + (patchB * patch_weight)
123
- )
118
+ weighted_version_A = (majorA * major_weight) + (minorA * minor_weight) + (patchA * patch_weight)
119
+ weighted_version_B = (majorB * major_weight) + (minorB * minor_weight) + (patchB * patch_weight)
124
120
  version_delta += abs(weighted_version_B - weighted_version_A)
125
121
 
126
122
  return version_delta
@@ -52,11 +52,7 @@ def gh_get_rate_limit_info(github_token: str | None = None) -> dict | None:
52
52
  core = rate_limit.resources.core
53
53
 
54
54
  # core.reset is a datetime object
55
- reset_dt = (
56
- core.reset
57
- if isinstance(core.reset, datetime)
58
- else datetime.fromtimestamp(core.reset)
59
- )
55
+ reset_dt = core.reset if isinstance(core.reset, datetime) else datetime.fromtimestamp(core.reset)
60
56
  # convert to local timezone
61
57
  reset_dt = reset_dt.astimezone()
62
58
  # convert to a naive datetime in local time
@@ -164,9 +160,7 @@ def gh_check_repo_exists(owner: str, repo: str) -> GithubAPIResult:
164
160
  if response.status_code == 403:
165
161
  remaining = response.headers.get("X-RateLimit-Remaining", "?")
166
162
  reset_time_str = response.headers.get("X-RateLimit-Reset", "")
167
- error_msg = (
168
- f"Rate limited (remaining: {remaining}, resets: {reset_time_str})"
169
- )
163
+ error_msg = f"Rate limited (remaining: {remaining}, resets: {reset_time_str})"
170
164
  return GithubAPIResult(
171
165
  data={"owner": owner, "repo": repo},
172
166
  error=error_msg,
@@ -182,9 +176,7 @@ def gh_check_repo_exists(owner: str, repo: str) -> GithubAPIResult:
182
176
  error_msg = error_data["message"]
183
177
  except Exception:
184
178
  pass
185
- return GithubAPIResult(
186
- data={"owner": owner, "repo": repo}, error=error_msg, success=False
187
- )
179
+ return GithubAPIResult(data={"owner": owner, "repo": repo}, error=error_msg, success=False)
188
180
 
189
181
  # Other errors
190
182
  error_msg = f"HTTP {response.status_code}"
@@ -195,18 +187,12 @@ def gh_check_repo_exists(owner: str, repo: str) -> GithubAPIResult:
195
187
  except Exception:
196
188
  pass
197
189
  logger.warning(f"GitHub API error for {owner}/{repo}: {error_msg}")
198
- return GithubAPIResult(
199
- data={"owner": owner, "repo": repo}, error=error_msg, success=False
200
- )
190
+ return GithubAPIResult(data={"owner": owner, "repo": repo}, error=error_msg, success=False)
201
191
  except requests.RequestException as e:
202
- return GithubAPIResult(
203
- data={"owner": owner, "repo": repo}, error=str(e), success=False
204
- )
192
+ return GithubAPIResult(data={"owner": owner, "repo": repo}, error=str(e), success=False)
205
193
 
206
194
 
207
- def fetch_github_repo_metadata(
208
- repo_url: str, github_token: str | None = None
209
- ) -> pd.DataFrame:
195
+ def fetch_github_repo_metadata(repo_url: str, github_token: str | None = None) -> pd.DataFrame:
210
196
  """Fetch GitHub repo metadata given repo URL and token.
211
197
 
212
198
  Handles rate limiting by catching RateLimitExceededException and waiting
@@ -218,9 +204,7 @@ def fetch_github_repo_metadata(
218
204
  raise ValueError(f"Invalid repository URL: {repo_url}")
219
205
 
220
206
  # Configure GitHub client with explicit timeout (30 seconds)
221
- github_client = (
222
- Github(github_token, timeout=30) if github_token else Github(timeout=30)
223
- )
207
+ github_client = Github(github_token, timeout=30) if github_token else Github(timeout=30)
224
208
  repo_obj = github_client.get_repo(f"{owner}/{repo}")
225
209
  data = {
226
210
  "repo_url": repo_url,
osslag/utils/vcs.py CHANGED
@@ -72,9 +72,7 @@ def normalize_https_repo_url(url: str) -> NormalizeRepoResult:
72
72
  clean_url = f"https://github.com/{match.group(1)}/{match.group(2)}"
73
73
  return NormalizeRepoResult(clean_url, None)
74
74
 
75
- return NormalizeRepoResult(
76
- None, "URL does not match expected git repository patterns"
77
- )
75
+ return NormalizeRepoResult(None, "URL does not match expected git repository patterns")
78
76
 
79
77
 
80
78
  def ensure_dir(p: str | os.PathLike) -> pathlib.Path:
@@ -115,9 +113,7 @@ def extract_owner_name_repo(repo_url: str) -> RepoOwnerName:
115
113
  if repo_url_normalized and repo_url_normalized.url is not None:
116
114
  parts = repo_url_normalized.url.split("/")
117
115
  if len(parts) < 2:
118
- logger.error(
119
- "Could not extract owner/repo from URL: call normalize_repo_url first"
120
- )
116
+ logger.error("Could not extract owner/repo from URL: call normalize_repo_url first")
121
117
  return RepoOwnerName(None, None)
122
118
  owner = parts[-2]
123
119
  repo = parts[-1]
@@ -206,15 +202,11 @@ def clone_repo(
206
202
  github_token = os.getenv("GITHUB_TOKEN")
207
203
  github_username = os.getenv("GITHUB_USERNAME")
208
204
  if github_token and github_username:
209
- callbacks = pygit2.RemoteCallbacks(
210
- pygit2.UserPass(github_username, github_token)
211
- )
205
+ callbacks = pygit2.RemoteCallbacks(pygit2.UserPass(github_username, github_token))
212
206
  else:
213
207
  callbacks = None
214
208
 
215
- repo_obj = pygit2.clone_repository(
216
- sanitize_url.url, dest_dir, callbacks=callbacks, bare=False
217
- )
209
+ repo_obj = pygit2.clone_repository(sanitize_url.url, dest_dir, callbacks=callbacks, bare=False)
218
210
 
219
211
  # Checkout specific branch if requested
220
212
  if branch:
@@ -274,9 +266,7 @@ def construct_repo_local_path(
274
266
  Examples:
275
267
  >>> construct_repo_local_path("https://github.com/owner/repo", "./cache")
276
268
  None # if not cloned yet
277
- >>> construct_repo_local_path(
278
- ... "https://github.com/owner/repo", "./cache", must_exist=False
279
- ... )
269
+ >>> construct_repo_local_path("https://github.com/owner/repo", "./cache", must_exist=False)
280
270
  PosixPath('./cache/owner--repo')
281
271
 
282
272
  """
@@ -289,9 +279,7 @@ def construct_repo_local_path(
289
279
  return None
290
280
 
291
281
  REPOS_CACHE_DIR = os.getenv("REPOS_CACHE_DIR") or str(cache_dir)
292
- local_repo_path = (
293
- pathlib.Path(REPOS_CACHE_DIR) / f"{repo_owner.owner}--{repo_owner.name}"
294
- )
282
+ local_repo_path = pathlib.Path(REPOS_CACHE_DIR) / f"{repo_owner.owner}--{repo_owner.name}"
295
283
  if must_exist and not local_repo_path.exists():
296
284
  return None
297
285
  if local_repo_path.exists():
@@ -332,10 +320,7 @@ def label_trivial_commits(
332
320
  return True
333
321
 
334
322
  # Documentation-only change if all files are .md files
335
- rval = all(
336
- isinstance(f, str) and pathlib.PurePosixPath(f).suffix.lower() == ".md"
337
- for f in files
338
- )
323
+ rval = all(isinstance(f, str) and pathlib.PurePosixPath(f).suffix.lower() == ".md" for f in files)
339
324
  return rval
340
325
 
341
326
  commits_df[label_column] = commits_df[files_column].apply(_is_trivial)
@@ -388,9 +373,7 @@ def load_commits(
388
373
 
389
374
  git_dir = repo_path / ".git"
390
375
  if not git_dir.exists():
391
- raise FileNotFoundError(
392
- f"Not a Git repository (missing .git directory): {repo_path}"
393
- )
376
+ raise FileNotFoundError(f"Not a Git repository (missing .git directory): {repo_path}")
394
377
 
395
378
  try:
396
379
  repo = pygit2.Repository(str(repo_path))
@@ -406,16 +389,12 @@ def load_commits(
406
389
  ref = repo.references[f"refs/remotes/origin/{branch}"]
407
390
  start_id = ref.peel(pygit2.Commit).id
408
391
  except KeyError as e:
409
- raise ValueError(
410
- f"Branch '{branch}' not found in {repo_path}"
411
- ) from e
392
+ raise ValueError(f"Branch '{branch}' not found in {repo_path}") from e
412
393
  else:
413
394
  try:
414
395
  start_id = repo.head.peel(pygit2.Commit).id
415
396
  except pygit2.GitError as e:
416
- raise ValueError(
417
- f"Repository has no HEAD (empty repository?): {repo_path}"
418
- ) from e
397
+ raise ValueError(f"Repository has no HEAD (empty repository?): {repo_path}") from e
419
398
 
420
399
  def _changed_paths(c: pygit2.Commit) -> list[str]:
421
400
  """Return list of paths touched by the commit (optimized, skips merge commits)."""
@@ -431,18 +410,14 @@ def load_commits(
431
410
  if c.parents:
432
411
  # Diff from parent to commit (shows what changed in this commit)
433
412
  # context_lines=0 skips computing context around changes
434
- diff = c.parents[0].tree.diff_to_tree(
435
- c.tree, flags=flags, context_lines=0
436
- )
413
+ diff = c.parents[0].tree.diff_to_tree(c.tree, flags=flags, context_lines=0)
437
414
  else:
438
415
  # Initial commit - diff against empty tree
439
416
  diff = c.tree.diff_to_tree(flags=flags, context_lines=0)
440
417
 
441
418
  # Extract paths directly with list comprehension
442
419
  # new_file.path is set for adds/modifies, old_file.path for deletes
443
- return [
444
- delta.new_file.path or delta.old_file.path for delta in diff.deltas
445
- ]
420
+ return [delta.new_file.path or delta.old_file.path for delta in diff.deltas]
446
421
  except Exception:
447
422
  return []
448
423
 
@@ -480,12 +455,8 @@ def load_commits(
480
455
  continue
481
456
  row = {
482
457
  "hash": str(commit.id),
483
- "author": _safe_str(
484
- lambda c=commit: c.author.name if c.author else None
485
- ),
486
- "email": _safe_str(
487
- lambda c=commit: c.author.email if c.author else None
488
- ),
458
+ "author": _safe_str(lambda c=commit: c.author.name if c.author else None),
459
+ "email": _safe_str(lambda c=commit: c.author.email if c.author else None),
489
460
  "message": _safe_str(lambda c=commit: c.message),
490
461
  "timestamp": commit.commit_time,
491
462
  "date": datetime.fromtimestamp(commit.commit_time),
@@ -524,12 +495,8 @@ def find_upstream_version_tag_commit(
524
495
  version_tag_patterns = [
525
496
  re.compile(rf"^v{re.escape(version)}$"), # v1.2.3
526
497
  re.compile(rf"^{re.escape(version)}$"), # 1.2.3
527
- re.compile(
528
- rf"^release[-_]?{re.escape(version)}$"
529
- ), # release-1.2.3 or release_1.2.3
530
- re.compile(
531
- rf"^version[-_]?{re.escape(version)}$"
532
- ), # version-1.2.3 or version_1.2.3
498
+ re.compile(rf"^release[-_]?{re.escape(version)}$"), # release-1.2.3 or release_1.2.3
499
+ re.compile(rf"^version[-_]?{re.escape(version)}$"), # version-1.2.3 or version_1.2.3
533
500
  ]
534
501
 
535
502
  for _, row in commits.iterrows():
@@ -1,16 +1,21 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: osslag
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Technical Lag tools for Open Source Software Projects
5
- Keywords: oss,open source,technical lag,software lag,software maintenance
6
- Author: Shane Panter
7
5
  Author-email: Shane Panter <shanepanter@boisestate.edu>
8
6
  License: MIT
7
+ Project-URL: Homepage, https://github.com/shanep/osslag
8
+ Project-URL: Issues, https://github.com/shanep/osslag/issues
9
+ Keywords: oss,open source,technical lag,software lag,software maintenance
9
10
  Classifier: Intended Audience :: Science/Research
10
11
  Classifier: License :: OSI Approved :: MIT License
11
12
  Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Python: >=3.14
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
12
16
  Requires-Dist: dotenv>=0.9.9
13
17
  Requires-Dist: pandas>=3.0.0
18
+ Requires-Dist: pyarrow>=23.0.0
14
19
  Requires-Dist: pygit2>=1.19.1
15
20
  Requires-Dist: pygithub>=2.8.1
16
21
  Requires-Dist: python-dateutil>=2.9.0.post0
@@ -18,16 +23,17 @@ Requires-Dist: python-debian>=1.0.1
18
23
  Requires-Dist: requests>=2.32.5
19
24
  Requires-Dist: rich>=14.3.1
20
25
  Requires-Dist: typer>=0.21.1
21
- Requires-Python: >=3.14
22
- Project-URL: Homepage, https://github.com/shanep/osslag
23
- Project-URL: Issues, https://github.com/shanep/osslag/issues
24
- Description-Content-Type: text/markdown
26
+ Dynamic: license-file
25
27
 
26
28
  # OSS-Lag: Open Source Software Lag Dataset
27
29
 
28
30
  This repository contains code to build a dataset measuring technical lag and
29
31
  abandonment of open source packages across multiple Linux distributions.
30
32
 
33
+ The algorithms implemented here are described in the following paper:
34
+
35
+ - [docs/Paper.pdf](docs/Paper.pdf)
36
+
31
37
 
32
38
  ## Installation
33
39
 
@@ -0,0 +1,17 @@
1
+ osslag/__init__.py,sha256=cwlK7jN7SyU_ao6nLWTJxjOX-5WzTEdZJrcwrTnRgeI,212
2
+ osslag/cli.py,sha256=IswvyAStwEUt8VRhCelULd51UAViwsD7klwDOi8P6eM,51110
3
+ osslag/distro/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ osslag/distro/debian.py,sha256=iCItgoK4H59fbBl-wB4MYREiHKziN3LrGP8PMATyveY,12561
5
+ osslag/distro/fedora.py,sha256=kJwwGSr6Sw2lZW82gnzEUYHog9lrKJCy3Johu9ZjC2M,1168
6
+ osslag/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ osslag/metrics/malta.py,sha256=aElue_1_NWcWzSdCffI2bhe13mPsRCKXVgjVnJx4r-o,30668
8
+ osslag/metrics/pvac.py,sha256=L04fKxKAgWDB-mPUwNQCwp7R6UiaPSSKgiPiGVmV35o,6088
9
+ osslag/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ osslag/utils/github_helper.py,sha256=MU40G0EjRTGBenQTFpQm_nUL5rvBKvYp7mEIiafWhsE,8576
11
+ osslag/utils/vcs.py,sha256=m8U4is6k5uXsLr-KRzcVrb2tWJ2bDsbi44iXkB9QTIc,18717
12
+ osslag-1.0.1.dist-info/licenses/LICENSE,sha256=ohKRn422C1u0QfHnJgBh_YHgTfLySo4ncRuw_T32yzE,1072
13
+ osslag-1.0.1.dist-info/METADATA,sha256=Ur48ahgiXroiBOvVde-Ttkqdnqu3Qotouy-mu_IPAqI,1451
14
+ osslag-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
15
+ osslag-1.0.1.dist-info/entry_points.txt,sha256=LfxP6PyXpMTwnuATQp2hZ0iIOOAXarQlz0pMIQU-NRU,43
16
+ osslag-1.0.1.dist-info/top_level.txt,sha256=5RYA9YhTanChK5jQFElHU_4kMqiqWkZXyCEpRmtvIyk,7
17
+ osslag-1.0.1.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.28
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
+
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
2
  osslag = osslag.cli:main
3
-
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shane K. Panter
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 @@
1
+ osslag
@@ -1,15 +0,0 @@
1
- osslag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- osslag/cli.py,sha256=APVFUBACfJ-PRs9pwvD359aaCRd4w6KCZcAjGdTWNoM,52658
3
- osslag/distro/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- osslag/distro/debian.py,sha256=sjodEtq8rboTkWfywn3wrbC-RiDmIURev7XvB9KJ_Ig,12867
5
- osslag/distro/fedora.py,sha256=88b9gIMUaixCwsjQpX5oBK654tFLVI_8Bv3QwwdV3Yo,1174
6
- osslag/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- osslag/metrics/malta.py,sha256=ToT4imGHXHkws-y9m9qUMn913OMakRF1qPMiXR-wGEI,19563
8
- osslag/metrics/pvac.py,sha256=tfkMbYFS6Mk-pEocu6sMOIoniD5to0QA-p8OyXW2lyo,6136
9
- osslag/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- osslag/utils/github_helper.py,sha256=vsWPoSqKEcNaa_1MdUJlWJhP4CCwW-Es4CyodOr7tUs,8752
11
- osslag/utils/vcs.py,sha256=EMtHgZ6ftQVn-B9H0D6gUhWK-YjGBp05ArAymcH7V4w,19203
12
- osslag-1.0.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
13
- osslag-1.0.0.dist-info/entry_points.txt,sha256=-MrR4OE3-FnFkYTVyMYLbhwzpLBKgTtrDvCyvHUQqP8,44
14
- osslag-1.0.0.dist-info/METADATA,sha256=o1c5-RahYQoECta_PLJlukYme-azqzKTuikE4DlJ5z8,1290
15
- osslag-1.0.0.dist-info/RECORD,,