slack-objects 0.0.8__tar.gz → 0.0.9__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 (72) hide show
  1. {slack_objects-0.0.8 → slack_objects-0.0.9}/PKG-INFO +1 -1
  2. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/_version.py +3 -3
  3. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/users.py +27 -37
  4. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/PKG-INFO +1 -1
  5. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/users_smoke_test.py +2 -0
  6. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/users_unit_test.py +45 -2
  7. {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/copilot-instructions.md +0 -0
  8. {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/workflows/ci.yml +0 -0
  9. {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/workflows/publish-pypi.yml +0 -0
  10. {slack_objects-0.0.8 → slack_objects-0.0.9}/.github/workflows/publish-testpypi.yml +0 -0
  11. {slack_objects-0.0.8 → slack_objects-0.0.9}/.gitignore +0 -0
  12. {slack_objects-0.0.8 → slack_objects-0.0.9}/.vs/VSWorkspaceState.json +0 -0
  13. {slack_objects-0.0.8 → slack_objects-0.0.9}/.vs/slnx.sqlite +0 -0
  14. {slack_objects-0.0.8 → slack_objects-0.0.9}/LICENSE +0 -0
  15. {slack_objects-0.0.8 → slack_objects-0.0.9}/README.md +0 -0
  16. {slack_objects-0.0.8 → slack_objects-0.0.9}/pyproject.toml +0 -0
  17. {slack_objects-0.0.8 → slack_objects-0.0.9}/pytest.ini +0 -0
  18. {slack_objects-0.0.8 → slack_objects-0.0.9}/setup.cfg +0 -0
  19. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/__init__.py +0 -0
  20. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/api_caller.py +0 -0
  21. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/base.py +0 -0
  22. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/client.py +0 -0
  23. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/config.py +0 -0
  24. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/conversations.py +0 -0
  25. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/files.py +0 -0
  26. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/idp_groups.py +0 -0
  27. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/messages.py +0 -0
  28. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/rate_limits.py +0 -0
  29. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/scim_base.py +0 -0
  30. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/usergroups.py +0 -0
  31. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects/workspaces.py +0 -0
  32. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/SOURCES.txt +0 -0
  33. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/dependency_links.txt +0 -0
  34. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/requires.txt +0 -0
  35. {slack_objects-0.0.8 → slack_objects-0.0.9}/src/slack_objects.egg-info/top_level.txt +0 -0
  36. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Examples/examples_config.example.json +0 -0
  37. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Examples/users_search_by_email_example.py +0 -0
  38. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Examples/users_test_AzureKeyVault.py +0 -0
  39. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/conftest_live.py +0 -0
  40. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/run_all_scim_users_live_tests.py +0 -0
  41. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_idp_groups_live.py +0 -0
  42. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_create_live.py +0 -0
  43. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_deactivate_live.py +0 -0
  44. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_input_validation_live.py +0 -0
  45. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_make_guest_live.py +0 -0
  46. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_reactivate_live.py +0 -0
  47. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/SCIM/test_scim_users_update_attribute_live.py +0 -0
  48. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/_smoke_harness.py +0 -0
  49. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/api_caller_smoke_test.py +0 -0
  50. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/conversations_smoke_test.py +0 -0
  51. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/files_smoke_test.py +0 -0
  52. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/idp_groups_smoke_test.py +0 -0
  53. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/messages_smoke_test.py +0 -0
  54. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/run_all_smoke.py +0 -0
  55. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/security_smoke_test.py +0 -0
  56. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/usergroups_smoke_test.py +0 -0
  57. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Smoke/workspaces_smoke_test.py +0 -0
  58. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/config_unit_test.py +0 -0
  59. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/conversations_unit_test.py +0 -0
  60. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/files_unit_test.py +0 -0
  61. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/idp_groups_unit_test.py +0 -0
  62. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/messages_unit_test.py +0 -0
  63. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/rate_limits_unit_test.py +0 -0
  64. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/usergroups_test.py +0 -0
  65. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/UnitTests/workspaces_unit_test.py +0 -0
  66. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/conftest_live.py +0 -0
  67. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/run_all_users_live_tests.py +0 -0
  68. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_admin_live.py +0 -0
  69. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_classification_live.py +0 -0
  70. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_input_validation_live.py +0 -0
  71. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/Users/test_users_read_live.py +0 -0
  72. {slack_objects-0.0.8 → slack_objects-0.0.9}/tests/live_test_config.example.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slack-objects
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: Opinionated, testable Python wrappers for Slack’s Web, Admin, and SCIM APIs, organized by object domain (users, conversations, messages, files, workspaces, and IdP groups). Designed for automation and administration workflows.
5
5
  Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
6
6
  Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.8'
32
- __version_tuple__ = version_tuple = (0, 0, 8)
31
+ __version__ = version = '0.0.9'
32
+ __version_tuple__ = version_tuple = (0, 0, 9)
33
33
 
34
- __commit_id__ = commit_id = 'g41dd54231'
34
+ __commit_id__ = commit_id = 'g82c79f1f4'
@@ -268,7 +268,11 @@ class Users(ScimMixin, SlackObjectBase):
268
268
  if not uid:
269
269
  raise ValueError("is_active_scim requires user_id (passed or bound)")
270
270
 
271
- scim_resp = self._scim_request(path=f"Users/{uid}", method="GET")
271
+ scim_resp = self._scim_request(
272
+ path=f"Users/{uid}",
273
+ method="GET",
274
+ raise_for_status=False,
275
+ )
272
276
  if not scim_resp.ok:
273
277
  return False
274
278
  return bool(scim_resp.data.get("active", False))
@@ -511,15 +515,27 @@ class Users(ScimMixin, SlackObjectBase):
511
515
 
512
516
  identifier = identifier.strip()
513
517
 
518
+ # Normalize: strip leading '@' so "@U1234" falls into the user-ID
519
+ # path and "@alice" falls into the username path — no special stage needed.
520
+ if identifier.startswith("@"):
521
+ identifier = identifier.removeprefix("@")
522
+
514
523
  # ── 1. Slack user ID ──────────────────────────────────────
515
524
  if self._looks_like_user_id(identifier):
516
- resp = self.get_user_info(identifier)
517
- if resp.get("ok"):
518
- return identifier
525
+ try:
526
+ resp = self.get_user_info(identifier)
527
+ if resp.get("ok"):
528
+ return identifier
529
+ except SlackApiError:
530
+ pass # expected for deactivated / not-found users
519
531
 
520
532
  # Slow path: SCIM (active + deactivated)
521
533
  self.logger.info("Web API miss for %s — falling back to SCIM lookup", identifier)
522
- scim_resp = self._scim_request(path=f"Users/{identifier}", method="GET")
534
+ scim_resp = self._scim_request(
535
+ path=f"Users/{identifier}",
536
+ method="GET",
537
+ raise_for_status=False,
538
+ )
523
539
  if scim_resp.ok and scim_resp.data.get("id"):
524
540
  return scim_resp.data["id"]
525
541
 
@@ -528,12 +544,9 @@ class Users(ScimMixin, SlackObjectBase):
528
544
  # ── 2. Email address ──────────────────────────────────────
529
545
  if EMAIL_RE.match(identifier):
530
546
  # Fast path: Web API (active users only)
531
- try:
532
- uid = self.get_user_id_from_email(identifier)
533
- if uid:
534
- return uid
535
- except SlackApiError:
536
- pass # expected for deactivated / not-found users
547
+ uid = self.get_user_id_from_email(identifier)
548
+ if uid:
549
+ return uid
537
550
 
538
551
  # Slow path: SCIM (active + deactivated)
539
552
  self.logger.info("Web API miss for %s — falling back to SCIM search", identifier)
@@ -543,32 +556,9 @@ class Users(ScimMixin, SlackObjectBase):
543
556
 
544
557
  raise LookupError(f"No user found for email: {identifier}")
545
558
 
546
- # ── 3. @username handle ───────────────────────────────────
547
- if identifier.startswith("@"):
548
- stripped = identifier.removeprefix("@")
549
-
550
- # The caller may have passed "@U1234" — recognise the ID
551
- # and verify via the cheaper Web API instead of SCIM.
552
- if self._looks_like_user_id(stripped):
553
- resp = self.get_user_info(stripped)
554
- if resp.get("ok"):
555
- return stripped
556
-
557
- # Slow path: SCIM (active + deactivated)
558
- self.logger.info("Web API miss for %s — falling back to SCIM lookup", stripped)
559
- scim_resp = self._scim_request(path=f"Users/{stripped}", method="GET")
560
- if scim_resp.ok and scim_resp.data.get("id"):
561
- return scim_resp.data["id"]
562
-
563
- raise LookupError(f"No user found for user ID: {stripped}")
564
-
565
- uid = self._first_scim_user_id(self.scim_search_user_by_username(stripped))
566
- if uid:
567
- return uid
568
-
569
- raise LookupError(f"No user found for username: @{stripped}")
570
-
571
- # ── 4. Bare string — try as username via SCIM ─────────────
559
+ # ── 3. Bare string / username — SCIM only ─────────────────
560
+ # No Web API endpoint exists for username lookup; SCIM is the
561
+ # only option. This also handles "@alice" (already stripped above).
572
562
  uid = self._first_scim_user_id(self.scim_search_user_by_username(identifier))
573
563
  if uid:
574
564
  return uid
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slack-objects
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: Opinionated, testable Python wrappers for Slack’s Web, Admin, and SCIM APIs, organized by object domain (users, conversations, messages, files, workspaces, and IdP groups). Designed for automation and administration workflows.
5
5
  Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
6
6
  Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
@@ -169,6 +169,8 @@ def main() -> None:
169
169
 
170
170
  # Identifier resolution
171
171
  CallSpec("resolve_user_id(user_id)", lambda: bound.resolve_user_id("U1")),
172
+ CallSpec("resolve_user_id(email)", lambda: bound.resolve_user_id("found@example.com")),
173
+ CallSpec("resolve_user_id(@username)", lambda: bound.resolve_user_id("@testuser")),
172
174
  ]
173
175
 
174
176
  run_smoke("Users smoke (all public methods)", specs)
@@ -3,6 +3,7 @@ from typing import Any, Dict, Optional
3
3
  from unittest.mock import MagicMock
4
4
 
5
5
  import pytest
6
+ from slack_sdk.errors import SlackApiError
6
7
 
7
8
  from slack_objects.client import SlackObjectsClient
8
9
  from slack_objects.config import SlackObjectsConfig, RateTier, USER_ID_RE
@@ -182,6 +183,9 @@ class TestResolveUserId:
182
183
  )
183
184
  with pytest.raises(LookupError, match="No user found for user ID"):
184
185
  users.resolve_user_id("U00GHOST")
186
+ users._scim_request.assert_called_once_with(
187
+ path="Users/U00GHOST", method="GET", raise_for_status=False,
188
+ )
185
189
 
186
190
  def test_email_active_user(self):
187
191
  """Active user email resolves via Web API (fast path)."""
@@ -221,7 +225,7 @@ class TestResolveUserId:
221
225
  scim_resp = ScimResponse(ok=True, status_code=200, data={"Resources": []}, text="")
222
226
  users.scim_search_user_by_username = MagicMock(return_value=scim_resp)
223
227
 
224
- with pytest.raises(LookupError, match="No user found for username"):
228
+ with pytest.raises(LookupError, match="No user found for identifier"):
225
229
  users.resolve_user_id("@ghost")
226
230
 
227
231
  def test_bare_username(self):
@@ -258,6 +262,43 @@ class TestResolveUserId:
258
262
  )
