slack-objects 0.0.post31__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.
- slack_objects-0.0.post31/.gitignore +14 -0
- slack_objects-0.0.post31/.vs/VSWorkspaceState.json +8 -0
- slack_objects-0.0.post31/.vs/slnx.sqlite +0 -0
- slack_objects-0.0.post31/LICENSE +21 -0
- slack_objects-0.0.post31/PKG-INFO +201 -0
- slack_objects-0.0.post31/README.md +185 -0
- slack_objects-0.0.post31/pyproject.toml +29 -0
- slack_objects-0.0.post31/pytest.ini +3 -0
- slack_objects-0.0.post31/setup.cfg +4 -0
- slack_objects-0.0.post31/src/slack_objects/__init__.py +22 -0
- slack_objects-0.0.post31/src/slack_objects/_version.py +34 -0
- slack_objects-0.0.post31/src/slack_objects/api_caller.py +42 -0
- slack_objects-0.0.post31/src/slack_objects/base.py +30 -0
- slack_objects-0.0.post31/src/slack_objects/client.py +50 -0
- slack_objects-0.0.post31/src/slack_objects/config.py +29 -0
- slack_objects-0.0.post31/src/slack_objects/conversations.py +437 -0
- slack_objects-0.0.post31/src/slack_objects/files.py +331 -0
- slack_objects-0.0.post31/src/slack_objects/idp_groups.py +200 -0
- slack_objects-0.0.post31/src/slack_objects/messages.py +322 -0
- slack_objects-0.0.post31/src/slack_objects/rate_limits.py +51 -0
- slack_objects-0.0.post31/src/slack_objects/users.py +554 -0
- slack_objects-0.0.post31/src/slack_objects/workspaces.py +261 -0
- slack_objects-0.0.post31/src/slack_objects.egg-info/PKG-INFO +201 -0
- slack_objects-0.0.post31/src/slack_objects.egg-info/SOURCES.txt +40 -0
- slack_objects-0.0.post31/src/slack_objects.egg-info/dependency_links.txt +1 -0
- slack_objects-0.0.post31/src/slack_objects.egg-info/requires.txt +2 -0
- slack_objects-0.0.post31/src/slack_objects.egg-info/top_level.txt +1 -0
- slack_objects-0.0.post31/tests/_smoke_harness.py +273 -0
- slack_objects-0.0.post31/tests/conversations_example_test.py +64 -0
- slack_objects-0.0.post31/tests/conversations_smoke_test.py +50 -0
- slack_objects-0.0.post31/tests/files_example_test.py +124 -0
- slack_objects-0.0.post31/tests/files_smoke_test.py +57 -0
- slack_objects-0.0.post31/tests/idp_groups_example_test.py +269 -0
- slack_objects-0.0.post31/tests/idp_groups_smoke_test.py +37 -0
- slack_objects-0.0.post31/tests/messages_example_test.py +115 -0
- slack_objects-0.0.post31/tests/messages_smoke_test.py +42 -0
- slack_objects-0.0.post31/tests/run_all_smoke.py +29 -0
- slack_objects-0.0.post31/tests/users_example_test.py +63 -0
- slack_objects-0.0.post31/tests/users_smoke_test.py +167 -0
- slack_objects-0.0.post31/tests/users_test_AzureKeyVault.py +19 -0
- slack_objects-0.0.post31/tests/workspaces_example_test.py +67 -0
- slack_objects-0.0.post31/tests/workspaces_smoke_test.py +40 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
|
3
|
+
################################################################################
|
|
4
|
+
|
|
5
|
+
/.vs/slack-objects.slnx
|
|
6
|
+
*.slnx
|
|
7
|
+
*.pyproj
|
|
8
|
+
/.vs/slack-objects/CopilotIndices/18.3.494.57194
|
|
9
|
+
/src/slack_objects/__pycache__
|
|
10
|
+
/.vs/CopilotSnapshots/*
|
|
11
|
+
/.vs/CopilotIndices/*
|
|
12
|
+
/.vs/slack-objects/CopilotIndices/*
|
|
13
|
+
/.vs/slack-objects/CopilotIndices/*
|
|
14
|
+
/tests/__pycache__
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marcos Mercado
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slack-objects
|
|
3
|
+
Version: 0.0.post31
|
|
4
|
+
Summary: This package defines classes for working with slack objects like users, conversations, messages, etc.
|
|
5
|
+
Author-email: "Marcos E. Mercado" <marcos_elias@hotmail.com>
|
|
6
|
+
Keywords: slack,objects,classes,slack objects,utilities,slack utilities,slack object types,slack types,types
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: slack-sdk
|
|
14
|
+
Requires-Dist: PC_Utils
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# slack-objects
|
|
18
|
+
|
|
19
|
+
A focused Python package for working with **Slack objects** commonly used in administration and automation workflows.
|
|
20
|
+
|
|
21
|
+
The following Slack object types will be supported:
|
|
22
|
+
|
|
23
|
+
- **Users**
|
|
24
|
+
- **Conversations**
|
|
25
|
+
- **Messages**
|
|
26
|
+
- **Files**
|
|
27
|
+
- **Workspaces**
|
|
28
|
+
- **IDP_groups**
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Overview
|
|
34
|
+
|
|
35
|
+
`slack-objects` provides lightweight, reusable classes that wrap Slack Web API, Admin API, and SCIM operations in a consistent, object-oriented way. It is designed for:
|
|
36
|
+
|
|
37
|
+
- Slack administration automation
|
|
38
|
+
- Identity and access management flows
|
|
39
|
+
- Internal tooling and bots
|
|
40
|
+
- Auditing and cleanup scripts
|
|
41
|
+
|
|
42
|
+
The package does **not** aim to be a full Slack SDK replacement. Instead, it focuses on common higher-level tasks that typically require multiple API calls and boilerplate logic.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Requirements
|
|
47
|
+
|
|
48
|
+
- Python **3.9+**
|
|
49
|
+
- Slack app with appropriate scopes
|
|
50
|
+
- Tokens provided via environment variables or from Azure KeyVault using PC_Azure package (`python -m pip install PC_Azure`)
|
|
51
|
+
|
|
52
|
+
Typical dependencies:
|
|
53
|
+
- `slack_sdk`
|
|
54
|
+
- `requests`
|
|
55
|
+
- `python-dotenv` (optional)
|
|
56
|
+
- `PC_Azure` (optional)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install -r requirements.txt
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Classes and usage
|
|
67
|
+
|
|
68
|
+
### `Users`
|
|
69
|
+
|
|
70
|
+
Purpose: actions related to Slack users.
|
|
71
|
+
|
|
72
|
+
Constructor:
|
|
73
|
+
```python
|
|
74
|
+
Users(global_vars, client, logger, user_id="")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Key methods:
|
|
78
|
+
- `is_contingent_worker()` → bool using name/display name label `[External]`.
|
|
79
|
+
- `is_guest()` → bool if `is_restricted` or `is_ultra_restricted`.
|
|
80
|
+
- `make_multi_channel_guest(token, scim_version='v1')` → `requests.Response` via SCIM (v1/v2).
|
|
81
|
+
- `remove_from_channels(token, client, logger, channel_ids)` → remove user from channels (admin API).
|
|
82
|
+
- `remove_from_workspaces(client, logger, workspace_ids, keep=[])` → remove user from workspaces.
|
|
83
|
+
- `ap_studio_process()` → composite flow: convert to MCG, remove from org-wide channels, remove from other workspaces.
|
|
84
|
+
- `get_userId_from_email(email)` → Slack user ID or empty string.
|
|
85
|
+
- `is_user_authorized(service_name, auth_level='read')` → bool based on IdP group membership.
|
|
86
|
+
- `invite_user(channel_ids, email, team_id, email_password_policy_enabled=False)` → invite a user, returns response string.
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
```python
|
|
90
|
+
u = Users(global_vars, client, logger, user_id="U123")
|
|
91
|
+
if u.is_contingent_worker():
|
|
92
|
+
u.make_multi_channel_guest(token=global_vars.user_token)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `Conversations`
|
|
96
|
+
|
|
97
|
+
Purpose: actions related to conversations (e.g., channels).
|
|
98
|
+
|
|
99
|
+
Constructor:
|
|
100
|
+
```python
|
|
101
|
+
Conversations(global_vars, client, logger, channel_id)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Key methods:
|
|
105
|
+
- `is_private()` → bool.
|
|
106
|
+
- `get_messages(channel_id="", include_all_metadata=False, limit=None, inclusive=True, latest=None, oldest=None)` → list of messages using `conversations.history` with pagination.
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
```python
|
|
110
|
+
ch = Conversations(global_vars, client, logger, channel_id="C123")
|
|
111
|
+
msgs = ch.get_messages(limit=100)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `Messages`
|
|
115
|
+
|
|
116
|
+
Purpose: manage Slack messages and blocks.
|
|
117
|
+
|
|
118
|
+
Constructor:
|
|
119
|
+
```python
|
|
120
|
+
Messages(global_vars, client, logger, channel_id, ts, message=None)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Key methods:
|
|
124
|
+
- `update_message(as_user=True, channel_id="", message_ts="", new_message_blocks=[], new_message_text="", new_message_attachments="")` → update message via `chat.update`.
|
|
125
|
+
- `replace_message_block(blocks=[], block_type="", block_id="", text="", new_block={}, new_block_id="")` → find a block by type or id and replace it, then update message.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
```python
|
|
129
|
+
msg = Messages(global_vars, client, logger, "C123", "1717000000.000100")
|
|
130
|
+
msg.update_message(new_message_text="Updated content")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `Files`
|
|
134
|
+
|
|
135
|
+
Purpose: interact with files in Slack.
|
|
136
|
+
|
|
137
|
+
Constructor:
|
|
138
|
+
```python
|
|
139
|
+
Files(global_vars, client, logger, file_id="", get_content=False)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Key methods:
|
|
143
|
+
- `get_text_content()` → fetch content for text files via `url_private` (uses bot token).
|
|
144
|
+
- `upload_to_slack(title, channel="", thread_ts="")` → upload the current file content via `files_upload_v2`.
|
|
145
|
+
- `delete_file(file_id="")` → delete a file by id.
|
|
146
|
+
- `list_files(**args)` → simple wrapper around `files.list`.
|
|
147
|
+
- `get_file_source_message(channel: Channels, file_id="", user_id="")` → find the message where a file was shared (looks back ~5 messages).
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
```python
|
|
151
|
+
f = Files(global_vars, client, logger, file_id="F123", get_content=True)
|
|
152
|
+
f.upload_to_slack(title="Processed file", channel="C123")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `Workspaces`
|
|
156
|
+
|
|
157
|
+
Purpose: workspace info helper.
|
|
158
|
+
|
|
159
|
+
Constructor:
|
|
160
|
+
```python
|
|
161
|
+
Workspaces(client, logger, workspace_id)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Obtains attributes via `team.info`.
|
|
165
|
+
|
|
166
|
+
### `IDP_groups`
|
|
167
|
+
|
|
168
|
+
Purpose: manage IdP (Okta) groups via SCIM.
|
|
169
|
+
|
|
170
|
+
Constructor:
|
|
171
|
+
```python
|
|
172
|
+
IDP_groups(global_vars)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Key methods:
|
|
176
|
+
- `get_groups()` → list of `{ 'group id', 'group name' }` (paginated).
|
|
177
|
+
- `get_members(group_id)` → list of members with `value` (user id) and `display` (name).
|
|
178
|
+
- `is_member(user_id, group_id)` → bool.
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
```python
|
|
182
|
+
idp = IDP_groups(global_vars)
|
|
183
|
+
if idp.is_member("U123", "GP456"):
|
|
184
|
+
print("authorized")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Tokens and rate limits
|
|
188
|
+
|
|
189
|
+
- Methods that call admin or SCIM APIs require the User OAuth token.
|
|
190
|
+
- Standard Web API calls may use the Bot token via `App.client`.
|
|
191
|
+
- Some methods respect internal wait times (e.g., `Tier_2`, `Tier_3`, `Tier_4`) to avoid rate limits. Configure these in `libraries_and_globals.py`.
|
|
192
|
+
|
|
193
|
+
## Error handling
|
|
194
|
+
|
|
195
|
+
- Methods catch `SlackApiError` and log messages via the provided `logger`.
|
|
196
|
+
- Some methods post audit logs to channels configured in `global_vars`.
|
|
197
|
+
|
|
198
|
+
## Notes
|
|
199
|
+
|
|
200
|
+
- SCIM version: production uses `v1`, sandbox may use `v2`.
|
|
201
|
+
- `Files.get_text_content()` is designed for `text/*` mimetypes.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# slack-objects
|
|
2
|
+
|
|
3
|
+
A focused Python package for working with **Slack objects** commonly used in administration and automation workflows.
|
|
4
|
+
|
|
5
|
+
The following Slack object types will be supported:
|
|
6
|
+
|
|
7
|
+
- **Users**
|
|
8
|
+
- **Conversations**
|
|
9
|
+
- **Messages**
|
|
10
|
+
- **Files**
|
|
11
|
+
- **Workspaces**
|
|
12
|
+
- **IDP_groups**
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
`slack-objects` provides lightweight, reusable classes that wrap Slack Web API, Admin API, and SCIM operations in a consistent, object-oriented way. It is designed for:
|
|
20
|
+
|
|
21
|
+
- Slack administration automation
|
|
22
|
+
- Identity and access management flows
|
|
23
|
+
- Internal tooling and bots
|
|
24
|
+
- Auditing and cleanup scripts
|
|
25
|
+
|
|
26
|
+
The package does **not** aim to be a full Slack SDK replacement. Instead, it focuses on common higher-level tasks that typically require multiple API calls and boilerplate logic.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Python **3.9+**
|
|
33
|
+
- Slack app with appropriate scopes
|
|
34
|
+
- Tokens provided via environment variables or from Azure KeyVault using PC_Azure package (`python -m pip install PC_Azure`)
|
|
35
|
+
|
|
36
|
+
Typical dependencies:
|
|
37
|
+
- `slack_sdk`
|
|
38
|
+
- `requests`
|
|
39
|
+
- `python-dotenv` (optional)
|
|
40
|
+
- `PC_Azure` (optional)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install -r requirements.txt
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Classes and usage
|
|
51
|
+
|
|
52
|
+
### `Users`
|
|
53
|
+
|
|
54
|
+
Purpose: actions related to Slack users.
|
|
55
|
+
|
|
56
|
+
Constructor:
|
|
57
|
+
```python
|
|
58
|
+
Users(global_vars, client, logger, user_id="")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Key methods:
|
|
62
|
+
- `is_contingent_worker()` → bool using name/display name label `[External]`.
|
|
63
|
+
- `is_guest()` → bool if `is_restricted` or `is_ultra_restricted`.
|
|
64
|
+
- `make_multi_channel_guest(token, scim_version='v1')` → `requests.Response` via SCIM (v1/v2).
|
|
65
|
+
- `remove_from_channels(token, client, logger, channel_ids)` → remove user from channels (admin API).
|
|
66
|
+
- `remove_from_workspaces(client, logger, workspace_ids, keep=[])` → remove user from workspaces.
|
|
67
|
+
- `ap_studio_process()` → composite flow: convert to MCG, remove from org-wide channels, remove from other workspaces.
|
|
68
|
+
- `get_userId_from_email(email)` → Slack user ID or empty string.
|
|
69
|
+
- `is_user_authorized(service_name, auth_level='read')` → bool based on IdP group membership.
|
|
70
|
+
- `invite_user(channel_ids, email, team_id, email_password_policy_enabled=False)` → invite a user, returns response string.
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
```python
|
|
74
|
+
u = Users(global_vars, client, logger, user_id="U123")
|
|
75
|
+
if u.is_contingent_worker():
|
|
76
|
+
u.make_multi_channel_guest(token=global_vars.user_token)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `Conversations`
|
|
80
|
+
|
|
81
|
+
Purpose: actions related to conversations (e.g., channels).
|
|
82
|
+
|
|
83
|
+
Constructor:
|
|
84
|
+
```python
|
|
85
|
+
Conversations(global_vars, client, logger, channel_id)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Key methods:
|
|
89
|
+
- `is_private()` → bool.
|
|
90
|
+
- `get_messages(channel_id="", include_all_metadata=False, limit=None, inclusive=True, latest=None, oldest=None)` → list of messages using `conversations.history` with pagination.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
```python
|
|
94
|
+
ch = Conversations(global_vars, client, logger, channel_id="C123")
|
|
95
|
+
msgs = ch.get_messages(limit=100)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `Messages`
|
|
99
|
+
|
|
100
|
+
Purpose: manage Slack messages and blocks.
|
|
101
|
+
|
|
102
|
+
Constructor:
|
|
103
|
+
```python
|
|
104
|
+
Messages(global_vars, client, logger, channel_id, ts, message=None)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Key methods:
|
|
108
|
+
- `update_message(as_user=True, channel_id="", message_ts="", new_message_blocks=[], new_message_text="", new_message_attachments="")` → update message via `chat.update`.
|
|
109
|
+
- `replace_message_block(blocks=[], block_type="", block_id="", text="", new_block={}, new_block_id="")` → find a block by type or id and replace it, then update message.
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
```python
|
|
113
|
+
msg = Messages(global_vars, client, logger, "C123", "1717000000.000100")
|
|
114
|
+
msg.update_message(new_message_text="Updated content")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `Files`
|
|
118
|
+
|
|
119
|
+
Purpose: interact with files in Slack.
|
|
120
|
+
|
|
121
|
+
Constructor:
|
|
122
|
+
```python
|
|
123
|
+
Files(global_vars, client, logger, file_id="", get_content=False)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Key methods:
|
|
127
|
+
- `get_text_content()` → fetch content for text files via `url_private` (uses bot token).
|
|
128
|
+
- `upload_to_slack(title, channel="", thread_ts="")` → upload the current file content via `files_upload_v2`.
|
|
129
|
+
- `delete_file(file_id="")` → delete a file by id.
|
|
130
|
+
- `list_files(**args)` → simple wrapper around `files.list`.
|
|
131
|
+
- `get_file_source_message(channel: Channels, file_id="", user_id="")` → find the message where a file was shared (looks back ~5 messages).
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
```python
|
|
135
|
+
f = Files(global_vars, client, logger, file_id="F123", get_content=True)
|
|
136
|
+
f.upload_to_slack(title="Processed file", channel="C123")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### `Workspaces`
|
|
140
|
+
|
|
141
|
+
Purpose: workspace info helper.
|
|
142
|
+
|
|
143
|
+
Constructor:
|
|
144
|
+
```python
|
|
145
|
+
Workspaces(client, logger, workspace_id)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Obtains attributes via `team.info`.
|
|
149
|
+
|
|
150
|
+
### `IDP_groups`
|
|
151
|
+
|
|
152
|
+
Purpose: manage IdP (Okta) groups via SCIM.
|
|
153
|
+
|
|
154
|
+
Constructor:
|
|
155
|
+
```python
|
|
156
|
+
IDP_groups(global_vars)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Key methods:
|
|
160
|
+
- `get_groups()` → list of `{ 'group id', 'group name' }` (paginated).
|
|
161
|
+
- `get_members(group_id)` → list of members with `value` (user id) and `display` (name).
|
|
162
|
+
- `is_member(user_id, group_id)` → bool.
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
```python
|
|
166
|
+
idp = IDP_groups(global_vars)
|
|
167
|
+
if idp.is_member("U123", "GP456"):
|
|
168
|
+
print("authorized")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Tokens and rate limits
|
|
172
|
+
|
|
173
|
+
- Methods that call admin or SCIM APIs require the User OAuth token.
|
|
174
|
+
- Standard Web API calls may use the Bot token via `App.client`.
|
|
175
|
+
- Some methods respect internal wait times (e.g., `Tier_2`, `Tier_3`, `Tier_4`) to avoid rate limits. Configure these in `libraries_and_globals.py`.
|
|
176
|
+
|
|
177
|
+
## Error handling
|
|
178
|
+
|
|
179
|
+
- Methods catch `SlackApiError` and log messages via the provided `logger`.
|
|
180
|
+
- Some methods post audit logs to channels configured in `global_vars`.
|
|
181
|
+
|
|
182
|
+
## Notes
|
|
183
|
+
|
|
184
|
+
- SCIM version: production uses `v1`, sandbox may use `v2`.
|
|
185
|
+
- `Files.get_text_content()` is designed for `text/*` mimetypes.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 77.0.3", "setuptools_scm>=8"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "slack-objects"
|
|
7
|
+
# version = "0.0.1" # Remove any existing version parameter.
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
authors = [
|
|
10
|
+
{name="Marcos E. Mercado", email="marcos_elias@hotmail.com"},
|
|
11
|
+
]
|
|
12
|
+
description = "This package defines classes for working with slack objects like users, conversations, messages, etc."
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
classifiers=[
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
] # Update these accordingly
|
|
20
|
+
keywords = ["slack", "objects", "classes", "slack objects", "utilities", "slack utilities", "slack object types", "slack types", "types"]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"slack-sdk",
|
|
23
|
+
"PC_Utils",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools_scm]
|
|
27
|
+
version_file = "src/slack_objects/_version.py"
|
|
28
|
+
local_scheme = "no-local-version"
|
|
29
|
+
version_scheme = "post-release"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from .client import SlackObjectsClient
|
|
2
|
+
from .config import SlackObjectsConfig, RateTier #, IdPGroupConfig
|
|
3
|
+
|
|
4
|
+
from .users import Users
|
|
5
|
+
#from .conversations import Conversations
|
|
6
|
+
#from .messages import Messages
|
|
7
|
+
#from .files import Files
|
|
8
|
+
#from .workspaces import Workspaces
|
|
9
|
+
#from .idp_groups import IDP_groups
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SlackObjectsClient",
|
|
13
|
+
"SlackObjectsConfig",
|
|
14
|
+
"RateTier",
|
|
15
|
+
"IdPGroupConfig",
|
|
16
|
+
"Users",
|
|
17
|
+
"Channels",
|
|
18
|
+
"Messages",
|
|
19
|
+
"Files",
|
|
20
|
+
"Workspaces",
|
|
21
|
+
"IDP_groups",
|
|
22
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.0.post31'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 'post31')
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = 'gcf17794dd'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from slack_sdk.errors import SlackApiError
|
|
5
|
+
|
|
6
|
+
from .config import SlackObjectsConfig, RateTier
|
|
7
|
+
from .rate_limits import DEFAULT_RATE_POLICY, RateLimitPolicy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SlackApiCaller:
|
|
11
|
+
"""
|
|
12
|
+
Wrapper around Slack SDK client to handle rate limiting and API calls.
|
|
13
|
+
|
|
14
|
+
Example: self.api.call(self.client, "users.lookupByEmail", email=email)
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, cfg: SlackObjectsConfig, policy: RateLimitPolicy = DEFAULT_RATE_POLICY):
|
|
17
|
+
self.cfg = cfg
|
|
18
|
+
self.policy = policy
|
|
19
|
+
|
|
20
|
+
def call(self, client, method: str, *, rate_tier: Optional[RateTier] = None, use_json: bool = False, **kwargs) -> dict:
|
|
21
|
+
tier = rate_tier or self.policy.tier_for(method) or self.cfg.default_rate_tier
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
if use_json:
|
|
25
|
+
resp = client.api_call(method, json=kwargs)
|
|
26
|
+
else:
|
|
27
|
+
resp = client.api_call(method, params=kwargs)
|
|
28
|
+
|
|
29
|
+
data = resp.data if hasattr(resp, "data") else resp
|
|
30
|
+
|
|
31
|
+
# Space out subsequent calls
|
|
32
|
+
time.sleep(float(tier))
|
|
33
|
+
return data
|
|
34
|
+
|
|
35
|
+
except SlackApiError as e:
|
|
36
|
+
# Handle rate limiting properly
|
|
37
|
+
if e.response is not None and e.response.status_code == 429:
|
|
38
|
+
retry_after = int(e.response.headers.get("Retry-After", tier))
|
|
39
|
+
time.sleep(retry_after)
|
|
40
|
+
return self.call(client, method, rate_tier=tier, **kwargs)
|
|
41
|
+
|
|
42
|
+
raise
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Optional, Any
|
|
4
|
+
from slack_sdk import WebClient
|
|
5
|
+
|
|
6
|
+
from .api_caller import SlackApiCaller
|
|
7
|
+
from .config import SlackObjectsConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class SlackObjectBase:
|
|
12
|
+
"""
|
|
13
|
+
Base class that all object helpers inherit from.
|
|
14
|
+
|
|
15
|
+
Holds shared context so every object doesn't need to reinvent plumbing (i.e., don't need to pass cfg, client, logger, and the apicaller).
|
|
16
|
+
Logging is optional; a default package logger will be used if none is provided.
|
|
17
|
+
"""
|
|
18
|
+
cfg: SlackObjectsConfig
|
|
19
|
+
client: WebClient
|
|
20
|
+
api: SlackApiCaller
|
|
21
|
+
logger: logging.Logger = field(default_factory=lambda: logging.getLogger("slack-objects")) # logger is guaranteed to exist via default_factory
|
|
22
|
+
|
|
23
|
+
def __post_init__(self) -> None:
|
|
24
|
+
# Required dependencies check
|
|
25
|
+
if self.cfg is None:
|
|
26
|
+
raise ValueError("cfg is required")
|
|
27
|
+
if self.client is None:
|
|
28
|
+
raise ValueError("client is required")
|
|
29
|
+
if self.api is None:
|
|
30
|
+
raise ValueError("api is required")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from slack_sdk import WebClient
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .config import SlackObjectsConfig
|
|
6
|
+
from .api_caller import SlackApiCaller
|
|
7
|
+
from .users import Users
|
|
8
|
+
from .messages import Messages
|
|
9
|
+
from .conversations import Conversations
|
|
10
|
+
from .files import Files
|
|
11
|
+
from .workspaces import Workspaces
|
|
12
|
+
from .idp_groups import IDP_groups
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SlackObjectsClient:
|
|
16
|
+
"""
|
|
17
|
+
Central factory / context object.
|
|
18
|
+
Owns config, Slack client, and rate-limited API caller.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, cfg: SlackObjectsConfig, logger: logging.Logger | None = None):
|
|
22
|
+
self.cfg = cfg
|
|
23
|
+
self.logger = logger or logging.getLogger("slack-objects")
|
|
24
|
+
|
|
25
|
+
# Prefer bot token for general Web API calls; fall back to user token.
|
|
26
|
+
web_token = cfg.bot_token or cfg.user_token
|
|
27
|
+
if not web_token:
|
|
28
|
+
raise ValueError("SlackObjectsClient requires cfg.bot_token or cfg.user_token.")
|
|
29
|
+
|
|
30
|
+
self.web_client = WebClient(token=web_token)
|
|
31
|
+
self.api = SlackApiCaller(cfg)
|
|
32
|
+
|
|
33
|
+
def users(self, user_id: Optional[str] = None) -> Users:
|
|
34
|
+
base = Users(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
|
|
35
|
+
return base if user_id is None else base.with_user(user_id)
|
|
36
|
+
|
|
37
|
+
def conversations(self) -> Conversations:
|
|
38
|
+
return Conversations(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
|
|
39
|
+
|
|
40
|
+
def files(self) -> Files:
|
|
41
|
+
return Files(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
|
|
42
|
+
|
|
43
|
+
def messages(self) -> Messages:
|
|
44
|
+
return Messages(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
|
|
45
|
+
|
|
46
|
+
def workspaces(self) -> Workspaces:
|
|
47
|
+
return Workspaces(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
|
|
48
|
+
|
|
49
|
+
def idp_groups(self) -> IDP_groups:
|
|
50
|
+
return IDP_groups(cfg=self.cfg, client=self.web_client, api=self.api, logger=self.logger)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
class RateTier(float, Enum):
|
|
6
|
+
"""
|
|
7
|
+
Slack API rate-tier backoff defaults (seconds). These are defined to conform to Slack Web API rate limits.
|
|
8
|
+
https://docs.slack.dev/apis/web-api/rate-limits/
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
TIER_1 = 60.0 # 1+ per minute
|
|
12
|
+
TIER_2 = 3.0 # 20+ per minute
|
|
13
|
+
TIER_3 = 1.2 # 50+ per minute
|
|
14
|
+
TIER_4 = 0.6 # 100+ per minute
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class SlackObjectsConfig:
|
|
19
|
+
"""
|
|
20
|
+
Configuration settings for slack-objects.
|
|
21
|
+
|
|
22
|
+
Tokens are optional at construction time.
|
|
23
|
+
Individual methods will raise clear errors if a required token is missing.
|
|
24
|
+
"""
|
|
25
|
+
bot_token: Optional[str] = None
|
|
26
|
+
user_token: Optional[str] = None
|
|
27
|
+
scim_token: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
default_rate_tier: RateTier = RateTier.TIER_2
|