runspec-gitlab-core 0.5.0__tar.gz → 0.6.0__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.
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to `runspec-gitlab-core` are documented here.
4
4
 
5
+ ## [0.6.0] — 2026-10-25
6
+
7
+ - New `list_projects`: the token user's member (or owned) projects, optionally
8
+ filtered by a term that also matches the namespace path, in GitLab's short
9
+ form unless `full`.
10
+ - `search_projects` adds the namespace match (`search_namespaces`) only within
11
+ the user's own projects. Across every visible project it made gitlab.com
12
+ time out with a 500 after about 16 seconds (0.5.0 regression). New `simple`
13
+ parameter.
14
+
5
15
  ## [0.5.0] — 2026-10-25
6
16
 
7
17
  - `search_projects` can list the token user's projects: `search` is now
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: runspec-gitlab-core
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Pure-Python GitLab REST helpers — the importable core behind runspec-gitlab (no runspec dependency, no runnables)
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: httpx
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "runspec-gitlab-core"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  requires-python = ">=3.10"
9
9
  description = "Pure-Python GitLab REST helpers — the importable core behind runspec-gitlab (no runspec dependency, no runnables)"
10
10
  dependencies = [
@@ -56,7 +56,14 @@ from runspec_gitlab_core.pipelines import (
56
56
  retry_job,
57
57
  retry_pipeline,
58
58
  )
59
- from runspec_gitlab_core.projects import get_project, resolve_project_path, resolve_user_ids, search_projects, whoami
59
+ from runspec_gitlab_core.projects import (
60
+ get_project,
61
+ list_projects,
62
+ resolve_project_path,
63
+ resolve_user_ids,
64
+ search_projects,
65
+ whoami,
66
+ )
60
67
  from runspec_gitlab_core.repository import (
61
68
  create_branch,
62
69
  delete_branch,
@@ -80,6 +87,7 @@ __all__ = [
80
87
  # identity / projects
81
88
  "whoami",
82
89
  "get_project",
90
+ "list_projects",
83
91
  "search_projects",
84
92
  # issues
85
93
  "list_issues",
@@ -52,29 +52,59 @@ def search_projects(
52
52
  per_page: int = 20,
53
53
  page: int = 1,
54
54
  owned: bool = False,
55
+ simple: bool = False,
55
56
  ) -> list[dict[str, Any]]:
56
57
  """Search or list projects, most recently active first.
57
58
 
58
- ``search`` matches the project name or path, including its group/namespace
59
- path (so ``"finejason"`` finds projects under that user's namespace). GitLab
60
- matches a term shorter than 3 characters only as an exact name. With no
61
- ``search`` the call lists the token user's projects: ``membership`` is
62
- implied, since listing every visible project on a public instance is
63
- useless. ``owned`` limits results to projects the user owns.
59
+ ``search`` matches the project name or path. GitLab matches a term shorter
60
+ than 3 characters only as an exact name. With no ``search`` the call lists
61
+ the token user's projects: ``membership`` is implied, since listing every
62
+ visible project on a public instance is useless. ``owned`` limits results
63
+ to projects the user owns. Within the user's own projects (``membership``
64
+ or ``owned``) a term also matches the group/namespace path; across every
65
+ visible project that match times out on gitlab.com, so it is left off.
66
+ ``simple`` asks GitLab for its short project representation.
64
67
  """
68
+ mine = membership or owned or not search
65
69
  params: dict[str, Any] = {
66
- "membership": membership or not search or None,
70
+ "membership": mine or None,
67
71
  "owned": owned or None,
72
+ "simple": simple or None,
68
73
  "order_by": "last_activity_at",
69
74
  "per_page": per_page,
70
75
  "page": page,
71
76
  }
72
77
  if search:
73
78
  params["search"] = search
74
- params["search_namespaces"] = True
79
+ if mine:
80
+ params["search_namespaces"] = True
75
81
  return gitlab_request("GET", "/projects", config=config, params=params)
76
82
 
77
83
 
84
+ def list_projects(
85
+ config: GitlabConfig,
86
+ search: str | None = None,
87
+ owned: bool = False,
88
+ per_page: int = 20,
89
+ page: int = 1,
90
+ full: bool = False,
91
+ ) -> list[dict[str, Any]]:
92
+ """List the projects the token user is a member of (or owns), most recently
93
+ active first, optionally filtered by ``search`` (name, path or namespace).
94
+
95
+ Returns GitLab's short project representation unless ``full``.
96
+ """
97
+ return search_projects(
98
+ config,
99
+ search=search,
100
+ membership=True,
101
+ per_page=per_page,
102
+ page=page,
103
+ owned=owned,
104
+ simple=not full,
105
+ )
106
+
107
+
78
108
  def resolve_user_ids(config: GitlabConfig, usernames: list[str] | None) -> list[int] | None:
79
109
  """Map GitLab usernames to numeric user ids (the write APIs take ids).
80
110
 
@@ -19,6 +19,7 @@ from runspec_gitlab_core import (
19
19
  get_merge_request_pipelines,
20
20
  list_issues,
21
21
  list_pipelines,
22
+ list_projects,
22
23
  list_tree,
23
24
  search_projects,
24
25
  whoami,
@@ -65,11 +66,29 @@ def test_search_projects_without_term_lists_member_projects(monkeypatch):
65
66
  assert "search_namespaces" not in seen["query"]
66
67
 
67
68
 
68
- def test_search_projects_term_without_membership_leaves_it_off(monkeypatch):
69
+ def test_search_projects_plain_term_searches_everything_by_name(monkeypatch):
70
+ # search_namespaces across every public gitlab.com project times out (500)
69
71
  seen = _capture(monkeypatch, httpx.Response(200, json=[]))
70
- search_projects(CFG, "runspec", owned=True)
72
+ search_projects(CFG, "runspec")
73
+ assert seen["query"]["search"] == "runspec"
71
74
  assert "membership" not in seen["query"]
75
+ assert "search_namespaces" not in seen["query"]
76
+
77
+
78
+ def test_list_projects_is_mine_and_short(monkeypatch):
79
+ seen = _capture(monkeypatch, httpx.Response(200, json=[]))
80
+ list_projects(CFG, "cli", owned=True)
81
+ assert seen["query"]["membership"] == "true"
72
82
  assert seen["query"]["owned"] == "true"
83
+ assert seen["query"]["simple"] == "true"
84
+ assert seen["query"]["search_namespaces"] == "true"
85
+
86
+
87
+ def test_list_projects_full(monkeypatch):
88
+ seen = _capture(monkeypatch, httpx.Response(200, json=[]))
89
+ list_projects(CFG, full=True)
90
+ assert "simple" not in seen["query"]
91
+ assert "search" not in seen["query"]
73
92
 
74
93
 
75
94
  def test_list_issues_uses_default_project_and_filters(monkeypatch):