From 485eb4b8402ffd95833748d376c806f27b6fbec8 Mon Sep 17 00:00:00 2001 From: "bishoy.sameh@progressiosolutions.com" Date: Thu, 30 Oct 2025 17:52:31 +0300 Subject: [PATCH 1/3] feat: add columns parameter to update_user methods for selective field updates Add support for the 'columns' parameter in user update operations to enable selective field updates, which is particularly useful for updating specific fields like roles without affecting other user attributes. Changes: - Add 'columns' parameter to update_user() method (sync and async) - Add 'columns' parameter to update_user_by_id() method (sync) - Add 'columns' parameter to modify_user() and modify_user_by_id() methods - Update method signatures to include Optional[List[str]] for columns - Add comprehensive docstrings explaining the columns parameter usage The columns parameter accepts a list of field names to update: sdk.update_user(user, columns=['roles', 'email']) This sends 'columns=roles,email' as a query parameter to the Casdoor API, allowing for more granular control over user updates and reducing the risk of unintended field modifications. Implements selective field update pattern supported by Casdoor API. Maintains backward compatibility - when columns=None, all fields are updated. --- src/casdoor/async_main.py | 28 ++++++++++++++++++++++++--- src/casdoor/user.py | 40 ++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/casdoor/async_main.py b/src/casdoor/async_main.py index 6732e1d..a42df70 100644 --- a/src/casdoor/async_main.py +++ b/src/casdoor/async_main.py @@ -419,8 +419,23 @@ async def get_user_count(self, is_online: bool = None) -> int: count = await session.get(path, headers=self.headers, params=params) return count["data"] - async def modify_user(self, method: str, user: User, params=None) -> Dict: + async def modify_user(self, method: str, user: User, params=None, columns: Optional[List[str]] = None) -> Dict: + """ + Modify user with optional columns parameter for selective field updates. + + :param method: the operation method (add-user, update-user, delete-user) + :param user: a User object that contains user's info + :param params: additional parameters + :param columns: optional list of column names to update (e.g., ["roles", "email"]) + """ path = f"/api/{method}" + + # Add columns to params if provided + if columns: + if params is None: + params = {} + params["columns"] = ",".join(columns) + async with self._session as session: return await session.post(path, params=params, headers=self.headers, json=user.to_dict()) @@ -428,9 +443,16 @@ async def add_user(self, user: User) -> Dict: response = await self.modify_user("add-user", user) return response - async def update_user(self, user: User) -> Dict: + async def update_user(self, user: User, columns: Optional[List[str]] = None) -> Dict: + """ + Update a user in Casdoor. + + :param user: a User object that contains user's info + :param columns: optional list of column names to update (e.g., ["roles", "email"]) + If not provided, all fields will be updated + """ params = {"id": f"{user.owner}/{user.name}"} - response = await self.modify_user("update-user", user, params) + response = await self.modify_user("update-user", user, params, columns) return response async def delete_user(self, user: User) -> Dict: diff --git a/src/casdoor/user.py b/src/casdoor/user.py index 63dd153..3634399 100644 --- a/src/casdoor/user.py +++ b/src/casdoor/user.py @@ -368,20 +368,26 @@ def get_user_count(self, is_online: bool = None) -> int: count = response.get("data") return count - def modify_user(self, method: str, user: User) -> Dict: + def modify_user(self, method: str, user: User, columns: Optional[List[str]] = None) -> Dict: """ modifyUser is an encapsulation of user CUD(Create, Update, Delete) operations. possible actions are `add-user`, `update-user`, `delete-user`, + + :param method: the operation method (add-user, update-user, delete-user) + :param user: a User object that contains user's info + :param columns: optional list of column names to update (e.g., ["roles", "email"]) """ id = user.get_id() - return self.modify_user_by_id(method, id, user) + return self.modify_user_by_id(method, id, user, columns) - def modify_user_by_id(self, method: str, id: str, user: User) -> Dict: + def modify_user_by_id(self, method: str, id: str, user: User, columns: Optional[List[str]] = None) -> Dict: """ Modify the user from Casdoor providing the ID. + :param method: the operation method (add-user, update-user, delete-user) :param id: the id ( owner/name ) of the user :param user: a User object that contains user's info + :param columns: optional list of column names to update (e.g., ["roles", "email"]) """ url = self.endpoint + f"/api/{method}" @@ -391,6 +397,11 @@ def modify_user_by_id(self, method: str, id: str, user: User) -> Dict: "clientId": self.client_id, "clientSecret": self.client_secret, } + + # Add columns parameter if provided (for selective field updates) + if columns: + params["columns"] = ",".join(columns) + user_info = json.dumps(user.to_dict()) r = requests.post(url, params=params, data=user_info) response = r.json() @@ -402,12 +413,27 @@ def add_user(self, user: User) -> Dict: response = self.modify_user("add-user", user) return response - def update_user(self, user: User) -> Dict: - response = self.modify_user("update-user", user) + def update_user(self, user: User, columns: Optional[List[str]] = None) -> Dict: + """ + Update a user in Casdoor. + + :param user: a User object that contains user's info + :param columns: optional list of column names to update (e.g., ["roles", "email"]) + If not provided, all fields will be updated + """ + response = self.modify_user("update-user", user, columns) return response - def update_user_by_id(self, id: str, user: User) -> Dict: - response = self.modify_user_by_id("update-user", id, user) + def update_user_by_id(self, id: str, user: User, columns: Optional[List[str]] = None) -> Dict: + """ + Update a user in Casdoor by ID. + + :param id: the id ( owner/name ) of the user + :param user: a User object that contains user's info + :param columns: optional list of column names to update (e.g., ["roles", "email"]) + If not provided, all fields will be updated + """ + response = self.modify_user_by_id("update-user", id, user, columns) return response def delete_user(self, user: User) -> Dict: From b396eea1868592dfd63fcffd34983b3cc18bca6a Mon Sep 17 00:00:00 2001 From: "bishoy.sameh@progressiosolutions.com" Date: Thu, 30 Oct 2025 18:05:17 +0300 Subject: [PATCH 2/3] style: apply black and ruff formatting --- .DS_Store | Bin 0 -> 6148 bytes src/.DS_Store | Bin 0 -> 6148 bytes src/casdoor/async_main.py | 8 ++++---- src/casdoor/user.py | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 .DS_Store create mode 100644 src/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1e1ead8ea51b2c245a21aaf494dfd6c0fff744f1 GIT binary patch literal 6148 zcmeHK%`O8`6h2p5hHe@mv78k<(WTvJiU(*ctk55=q%{rNmB_5D>}@>*4CQI3c_C}Vm8%O}Eq)`W~$%M?)Y zGn(~$sS>u@iAXUB1H!;xWPrcjG|f|;cId$QelwG^abGJe7R$}B44e0ScYXS>e!1lO zt6%!tjo9AcMM$Nwx4P7z4waqil&K^m;yqkUuO6ST=pH^irxgx586M^IqxV0`qMto< zvW?#cRcH&^pqkqkJ)y55k@5cez3}DHa5OY}(C7EjZodo6Uk3elsYbihb$dKnbG=Sq zXLGlWB8kAKS5C(*ScLsQW@S%)>#mJ!EI%z-1t1ZgV{UmxrJK-6Xja z284ls#ehl|OT_|S$kA&mGf*w?`FsHkEJg-lfyf^L MO@l1Lz^^j!3gK3*g8%>k literal 0 HcmV?d00001 diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e4e106bb8b58517427354b65876778e984e01482 GIT binary patch literal 6148 zcmeHKy-ve05I&byEn?}&f|OV2#2Tsq-OGZ+0BsN;HF8DE9u^*mmtbaNQiq+=Ey4DyahkscR!qv6L%75Xtyyx9w z+4{T>sYh$N#eCMZ(!FG(;ryyTx;c-p=gG_KnapD&zKB0_X!oe)51*Xh#Tjr0oPk|u z05zLsaG>a|GvEw30~G^&J|s}a)UZ^PqXUgx0f1eYMbMXjIAFpCU}{(@!U72z3e?bG zSB#+HuqWo18kUM0PC+u`J(fAx4JAl+*b{N5kScoX3^)UI2KMxE$o2np`~ANj}r2n)o21cU}} JoPj@O;2TKHO>+PM literal 0 HcmV?d00001 diff --git a/src/casdoor/async_main.py b/src/casdoor/async_main.py index a42df70..2574c27 100644 --- a/src/casdoor/async_main.py +++ b/src/casdoor/async_main.py @@ -422,20 +422,20 @@ async def get_user_count(self, is_online: bool = None) -> int: async def modify_user(self, method: str, user: User, params=None, columns: Optional[List[str]] = None) -> Dict: """ Modify user with optional columns parameter for selective field updates. - + :param method: the operation method (add-user, update-user, delete-user) :param user: a User object that contains user's info :param params: additional parameters :param columns: optional list of column names to update (e.g., ["roles", "email"]) """ path = f"/api/{method}" - + # Add columns to params if provided if columns: if params is None: params = {} params["columns"] = ",".join(columns) - + async with self._session as session: return await session.post(path, params=params, headers=self.headers, json=user.to_dict()) @@ -446,7 +446,7 @@ async def add_user(self, user: User) -> Dict: async def update_user(self, user: User, columns: Optional[List[str]] = None) -> Dict: """ Update a user in Casdoor. - + :param user: a User object that contains user's info :param columns: optional list of column names to update (e.g., ["roles", "email"]) If not provided, all fields will be updated diff --git a/src/casdoor/user.py b/src/casdoor/user.py index 3634399..b730368 100644 --- a/src/casdoor/user.py +++ b/src/casdoor/user.py @@ -372,7 +372,7 @@ def modify_user(self, method: str, user: User, columns: Optional[List[str]] = No """ modifyUser is an encapsulation of user CUD(Create, Update, Delete) operations. possible actions are `add-user`, `update-user`, `delete-user`, - + :param method: the operation method (add-user, update-user, delete-user) :param user: a User object that contains user's info :param columns: optional list of column names to update (e.g., ["roles", "email"]) @@ -397,11 +397,11 @@ def modify_user_by_id(self, method: str, id: str, user: User, columns: Optional[ "clientId": self.client_id, "clientSecret": self.client_secret, } - + # Add columns parameter if provided (for selective field updates) if columns: params["columns"] = ",".join(columns) - + user_info = json.dumps(user.to_dict()) r = requests.post(url, params=params, data=user_info) response = r.json() @@ -416,7 +416,7 @@ def add_user(self, user: User) -> Dict: def update_user(self, user: User, columns: Optional[List[str]] = None) -> Dict: """ Update a user in Casdoor. - + :param user: a User object that contains user's info :param columns: optional list of column names to update (e.g., ["roles", "email"]) If not provided, all fields will be updated @@ -427,7 +427,7 @@ def update_user(self, user: User, columns: Optional[List[str]] = None) -> Dict: def update_user_by_id(self, id: str, user: User, columns: Optional[List[str]] = None) -> Dict: """ Update a user in Casdoor by ID. - + :param id: the id ( owner/name ) of the user :param user: a User object that contains user's info :param columns: optional list of column names to update (e.g., ["roles", "email"]) From 59dd1dd71f94eb86bd84db30e363fd6d1d39ee44 Mon Sep 17 00:00:00 2001 From: "bishoy.sameh@progressiosolutions.com" Date: Thu, 30 Oct 2025 18:09:52 +0300 Subject: [PATCH 3/3] chore: remove DS_Store files --- .DS_Store | Bin 6148 -> 0 bytes src/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 src/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 1e1ead8ea51b2c245a21aaf494dfd6c0fff744f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%`O8`6h2p5hHe@mv78k<(WTvJiU(*ctk55=q%{rNmB_5D>}@>*4CQI3c_C}Vm8%O}Eq)`W~$%M?)Y zGn(~$sS>u@iAXUB1H!;xWPrcjG|f|;cId$QelwG^abGJe7R$}B44e0ScYXS>e!1lO zt6%!tjo9AcMM$Nwx4P7z4waqil&K^m;yqkUuO6ST=pH^irxgx586M^IqxV0`qMto< zvW?#cRcH&^pqkqkJ)y55k@5cez3}DHa5OY}(C7EjZodo6Uk3elsYbihb$dKnbG=Sq zXLGlWB8kAKS5C(*ScLsQW@S%)>#mJ!EI%z-1t1ZgV{UmxrJK-6Xja z284ls#ehl|OT_|S$kA&mGf*w?`FsHkEJg-lfyf^L MO@l1Lz^^j!3gK3*g8%>k diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index e4e106bb8b58517427354b65876778e984e01482..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKy-ve05I&byEn?}&f|OV2#2Tsq-OGZ+0BsN;HF8DE9u^*mmtbaNQiq+=Ey4DyahkscR!qv6L%75Xtyyx9w z+4{T>sYh$N#eCMZ(!FG(;ryyTx;c-p=gG_KnapD&zKB0_X!oe)51*Xh#Tjr0oPk|u z05zLsaG>a|GvEw30~G^&J|s}a)UZ^PqXUgx0f1eYMbMXjIAFpCU}{(@!U72z3e?bG zSB#+HuqWo18kUM0PC+u`J(fAx4JAl+*b{N5kScoX3^)UI2KMxE$o2np`~ANj}r2n)o21cU}} JoPj@O;2TKHO>+PM