Conversation
Harshal6927
commented
Dec 5, 2025
- Verify service docs
docs/usage/services.rst
Outdated
| title: str | None = None | ||
| content: str | None = None | ||
| published: bool | None = None |
There was a problem hiding this comment.
You'll need to use Optional since we still need to show 3.9 support here.
docs/usage/services.rst
Outdated
| data = data_dict | ||
|
|
||
| post = await self.to_model(data, "create") |
There was a problem hiding this comment.
| data = data_dict | |
| post = await self.to_model(data, "create") | |
| post = await self.to_model(data_dict, "create") |
docs/usage/services.rst
Outdated
| # Override update behavior to handle tags | ||
| async def update( | ||
| self, | ||
| data: ModelDictT[Post], |
There was a problem hiding this comment.
We should probably make the override mirror the service spec properly so there's no linting issues on the copy/paste
docs/usage/services.rst
Outdated
| async def update( | ||
| self, | ||
| data: ModelDictT[Post], | ||
| item_id: Any | None = None, | ||
| **kwargs, | ||
| **kwargs: Any, | ||
| ) -> Post: | ||
| """Update a post with tags, if provided.""" | ||
| tags_updated: list[str] = [] | ||
| if isinstance(data, dict): | ||
| tags_updated.extend(data.pop("tags", None) or []) | ||
| data["id"] = item_id | ||
| data = await self.to_model(data, "update") | ||
| existing_tags = [tag.name for tag in data.tags] | ||
| tags_to_remove = [tag for tag in data.tags if tag.name not in tags_updated] | ||
| tags_to_add = [tag for tag in tags_updated if tag not in existing_tags] | ||
| data_dict = schema_dump(data) | ||
| tags_updated = data_dict.pop("tags", []) | ||
|
|
||
| # Determine the effective item_id - either from parameter or from the data itself | ||
| effective_item_id = item_id if item_id is not None else data_dict.get("id") | ||
|
|
||
| # Get existing post to access current tags | ||
| if effective_item_id is not None: | ||
| existing_post = await self.get(effective_item_id) | ||
| existing_tags = [tag.name for tag in existing_post.tags] | ||
| else: | ||
| existing_tags = [] | ||
|
|
||
| post = await self.to_model(data_dict, "update") | ||
|
|
||
| if tags_updated: | ||
| tags_updated = [schema_dump(tag) for tag in tags_updated] | ||
| # Determine tags to remove and add | ||
| tags_to_remove = [tag for tag in post.tags if tag.name not in [t["name"] for t in tags_updated]] | ||
| tags_to_add = [tag for tag in tags_updated if tag["name"] not in existing_tags] | ||
|
|
||
| for tag_rm in tags_to_remove: | ||
| data.tags.remove(tag_rm) | ||
| data.tags.extend( | ||
| post.tags.remove(tag_rm) | ||
|
|
||
| post.tags.extend( | ||
| [ | ||
| await Tag.as_unique_async(self.repository.session, name=tag_text, slug=slugify(tag_text)) | ||
| for tag_text in tags_to_add | ||
| await Tag.as_unique_async(self.repository.session, name=tag["name"], slug=slugify(tag["name"])) | ||
| for tag in tags_to_add | ||
| ], | ||
| ) | ||
| return await super().update( | ||
| data=data, | ||
| item_id=item_id, | ||
| **kwargs, | ||
| ) |
There was a problem hiding this comment.
This needs to be remove or modified to mirror exactly how the TeamService works in fullstack. Here's the service for reference:
class TeamService(service.SQLAlchemyAsyncRepositoryService[m.Team]):
"""Team Service."""
class Repo(repository.SQLAlchemyAsyncSlugRepository[m.Team]):
"""Team Repository."""
model_type = m.Team
repository_type = Repo
match_fields = ["name"]
async def to_model_on_create(self, data: ModelDictT[m.Team]) -> ModelDictT[m.Team]:
data = service.schema_dump(data)
data = await self._populate_slug(data)
return await self._populate_with_owner_and_tags(data, "create")
async def to_model_on_update(self, data: ModelDictT[m.Team]) -> ModelDictT[m.Team]:
data = service.schema_dump(data)
data = await self._populate_slug(data)
return await self._populate_with_owner_and_tags(data, "update")
async def to_model_on_upsert(self, data: ModelDictT[m.Team]) -> ModelDictT[m.Team]:
data = service.schema_dump(data)
data = await self._populate_slug(data)
return await self._populate_with_owner_and_tags(data, "upsert")
@staticmethod
def can_view_all(user: m.User) -> bool:
return bool(
user.is_superuser
or any(
assigned_role.role.name
for assigned_role in user.roles
if assigned_role.role.name == constants.SUPERUSER_ACCESS_ROLE
),
)
async def _populate_slug(self, data: ModelDictT[m.Team]) -> ModelDictT[m.Team]:
if service.is_dict_without_field(data, "slug") and service.is_dict_with_field(data, "name"):
data["slug"] = await self.repository.get_available_slug(data["name"])
return data
async def _populate_with_owner_and_tags(
self,
data: ModelDictT[m.Team],
operation: str | None,
) -> ModelDictT[m.Team]:
if service.is_dict(data):
owner_id: UUID | None = data.pop("owner_id", None)
owner: m.User | None = data.pop("owner", None)
input_tags: list[str] = data.pop("tags", [])
data["id"] = data.get("id", uuid4())
data = await super().to_model(data)
if input_tags:
existing_tags = [tag.name for tag in data.tags]
tags_to_remove = [tag for tag in data.tags if tag.name not in input_tags]
tags_to_add = [tag for tag in input_tags if tag not in existing_tags]
for tag_rm in tags_to_remove:
data.tags.remove(tag_rm)
data.tags.extend(
[
await m.Tag.as_unique_async(self.repository.session, name=tag_text, slug=slugify(tag_text))
for tag_text in tags_to_add
],
)
if owner or owner_id:
for member in data.members:
if member.user_id == owner.id if owner is not None else owner_id and not member.is_owner:
member.is_owner = True
member.role = m.TeamRoles.ADMIN
break
else:
owner_data: dict[str, Any] = {"user": owner} if owner else {"user_id": owner_id}
data.members.append(m.TeamMember(**owner_data, role=m.TeamRoles.ADMIN, is_owner=True))
return data
docs/usage/services.rst
Outdated
| async def publish_post( | ||
| self, | ||
| post_id: int, | ||
| publish: bool = True, | ||
| ) -> PostResponse: | ||
| """Publish or unpublish a post with timestamp.""" | ||
| data = PostUpdate( | ||
| published=publish, | ||
| published_at=datetime.datetime.utcnow() if publish else None, | ||
| ) | ||
| post = await self.repository.update( | ||
| """Publish or unpublish a post.""" | ||
| post = await self.update( | ||
| item_id=post_id, | ||
| data=data, | ||
| data={"published": publish}, | ||
| auto_commit=True, | ||
| ) | ||
| return self.to_schema(post, schema_type=PostResponse) |
There was a problem hiding this comment.
What do we want to demonstrate here exactly? this looks like all the default behavior you can do out of the box.
We should consider removing it to reduce confusion if there's nothing new.
docs/usage/services.rst
Outdated
| post_data = post_service.to_schema(rows[0][0], schema_type=PostSchema) | ||
| posts_list = post_service.to_schema([row[0] for row in rows], schema_type=PostSchema) |
There was a problem hiding this comment.
I'm not sure 2 level of arrays is the best thing to demo here.
|
Co-authored-by: Cody Fincher <204685+cofin@users.noreply.github.com> Signed-off-by: Harshal Laheri <73422191+Harshal6927@users.noreply.github.com>
Co-authored-by: Cody Fincher <204685+cofin@users.noreply.github.com> Signed-off-by: Harshal Laheri <73422191+Harshal6927@users.noreply.github.com>
Co-authored-by: Cody Fincher <204685+cofin@users.noreply.github.com> Signed-off-by: Harshal Laheri <73422191+Harshal6927@users.noreply.github.com>
| content: str | ||
| published: bool | ||
|
|
||
| model_config = {"from_attributes": True} |
There was a problem hiding this comment.
I don't think you need this any more - you can double check and remove if so.
Co-authored-by: Cody Fincher <204685+cofin@users.noreply.github.com> Signed-off-by: Harshal Laheri <73422191+Harshal6927@users.noreply.github.com>
| result = await db_session.execute(select(Post)) | ||
| posts = result.scalars().all() # Returns list of Post models | ||
| posts_list = post_service.to_schema(posts, schema_type=PostSchema) |
There was a problem hiding this comment.
What's the intent here? I think we should use the service here instead of showing "raw" sqlalchemy
| result = await db_session.execute(select(Post)) | ||
| row_mapping = result.mappings().first() | ||
| mapping_data = post_service.to_schema(row_mapping["Post"], schema_type=PostSchema) |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #619 +/- ##
==========================================
- Coverage 79.06% 79.05% -0.02%
==========================================
Files 99 99
Lines 7915 7915
Branches 1073 1073
==========================================
- Hits 6258 6257 -1
Misses 1315 1315
- Partials 342 343 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|


