@@ -70,7 +70,7 @@ def _route_for_metrics(path: str) -> str:
7070 parts [3 ] = "{run_id}"
7171 elif parts [0 ] == "schedules" and len (parts ) >= 2 :
7272 parts [1 ] = "{schedule_id}"
73- elif parts [0 ] == " search-attributes" and len (parts ) >= 2 :
73+ elif parts [0 ] in { "namespaces" , " search-attributes"} and len (parts ) >= 2 :
7474 parts [1 ] = "{name}"
7575 elif parts [0 ] == "workers" and len (parts ) >= 2 :
7676 parts [1 ] = "{worker_id}"
@@ -109,6 +109,36 @@ class WorkflowList:
109109 next_page_token : str | None = None
110110
111111
112+ @dataclass
113+ class NamespaceDescription :
114+ """Server configuration for one workflow namespace."""
115+
116+ name : str
117+ description : str | None = None
118+ retention_days : int | None = None
119+ status : str | None = None
120+ created_at : str | None = None
121+ updated_at : str | None = None
122+
123+ @classmethod
124+ def from_dict (cls , data : dict [str , Any ]) -> NamespaceDescription :
125+ return cls (
126+ name = str (data .get ("name" , "" )),
127+ description = data .get ("description" ),
128+ retention_days = data .get ("retention_days" ),
129+ status = data .get ("status" ),
130+ created_at = data .get ("created_at" ),
131+ updated_at = data .get ("updated_at" ),
132+ )
133+
134+
135+ @dataclass
136+ class NamespaceList :
137+ """Namespaces visible to the current control-plane identity."""
138+
139+ namespaces : list [NamespaceDescription ]
140+
141+
112142@dataclass
113143class WorkflowRun :
114144 """Current server view of one durable run in a workflow execution chain."""
@@ -1040,6 +1070,93 @@ async def health(self) -> dict[str, Any]:
10401070 )
10411071 return result
10421072
1073+ # ── Namespaces ────────────────────────────────────────────────────
1074+ async def list_namespaces (self ) -> NamespaceList :
1075+ """List namespaces visible to the current control-plane identity."""
1076+ data = await self ._request ("GET" , "/namespaces" )
1077+ if not isinstance (data , dict ):
1078+ raise ServerError (
1079+ 200 ,
1080+ {
1081+ "reason" : "invalid_namespace_response" ,
1082+ "message" : f"expected JSON object, got { type (data ).__name__ } " ,
1083+ },
1084+ )
1085+ items = data .get ("namespaces" , [])
1086+ return NamespaceList (
1087+ namespaces = [
1088+ NamespaceDescription .from_dict (item )
1089+ for item in items
1090+ if isinstance (item , dict )
1091+ ],
1092+ )
1093+
1094+ async def describe_namespace (self , name : str ) -> NamespaceDescription :
1095+ """Return configuration and status for one namespace."""
1096+ data = await self ._request ("GET" , f"/namespaces/{ quote (name , safe = '' )} " , context = name )
1097+ if not isinstance (data , dict ):
1098+ raise ServerError (
1099+ 200 ,
1100+ {
1101+ "reason" : "invalid_namespace_response" ,
1102+ "message" : f"expected JSON object, got { type (data ).__name__ } " ,
1103+ },
1104+ )
1105+ return NamespaceDescription .from_dict (data )
1106+
1107+ async def create_namespace (
1108+ self ,
1109+ name : str ,
1110+ * ,
1111+ description : str | None = None ,
1112+ retention_days : int = 30 ,
1113+ ) -> NamespaceDescription :
1114+ """Create a workflow namespace and return the server representation."""
1115+ data = await self ._request (
1116+ "POST" ,
1117+ "/namespaces" ,
1118+ json = {
1119+ "name" : name ,
1120+ "description" : description ,
1121+ "retention_days" : retention_days ,
1122+ },
1123+ context = name ,
1124+ )
1125+ if not isinstance (data , dict ):
1126+ raise ServerError (
1127+ 200 ,
1128+ {
1129+ "reason" : "invalid_namespace_response" ,
1130+ "message" : f"expected JSON object, got { type (data ).__name__ } " ,
1131+ },
1132+ )
1133+ return NamespaceDescription .from_dict (data )
1134+
1135+ async def update_namespace (
1136+ self ,
1137+ name : str ,
1138+ * ,
1139+ description : str | None = None ,
1140+ retention_days : int | None = None ,
1141+ ) -> NamespaceDescription :
1142+ """Update namespace metadata. Only provided fields are sent."""
1143+ body : dict [str , Any ] = {}
1144+ if description is not None :
1145+ body ["description" ] = description
1146+ if retention_days is not None :
1147+ body ["retention_days" ] = retention_days
1148+
1149+ data = await self ._request ("PUT" , f"/namespaces/{ quote (name , safe = '' )} " , json = body , context = name )
1150+ if not isinstance (data , dict ):
1151+ raise ServerError (
1152+ 200 ,
1153+ {
1154+ "reason" : "invalid_namespace_response" ,
1155+ "message" : f"expected JSON object, got { type (data ).__name__ } " ,
1156+ },
1157+ )
1158+ return NamespaceDescription .from_dict (data )
1159+
10431160 # ── Task queues ────────────────────────────────────────────────────
10441161 async def list_task_queues (self ) -> TaskQueueList :
10451162 """List task queues with server-side admission status.
0 commit comments