259
263
  with pytest.raises(LookupError, match="No user found for user ID"):
260
264
  users.resolve_user_id("@U00GHOST")
265
+ users._scim_request.assert_called_once_with(
266
+ path="Users/U00GHOST", method="GET", raise_for_status=False,
267
+ )
268
+
269
+ def test_user_id_slack_api_error_falls_back_to_scim(self):
270
+ """SlackApiError from users.info falls through to SCIM lookup."""
271
+ users = _make_users()
272
+ users.get_user_info = MagicMock(
273
+ side_effect=SlackApiError("users_not_found", response=MagicMock())
274
+ )
275
+ scim_resp = ScimResponse(
276
+ ok=True, status_code=200,
277
+ data={"id": "USCIM", "userName": "scim-user", "active": True},
278
+ text="",
279
+ )
280
+ users._scim_request = MagicMock(return_value=scim_resp)
281
+
282
+ assert users.resolve_user_id("USCIM") == "USCIM"
283
+ users._scim_request.assert_called_once_with(
284
+ path="Users/USCIM", method="GET", raise_for_status=False,
285
+ )
286
+
287
+ def test_user_id_slack_api_error_and_scim_miss_raises(self):
288
+ """SlackApiError + SCIM miss still raises LookupError."""
289
+ users = _make_users()
290
+ users.get_user_info = MagicMock(
291
+ side_effect=SlackApiError("users_not_found", response=MagicMock())
292
+ )
293
+ users._scim_request = MagicMock(
294
+ return_value=ScimResponse(ok=False, status_code=404, data={}, text="")
295
+ )
296
+ with pytest.raises(LookupError, match="No user found for user ID"):
297
+ users.resolve_user_id("U00GHOST")
298
+ users._scim_request.assert_called_once_with(
299
+ path="Users/U00GHOST", method="GET", raise_for_status=False,
300
+ )
301
+
261
302
 
262
303
  # ═══════════════════════════════════════════════════════════════════════════
263
304
  # is_user_authorized
@@ -373,7 +414,9 @@ class TestIsActiveScim:
373
414
  return_value=ScimResponse(ok=True, status_code=200, data={"id": "U_OTHER", "active": True}, text=""),
374
415
  )
375
416
  assert bound.is_active_scim(user_id="U_OTHER") is True
376
- bound._scim_request.assert_called_once_with(path="Users/U_OTHER", method="GET")
417
+ bound._scim_request.assert_called_once_with(
418
+ path="Users/U_OTHER", method="GET", raise_for_status=False,
419
+ )
377
420
 
378
421
  def test_no_user_id_raises(self):
379
422
  """Unbound instance with no user_id raises ValueError."""
File without changes
File without changes
File without changes
File without changes
File without changes