runspec-gitlab-core 0.4.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,24 @@
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
+
15
+ ## [0.5.0] — 2026-10-25
16
+
17
+ - `search_projects` can list the token user's projects: `search` is now
18
+ optional, and with no term the call asks for member projects only (listing
19
+ every visible project on gitlab.com is no use). A term also matches the
20
+ group/namespace path (`search_namespaces`), so a username or group name finds
21
+ the projects under it. New `owned` parameter limits results to owned projects.
22
+
5
23
  ## [0.4.0] — 2026-10-25
6
24
 
7
25
  - `config_from_env` reads the token from `GITLAB_TOKEN` or `GITLAB_PAT` (each
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: runspec-gitlab-core
3
- Version: 0.4.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.4.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",
@@ -47,26 +47,61 @@ def get_project(config: GitlabConfig, project: str | None = None) -> dict[str, A
47
47
 
48
48
  def search_projects(
49
49
  config: GitlabConfig,
50
- search: str,
50
+ search: str | None = None,
51
51
  membership: bool = False,
52
52
  per_page: int = 20,
53
53
  page: int = 1,
54
+ owned: bool = False,
55
+ simple: bool = False,
54
56
  ) -> list[dict[str, Any]]:
55
- """Search visible projects by name/path.
57
+ """Search or list projects, most recently active first.
58
+
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.
67
+ """
68
+ mine = membership or owned or not search
69
+ params: dict[str, Any] = {
70
+ "membership": mine or None,
71
+ "owned": owned or None,
72
+ "simple": simple or None,
73
+ "order_by": "last_activity_at",
74
+ "per_page": per_page,
75
+ "page": page,
76
+ }
77
+ if search:
78
+ params["search"] = search
79
+ if mine:
80
+ params["search_namespaces"] = True
81
+ return gitlab_request("GET", "/projects", config=config, params=params)
82
+
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).
56
94
 
57
- ``membership`` limits results to projects the token's user is a member of.
95
+ Returns GitLab's short project representation unless ``full``.
58
96
  """
59
- return gitlab_request(
60
- "GET",
61
- "/projects",
62
- config=config,
63
- params={
64
- "search": search,
65
- "membership": membership,
66
- "order_by": "last_activity_at",
67
- "per_page": per_page,
68
- "page": page,
69
- },
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,
70
105
  )
71
106
 
72
107
 
@@ -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,
@@ -53,6 +54,41 @@ def test_search_projects_encodes_membership(monkeypatch):
53
54
  assert seen["path"] == "/api/v4/projects"
54
55
  assert seen["query"]["search"] == "runspec"
55
56
  assert seen["query"]["membership"] == "true"
57
+ assert seen["query"]["search_namespaces"] == "true"
58
+ assert "owned" not in seen["query"]
59
+
60
+
61
+ def test_search_projects_without_term_lists_member_projects(monkeypatch):
62
+ seen = _capture(monkeypatch, httpx.Response(200, json=[]))
63
+ search_projects(CFG)
64
+ assert seen["query"]["membership"] == "true"
65
+ assert "search" not in seen["query"]
66
+ assert "search_namespaces" not in seen["query"]
67
+
68
+
69
+ def test_search_projects_plain_term_searches_everything_by_name(monkeypatch):
70
+ # search_namespaces across every public gitlab.com project times out (500)
71
+ seen = _capture(monkeypatch, httpx.Response(200, json=[]))
72
+ search_projects(CFG, "runspec")
73
+ assert seen["query"]["search"] == "runspec"
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"
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"]
56
92
 
57
93
 
58
94
  def test_list_issues_uses_default_project_and_filters(monkeypatch):