meshagent-api 0.53.0__tar.gz → 0.53.2__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.
Files changed (74) hide show
  1. {meshagent_api-0.53.0/meshagent_api.egg-info → meshagent_api-0.53.2}/PKG-INFO +1 -1
  2. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/client.py +98 -11
  3. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/client_test.py +79 -1
  4. meshagent_api-0.53.2/meshagent/api/version.py +1 -0
  5. {meshagent_api-0.53.0 → meshagent_api-0.53.2/meshagent_api.egg-info}/PKG-INFO +1 -1
  6. meshagent_api-0.53.0/meshagent/api/version.py +0 -1
  7. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/LICENSE +0 -0
  8. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/MANIFEST.in +0 -0
  9. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/README.md +0 -0
  10. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/__init__.py +0 -0
  11. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/agent_content.py +0 -0
  12. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/aiohttp_redaction.py +0 -0
  13. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/chan.py +0 -0
  14. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/crdt.py +0 -0
  15. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/dataset_schema.py +0 -0
  16. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/entrypoint.js +0 -0
  17. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/error_codes.py +0 -0
  18. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/helpers.py +0 -0
  19. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/http.py +0 -0
  20. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/image_runtime.py +0 -0
  21. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/keys.py +0 -0
  22. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/llm_router_config.py +0 -0
  23. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/managed_agents.py +0 -0
  24. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/managed_agents_test.py +0 -0
  25. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/messaging.py +0 -0
  26. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/messaging_test.py +0 -0
  27. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/mime_types.py +0 -0
  28. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/mime_types_test.py +0 -0
  29. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/oauth.py +0 -0
  30. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/oauth_branding.py +0 -0
  31. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/oauth_scopes.py +0 -0
  32. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/oauth_scopes_test.py +0 -0
  33. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/participant.py +0 -0
  34. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/participant_token.py +0 -0
  35. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/participant_token_test.py +0 -0
  36. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/port_forward.py +0 -0
  37. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/port_forward_test.py +0 -0
  38. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/protocol.py +0 -0
  39. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/protocol_test.py +0 -0
  40. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/py.typed +0 -0
  41. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/reasoning_schema.py +0 -0
  42. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/registry_auth.py +0 -0
  43. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/room_ports.py +0 -0
  44. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/room_server_client.py +0 -0
  45. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/room_server_client_test.py +0 -0
  46. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/runtime.py +0 -0
  47. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/runtime_test.py +0 -0
  48. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/schema.py +0 -0
  49. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/schema_document.py +0 -0
  50. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/schema_document_test.py +0 -0
  51. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/schema_registry.py +0 -0
  52. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/schema_test.py +0 -0
  53. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/schema_util.py +0 -0
  54. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/service_spec_test.py +0 -0
  55. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/service_template_test.py +0 -0
  56. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/services.py +0 -0
  57. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/services_test.py +0 -0
  58. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/specs/service.py +0 -0
  59. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/sql.py +0 -0
  60. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/sql_test.py +0 -0
  61. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/sqlite_transfer.py +0 -0
  62. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/sqlite_transfer_test.py +0 -0
  63. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/token_test.py +0 -0
  64. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/urls.py +0 -0
  65. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/webhooks.py +0 -0
  66. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/webhooks_test.py +0 -0
  67. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/websocket_protocol.py +0 -0
  68. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent/api/websocket_protocol_test.py +0 -0
  69. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent_api.egg-info/SOURCES.txt +0 -0
  70. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent_api.egg-info/dependency_links.txt +0 -0
  71. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent_api.egg-info/requires.txt +0 -0
  72. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/meshagent_api.egg-info/top_level.txt +0 -0
  73. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/pyproject.toml +0 -0
  74. {meshagent_api-0.53.0 → meshagent_api-0.53.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.53.0
3
+ Version: 0.53.2
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -722,6 +722,27 @@ class UpdateUserProfileRequest(BaseModel):
722
722
  annotations: dict[str, str] = Field(default_factory=dict)
723
723
 
724
724
 
725
+ UserProfileView = Literal["project", "user", "merged"]
726
+ UserProfileField = Literal["first_name", "last_name", "metadata", "annotations"]
727
+
728
+
729
+ class UpdateProjectUserProfileRequest(UpdateUserProfileRequest):
730
+ inherit: list[UserProfileField] = Field(default_factory=list)
731
+
732
+ @model_validator(mode="after")
733
+ def validate_inherited_fields(self) -> "UpdateProjectUserProfileRequest":
734
+ if set(self.inherit) & self.model_fields_set:
735
+ raise ValueError(
736
+ "A field cannot be supplied and inherited in the same update"
737
+ )
738
+ return self
739
+
740
+
741
+ class UserProfilesPage(BaseModel):
742
+ users: list[User]
743
+ continuation_token: str | None = None
744
+
745
+
725
746
  class UserRoomGrant(BaseModel):
726
747
  room: Room
727
748
  user: User
@@ -1558,13 +1579,14 @@ class Meshagent:
1558
1579
  page_size: int = 100,
1559
1580
  continuation_token: str | None = None,
1560
1581
  filter: str | None = None,
1582
+ view: UserProfileView = "merged",
1561
1583
  ) -> ProjectMembersPage:
