-
Notifications
You must be signed in to change notification settings - Fork 4
feat: add list_docs/update_docs/copy_docs methods on NexsetsResource #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| from .requests import DocContainerInput | ||
| from .responses import DocContainer | ||
|
|
||
| __all__ = [ | ||
| "DocContainer", | ||
| "DocContainerInput", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| """Request models for doc containers.""" | ||
|
|
||
| from typing import Optional | ||
|
|
||
| from pydantic import ConfigDict | ||
|
|
||
| from nexla_sdk.models.base import BaseModel | ||
|
|
||
|
|
||
| class DocContainerInput(BaseModel): | ||
| """Writable fields for creating or replacing a documentation entry. | ||
|
|
||
| Server-owned and read-only fields (``id``, ``owner``, ``org``, | ||
| ``public``, ``tags``, ``created_at`` etc.) are silently ignored on | ||
| construction so a full ``DocContainer`` response can be round-tripped | ||
| through this model to drop fields the API does not accept on input. | ||
| """ | ||
|
|
||
| model_config = ConfigDict(extra="ignore") | ||
|
|
||
| name: str | ||
| description: Optional[str] = None | ||
| doc_type: str = "md" | ||
| text: str | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,27 @@ | ||
| from typing import Optional | ||
| from datetime import datetime | ||
| from typing import Any, Dict, List, Optional | ||
|
|
||
| from pydantic import Field | ||
|
|
||
| from nexla_sdk.models.base import BaseModel | ||
| from nexla_sdk.models.common import Organization, Owner | ||
|
|
||
|
|
||
| class DocContainer(BaseModel): | ||
| """Documentation container attached to a Nexla resource (e.g. a nexset).""" | ||
|
|
||
| id: int | ||
| owner: Optional[Owner] = None | ||
| org: Optional[Organization] = None | ||
| name: Optional[str] = None | ||
| description: Optional[str] = None | ||
| doc_type: Optional[str] = None | ||
| public: Optional[bool] = None | ||
| repo_type: Optional[str] = None | ||
| repo_config: Optional[Dict[str, Any]] = None | ||
| text: Optional[str] = None | ||
| access_roles: List[str] = Field(default_factory=list) | ||
| tags: List[str] = Field(default_factory=list) | ||
| copied_from_id: Optional[int] = None | ||
| created_at: Optional[datetime] = None | ||
| updated_at: Optional[datetime] = None |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| from typing import Any, Dict, List, Optional | ||
| from typing import Any, Dict, List, Optional, Union | ||
|
|
||
| from nexla_sdk.models.doc_containers import DocContainer, DocContainerInput | ||
| from nexla_sdk.models.nexsets.requests import ( | ||
| NexsetCopyOptions, | ||
| NexsetCreate, | ||
|
|
@@ -162,3 +163,119 @@ def docs_recommendation(self, set_id: int) -> Dict[str, Any]: | |
| """Generate AI suggestion for Nexset documentation.""" | ||
| path = f"{self._path}/{set_id}/docs/recommendation" | ||
| return self._make_request("POST", path) | ||
|
|
||
| def list_docs(self, set_id: int, expand: bool = True) -> List[DocContainer]: | ||
| """List documentation entries attached to a nexset. | ||
|
|
||
| Each entry is a ``DocContainer`` carrying the rich documentation body | ||
| in its ``text`` field (typically markdown), along with metadata such | ||
| as owner, org, doc type, and timestamps. | ||
|
|
||
| Args: | ||
| set_id: Nexset ID. | ||
| expand: When ``True`` (default), pass ``expand=1`` to the API to | ||
| include nested ``owner`` and ``org`` details in each entry. | ||
|
|
||
| Returns: | ||
| List of ``DocContainer`` instances. Empty list if the nexset has | ||
| no documentation. | ||
|
|
||
| Examples: | ||
| Read the markdown body of the first doc on a nexset:: | ||
|
|
||
| docs = client.nexsets.list_docs(419706) | ||
| if docs: | ||
| print(docs[0].text) | ||
| """ | ||
| path = f"{self._path}/{set_id}/docs" | ||
| params = {"expand": 1} if expand else {} | ||
| response = self._make_request("GET", path, params=params) | ||
| if isinstance(response, list): | ||
| return [DocContainer.model_validate(item) for item in response] | ||
| return [] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Silent Issue: If the API ever returns a non-list shape (e.g. a dict or This pattern is consistent with the rest of the SDK ( Generated by Claude Code |
||
|
|
||
| def update_docs( | ||
| self, | ||
| set_id: int, | ||
| docs: List[Union[DocContainerInput, Dict[str, Any]]], | ||
| ) -> List[DocContainer]: | ||
| """Replace all documentation entries on a nexset. | ||
|
|
||
| This call uses **replace-all** semantics: the provided list becomes | ||
| the entire set of docs for the nexset. Any existing entries that are | ||
| not in the list are removed. | ||
|
|
||
| Args: | ||
| set_id: Nexset ID. | ||
| docs: New documentation entries. Each item may be a | ||
| ``DocContainerInput`` instance or a plain dict with the same | ||
| shape (``name``, ``text``, ``doc_type``, optional | ||
| ``description``). Plain dicts are accepted to support | ||
| generic pass-through callers (e.g. the MCP server, raw | ||
| scripts). | ||
|
|
||
| Returns: | ||
| The new list of ``DocContainer`` entries as parsed from the | ||
| server response. | ||
|
Comment on lines
+217
to
+219
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Issue: The Suggestion: Dedent Args:
set_id: Nexset ID.
docs: ...
Returns:
The new list of ``DocContainer`` entries as parsed from the
server response.Generated by Claude Code |
||
|
|
||
| Examples: | ||
| Replace a nexset's docs with a single markdown entry:: | ||
|
|
||
| from nexla_sdk.models import DocContainerInput | ||
|
|
||
| client.nexsets.update_docs( | ||
| 419706, | ||
| [DocContainerInput( | ||
| name="Overview", | ||
| description="High-level description", | ||
| text="# Overview\\n\\n...", | ||
| )], | ||
| ) | ||
| """ | ||
| path = f"{self._path}/{set_id}/docs" | ||
| serialized = [self._serialize_data(d) for d in docs] | ||
| response = self._make_request( | ||
| "POST", path, json={"docs": serialized} | ||
| ) | ||
| if isinstance(response, list): | ||
| return [DocContainer.model_validate(item) for item in response] | ||
| return [] | ||
|
|
||
| def copy_docs(self, src_id: int, dst_id: int) -> List[DocContainer]: | ||
| """Copy all documentation entries from one nexset to another. | ||
|
|
||
| Reads the source's docs, strips server-owned fields (id, owner, org, | ||
| access_roles, copied_from_id, created_at, updated_at), and writes | ||
| them to the destination using ``update_docs`` (replace-all | ||
| semantics). Existing docs on the destination are overwritten. | ||
|
|
||
| If the source has no docs, this is a no-op: the destination is left | ||
| unchanged and an empty list is returned. | ||
|
|
||
| Note: only fields present on ``DocContainerInput`` are carried over. | ||
| Any new server-side doc fields will be ignored until | ||
| ``DocContainerInput`` is extended. | ||
|
|
||
| Args: | ||
| src_id: Source nexset ID (docs are read from here). | ||
| dst_id: Destination nexset ID (docs are written here). | ||
|
|
||
| Returns: | ||
| The destination's new list of ``DocContainer`` entries, or an | ||
| empty list if the source had no docs. | ||
|
|
||
| Examples: | ||
| Copy docs from one nexset to another:: | ||
|
|
||
| client.nexsets.copy_docs(src_id=419706, dst_id=419800) | ||
| """ | ||
| source_docs = self.list_docs(src_id) | ||
| if not source_docs: | ||
| return [] | ||
| payload = [ | ||
| DocContainerInput.model_validate( | ||
| doc.model_dump(exclude_none=True) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Issue: Suggestion: Drop valid_docs = [doc for doc in source_docs if doc.text is not None]
if not valid_docs:
return []
payload = [
DocContainerInput.model_validate(doc.model_dump())
for doc in valid_docs
]Generated by Claude Code |
||
| ) | ||
| for doc in source_docs | ||
| ] | ||
| return self.update_docs(dst_id, payload) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Docstring doesn't explain why
repo_type/repo_configare excludedIssue: The class docstring lists
id,owner,org,public,tags, andcreated_atas intentionally excluded, but doesn't mentionrepo_typeorrepo_config. The PR description documents the reasoning forpublicandtagsspecifically; a future maintainer seeing a 400 from a POST withrepo_typeincluded will have no breadcrumb here.Suggestion: Add a brief note (matching the existing PR description rationale) about
repo_type/repo_config:Generated by Claude Code