1562
1584
  """
1563
1585
  Corresponds to: GET /accounts/projects/:id/users
1564
1586
  Returns a JSON dict with { "users": [...] }.
1565
1587
  """
1566
1588
  url = f"{self.base_url}/accounts/projects/{project_id}/users"
1567
- params: Dict[str, str] = {"page_size": str(page_size)}
1589
+ params: Dict[str, str] = {"page_size": str(page_size), "view": view}
1568
1590
  if continuation_token is not None:
1569
1591
  params["continuation_token"] = continuation_token
1570
1592
  if filter is not None and filter.strip() != "":
@@ -1580,18 +1602,37 @@ class Meshagent:
1580
1602
  except ValidationError as exc:
1581
1603
  raise RoomException(f"Invalid project users payload: {exc}") from exc
1582
1604
 
1583
- async def get_users_in_project(self, project_id: str) -> Dict[str, Any]:
1584
- page = await self.get_users_in_project_page(project_id)
1585
- return page.model_dump(mode="json")
1605
+ async def get_users_in_project(
1606
+ self, project_id: str, *, view: UserProfileView = "merged"
1607
+ ) -> Dict[str, Any]:
1608
+ page = await self.get_users_in_project_page(project_id, view=view)
1609
+ return page.model_dump(mode="json", exclude_unset=view == "project")
1586
1610
 
1587
- async def get_user_profile(self, user_id: str) -> Dict[str, Any]:
1611
+ async def get_user_profile(
1612
+ self,
1613
+ user_id: str,
1614
+ *,
1615
+ project_id: str | None = None,
1616
+ view: UserProfileView = "merged",
1617
+ ) -> Dict[str, Any]:
1588
1618
  """
1589
1619
  Corresponds to: GET /accounts/profiles/:id
1590
1620
  Returns the user profile JSON, e.g. { "id", "first_name", "last_name", "email" } or raises 404 if not found.
1591
1621
  """
1592
- url = f"{self.base_url}/accounts/profiles/{user_id}"
1622
+ user_path = quote(user_id, safe="")
1623
+ if project_id is None:
1624
+ if view == "project":
1625
+ raise ValueError("project_id is required for the project view")
1626
+ url = f"{self.base_url}/accounts/profiles/{user_path}"
1627
+ params = None
1628
+ else:
1629
+ project_path = quote(project_id, safe="")
1630
+ url = f"{self.base_url}/accounts/projects/{project_path}/users/{user_path}/profile"
1631
+ params = {"view": view}
1593
1632
 
1594
- async with self._session.get(url, headers=self._get_headers()) as resp:
1633
+ async with self._session.get(
1634
+ url, headers=self._get_headers(), params=params
1635
+ ) as resp:
1595
1636
  await self._raise_for_status(resp)
1596
1637
  return await resp.json()
1597
1638
 
@@ -1604,15 +1645,17 @@ class Meshagent:
1604
1645
  metadata: dict[str, JsonValue] | None = None,
1605
1646
  annotations: dict[str, str] | None = None,
1606
1647
  project_id: str | None = None,
1648
+ inherit: list[UserProfileField] | None = None,
1607
1649
  ) -> Dict[str, Any]:
1608
1650
  """
1609
1651
  Corresponds to: PUT /accounts/profiles/:id
1610
1652
  Omitted fields are preserved; metadata and annotations replace their maps.
1611
- Editing another user or annotations requires the user_profile_editor role
1612
- in project_id, and the target must belong to that project.
1653
+ With project_id, edits project overrides and requires user_profile_editor,
1654
+ including for your own profile. Without a project, edits your global profile.
1613
1655
  Returns a JSON dict with { "ok": True } on success.
1614
1656
  """
1615
- url = f"{self.base_url}/accounts/profiles/{user_id}"
1657
+ user_path = quote(user_id, safe="")
1658
+ url = f"{self.base_url}/accounts/profiles/{user_path}"
1616
1659
  body: dict[str, Any] = {}
1617
1660
  if first_name is not None:
1618
1661
  body["first_name"] = first_name
@@ -1623,7 +1666,12 @@ class Meshagent:
1623
1666
  if annotations is not None:
1624
1667
  body["annotations"] = annotations
1625
1668
  if project_id is not None:
1626
- url += f"?project_id={quote(project_id, safe='')}"
1669
+ project_path = quote(project_id, safe="")
1670
+ url = f"{self.base_url}/accounts/projects/{project_path}/users/{user_path}/profile"
1671
+ if inherit is not None:
1672
+ if project_id is None:
1673
+ raise ValueError("project_id is required to inherit profile fields")
1674
+ body["inherit"] = inherit
1627
1675
 
1628
1676
  async with self._session.put(
1629
1677
  url, headers=self._get_headers(), json=body
@@ -1631,6 +1679,45 @@ class Meshagent:
1631
1679
  await self._raise_for_status(resp)
1632
1680
  return await resp.json()
1633
1681
 
1682
+ async def search_sysadmin_users(
1683
+ self,
1684
+ *,
1685
+ filter: str | None = None,
1686
+ page_size: int = 100,
1687
+ continuation_token: str | None = None,
1688
+ ) -> UserProfilesPage:
1689
+ params = {"page_size": str(page_size)}
1690
+ if filter is not None:
1691
+ params["filter"] = filter
1692
+ if continuation_token is not None:
1693
+ params["continuation_token"] = continuation_token
1694
+ async with self._session.get(
1695
+ f"{self.base_url}/accounts/sysadmin/users",
1696
+ headers=self._get_headers(),
1697
+ params=params,
1698
+ ) as resp:
1699
+ await self._raise_for_status(resp)
1700
+ return UserProfilesPage.model_validate(await resp.json())
1701
+
1702
+ async def get_sysadmin_user_profile(self, user_id: str) -> User:
1703
+ async with self._session.get(
1704
+ f"{self.base_url}/accounts/sysadmin/users/{quote(user_id, safe='')}",
1705
+ headers=self._get_headers(),
1706
+ ) as resp:
1707
+ await self._raise_for_status(resp)
1708
+ return User.model_validate(await resp.json())
1709
+
1710
+ async def update_sysadmin_user_profile(
1711
+ self, user_id: str, *, update: UpdateUserProfileRequest
1712
+ ) -> dict[str, Any]:
1713
+ async with self._session.put(
1714
+ f"{self.base_url}/accounts/sysadmin/users/{quote(user_id, safe='')}",
1715
+ headers=self._get_headers(),
1716
+ json=update.model_dump(mode="json", exclude_unset=True),
1717
+ ) as resp:
1718
+ await self._raise_for_status(resp)
1719
+ return await resp.json()
1720
+
1634
1721
  async def list_projects(self) -> ProjectsPage:
1635
1722
  """
1636
1723
  Corresponds to: GET /accounts/projects
@@ -2764,7 +2764,7 @@ async def test_update_user_profile_supports_partial_metadata_and_editor_context(
2764
2764
  ("put", "http://example.test/accounts/profiles/me", {"metadata": {}}),
2765
2765
  (
2766
2766
  "put",
2767
- "http://example.test/accounts/profiles/user-2?project_id=project-1",
2767
+ "http://example.test/accounts/projects/project-1/users/user-2/profile",
2768
2768
  {
2769
2769
  "first_name": "Grace",
2770
2770
  "last_name": "Hopper",
@@ -2792,3 +2792,81 @@ def test_user_profile_models_preserve_json_metadata_and_string_annotations():
2792
2792
  assert User(id="old-user", email="old@example.test").metadata == {}
2793
2793
  update = UpdateUserProfileRequest.model_validate({"metadata": {}})
2794
2794
  assert update.model_dump(exclude_unset=True) == {"metadata": {}}
2795
+
2796
+
2797
+ @pytest.mark.asyncio
2798
+ @pytest.mark.parametrize("view", ["project", "user", "merged"])
2799
+ async def test_project_profile_reads_use_project_endpoint_and_preserve_raw_fields(view):
2800
+ payload = {
2801
+ "id": "user-1",
2802
+ "email": "a@example.test",
2803
+ "first_name": None,
2804
+ "metadata": {"local": True},
2805
+ }
2806
+ session = _FakeSession([_FakeResponse(status=200, payload=payload)])
2807
+ client = Meshagent(base_url="http://example.test", token="token", session=session)
2808
+ assert (
2809
+ await client.get_user_profile("user-1", project_id="project-1", view=view)
2810
+ == payload
2811
+ )
2812
+ assert session.calls == [
2813
+ (
2814
+ "get",
2815
+ "http://example.test/accounts/projects/project-1/users/user-1/profile",
2816
+ {"view": view},
2817
+ )
2818
+ ]
2819
+
2820
+
2821
+ @pytest.mark.asyncio
2822
+ async def test_project_profile_inheritance_and_global_sysadmin_routes():
2823
+ from meshagent.api.client import UpdateUserProfileRequest
2824
+
2825
+ session = _FakeSession(
2826
+ [
2827
+ _FakeResponse(status=200, payload={"ok": True}),
2828
+ _FakeResponse(
2829
+ status=200, payload={"users": [], "continuation_token": None}
2830
+ ),
2831
+ _FakeResponse(status=200, payload={"ok": True}),
2832
+ ]
2833
+ )
2834
+ client = Meshagent(base_url="http://example.test", token="token", session=session)
2835
+ await client.update_user_profile(
2836
+ "me", project_id="project-1", inherit=["first_name", "metadata"]
2837
+ )
2838
+ await client.search_sysadmin_users(filter="Ada", page_size=10)
2839
+ await client.update_sysadmin_user_profile(
2840
+ "user-1", update=UpdateUserProfileRequest(annotations={})
2841
+ )
2842
+ assert session.calls == [
2843
+ (
2844
+ "put",
2845
+ "http://example.test/accounts/projects/project-1/users/me/profile",
2846
+ {"inherit": ["first_name", "metadata"]},
2847
+ ),
2848
+ (
2849
+ "get",
2850
+ "http://example.test/accounts/sysadmin/users",
2851
+ {"page_size": "10", "filter": "Ada"},
2852
+ ),
2853
+ (
2854
+ "put",
2855
+ "http://example.test/accounts/sysadmin/users/user-1",
2856
+ {"annotations": {}},
2857
+ ),
2858
+ ]
2859
+ with pytest.raises(ValueError):
2860
+ await client.get_user_profile("me", view="project")
2861
+ with pytest.raises(ValueError):
2862
+ await client.update_user_profile("me", inherit=["metadata"])
2863
+
2864
+
2865
+ @pytest.mark.asyncio
2866
+ async def test_sysadmin_profile_access_denials_are_raised():
2867
+ from meshagent.api.client import PermissionDeniedError
2868
+
2869
+ session = _FakeSession([_FakeResponse(status=403, payload={"error": "forbidden"})])
2870
+ client = Meshagent(base_url="http://example.test", token="token", session=session)
2871
+ with pytest.raises(PermissionDeniedError):
2872
+ await client.get_sysadmin_user_profile("user-1")
@@ -0,0 +1 @@
1
+ __version__ = "0.53.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.53.0
3
+ Version: 0.53.2
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -1 +0,0 @@
1
- __version__ = "0.53.0"
File without changes
File without changes
File without changes