aimodelshare 0.1.32__py3-none-any.whl → 0.1.62__py3-none-any.whl

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.

Potentially problematic release.


This version of aimodelshare might be problematic. Click here for more details.

Files changed (38) hide show
  1. aimodelshare/__init__.py +94 -14
  2. aimodelshare/aimsonnx.py +312 -95
  3. aimodelshare/api.py +13 -12
  4. aimodelshare/auth.py +163 -0
  5. aimodelshare/aws.py +4 -4
  6. aimodelshare/base_image.py +1 -1
  7. aimodelshare/containerisation.py +1 -1
  8. aimodelshare/data_sharing/download_data.py +142 -87
  9. aimodelshare/generatemodelapi.py +7 -6
  10. aimodelshare/main/authorization.txt +275 -275
  11. aimodelshare/main/eval_lambda.txt +81 -13
  12. aimodelshare/model.py +493 -197
  13. aimodelshare/modeluser.py +89 -1
  14. aimodelshare/moral_compass/README.md +408 -0
  15. aimodelshare/moral_compass/__init__.py +37 -0
  16. aimodelshare/moral_compass/_version.py +3 -0
  17. aimodelshare/moral_compass/api_client.py +601 -0
  18. aimodelshare/moral_compass/apps/__init__.py +17 -0
  19. aimodelshare/moral_compass/apps/tutorial.py +198 -0
  20. aimodelshare/moral_compass/challenge.py +365 -0
  21. aimodelshare/moral_compass/config.py +187 -0
  22. aimodelshare/playground.py +26 -14
  23. aimodelshare/preprocessormodules.py +60 -6
  24. aimodelshare/pyspark/authorization.txt +258 -258
  25. aimodelshare/pyspark/eval_lambda.txt +1 -1
  26. aimodelshare/reproducibility.py +20 -5
  27. aimodelshare/utils/__init__.py +78 -0
  28. aimodelshare/utils/optional_deps.py +38 -0
  29. aimodelshare-0.1.62.dist-info/METADATA +298 -0
  30. {aimodelshare-0.1.32.dist-info → aimodelshare-0.1.62.dist-info}/RECORD +33 -25
  31. {aimodelshare-0.1.32.dist-info → aimodelshare-0.1.62.dist-info}/WHEEL +1 -1
  32. aimodelshare-0.1.62.dist-info/licenses/LICENSE +5 -0
  33. {aimodelshare-0.1.32.dist-info → aimodelshare-0.1.62.dist-info}/top_level.txt +0 -1
  34. aimodelshare-0.1.32.dist-info/METADATA +0 -78
  35. aimodelshare-0.1.32.dist-info/licenses/LICENSE +0 -22
  36. tests/__init__.py +0 -0
  37. tests/test_aimsonnx.py +0 -135
  38. tests/test_playground.py +0 -721
aimodelshare/modeluser.py CHANGED
@@ -10,6 +10,27 @@ import datetime
10
10
  import regex as re
11
11
  from aimodelshare.exceptions import AuthorizationError, AWSAccessError, AWSUploadError
12
12
 
13
+ def decode_token_unverified(token: str):
14
+ """Decode a JWT without verifying signature or audience, compatible with PyJWT<2 and >=2 versions.
15
+
16
+ Parameters
17
+ ----------
18
+ token : str
19
+ The JWT token to decode
20
+
21
+ Returns
22
+ -------
23
+ dict
24
+ The decoded token payload
25
+ """
26
+ import jwt
27
+ try:
28
+ return jwt.decode(token, options={"verify_signature": False, "verify_aud": False})
29
+ except TypeError:
30
+ # PyJWT >=2 may require specifying algorithms if verification is enabled; here we keep it disabled
31
+ return jwt.decode(token, options={"verify_signature": False, "verify_aud": False}, algorithms=["HS256"])
32
+
33
+
13
34
  def get_jwt_token(username, password):
14
35
 
15
36
  config = botocore.config.Config(signature_version=botocore.UNSIGNED)
@@ -34,8 +55,73 @@ def get_jwt_token(username, password):
34
55
 
35
56
  return
36
57
 
37
- def create_user_getkeyandpassword():
58
+ def setup_bucket_only():
59
+ """
60
+ Set up the S3 bucket for aimodelshare without creating new IAM users.
61
+
62
+ Uses the provided AWS credentials to create or access the bucket.
63
+ This avoids creating a new IAM user every time credentials are set.
64
+ """
65
+ from aimodelshare.aws import get_s3_iam_client
66
+
67
+ s3, iam, region = get_s3_iam_client(os.environ.get("AWS_ACCESS_KEY_ID_AIMS"),
68
+ os.environ.get("AWS_SECRET_ACCESS_KEY_AIMS"),
69
+ os.environ.get("AWS_REGION_AIMS"))
70
+
71
+ user_session = boto3.session.Session(aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID_AIMS"),
72
+ aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY_AIMS"),
73
+ region_name= os.environ.get("AWS_REGION_AIMS"))
74
+
75
+ account_number = user_session.client(
76
+ 'sts').get_caller_identity().get('Account')
77
+
78
+ # Remove special characters from username
79
+ username_clean = re.sub('[^A-Za-z0-9-]+', '', os.environ.get("username"))
80
+ bucket_name = 'aimodelshare' + username_clean.lower() + str(account_number) + region.replace('-', '')
81
+
82
+ region = os.environ.get("AWS_REGION_AIMS")
83
+ s3_client = s3['client']
38
84
 
85
+ # Create bucket if it doesn't exist
86
+ try:
87
+ response = s3_client.head_bucket(Bucket=bucket_name)
88
+ except:
89
+ if region == "us-east-1":
90
+ response = s3_client.create_bucket(
91
+ ACL="private",
92
+ Bucket=bucket_name
93
+ )
94
+ else:
95
+ location = {'LocationConstraint': region}
96
+ response = s3_client.create_bucket(
97
+ ACL="private",
98
+ Bucket=bucket_name,
99
+ CreateBucketConfiguration=location
100
+ )
101
+
102
+ # Set the bucket name in environment for use by other functions
103
+ os.environ["BUCKET_NAME"] = bucket_name
104
+
105
+ return
106
+
107
+
108
+ def create_user_getkeyandpassword():
109
+ """
110
+ DEPRECATED: This function creates a new IAM user every time it's called.
111
+
112
+ Use setup_bucket_only() instead, which uses the provided AWS credentials
113
+ without creating new IAM users and policies.
114
+
115
+ This function is kept for backward compatibility but should not be used.
116
+ """
117
+ import warnings
118
+ warnings.warn(
119
+ "create_user_getkeyandpassword() is deprecated and creates unnecessary IAM users. "
120
+ "Use setup_bucket_only() instead.",
121
+ DeprecationWarning,
122
+ stacklevel=2
123
+ )
124
+
39
125
  from aimodelshare.bucketpolicy import _custom_s3_policy
40
126
  from aimodelshare.tools import form_timestamp
41
127
  from aimodelshare.aws import get_s3_iam_client
@@ -124,4 +210,6 @@ def create_user_getkeyandpassword():
124
210
  __all__ = [
125
211
  get_jwt_token,
126
212
  create_user_getkeyandpassword,
213
+ setup_bucket_only,
214
+ decode_token_unverified,
127
215
  ]
@@ -0,0 +1,408 @@
1
+ # moral_compass API Client
2
+
3
+ Production-ready Python client for the moral_compass REST API.
4
+
5
+ ## Features
6
+
7
+ - **Automatic API Discovery**: Finds API base URL from environment variables, cached terraform outputs, or terraform command
8
+ - **Retry Logic**: Automatic retries for network errors and 5xx server errors with exponential backoff
9
+ - **Pagination**: Simple iterator helpers for paginating through large result sets
10
+ - **Type Safety**: Dataclasses for all API responses
11
+ - **Structured Exceptions**: Specific exceptions for different error types (NotFoundError, ServerError)
12
+ - **Backward Compatibility**: Available via both `aimodelshare.moral_compass` and `moral_compass` import paths
13
+ - **Authentication**: Automatic JWT token attachment from environment variables
14
+ - **Ownership Enforcement**: Table-level ownership and authorization controls
15
+ - **Naming Conventions**: Enforced naming patterns for moral compass tables
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install -e . # Install in development mode
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ from aimodelshare.moral_compass import MoralcompassApiClient
27
+
28
+ # Create client (auto-discovers API URL from environment)
29
+ client = MoralcompassApiClient()
30
+
31
+ # Or specify URL explicitly
32
+ client = MoralcompassApiClient(api_base_url="https://api.example.com")
33
+
34
+ # Check API health
35
+ health = client.health()
36
+ print(health)
37
+
38
+ # Create a table
39
+ client.create_table("my-table", "My Display Name")
40
+
41
+ # Get table info
42
+ table = client.get_table("my-table")
43
+ print(f"Table: {table.table_id}, Users: {table.user_count}")
44
+
45
+ # List all tables with automatic pagination
46
+ for table in client.iter_tables():
47
+ print(f"- {table.table_id}: {table.display_name}")
48
+
49
+ # Add a user to a table
50
+ client.put_user("my-table", "user1", submission_count=10, total_count=100)
51
+
52
+ # Get user stats
53
+ user = client.get_user("my-table", "user1")
54
+ print(f"User {user.username}: {user.submission_count} submissions")
55
+
56
+ # List all users in a table
57
+ for user in client.iter_users("my-table"):
58
+ print(f"- {user.username}: {user.submission_count} submissions")
59
+ ```
60
+
61
+ ## Authentication & Authorization
62
+
63
+ The moral compass API supports authentication and authorization controls when `AUTH_ENABLED=true` on the server.
64
+
65
+ > **⚠️ SECURITY WARNING**
66
+ > JWT signature verification is currently a **stub implementation** that performs unverified token decoding.
67
+ > **DO NOT use in production** for security-critical operations without implementing JWKS-based signature verification.
68
+ > This is suitable for development and testing only.
69
+
70
+ ### Authentication Token
71
+
72
+ The client automatically attaches JWT authentication tokens from environment variables:
73
+
74
+ ```bash
75
+ # Preferred: Use JWT_AUTHORIZATION_TOKEN
76
+ export JWT_AUTHORIZATION_TOKEN="your.jwt.token"
77
+
78
+ # Legacy: AWS_TOKEN (deprecated, triggers warning)
79
+ export AWS_TOKEN="your.aws.token"
80
+ ```
81
+
82
+ You can also provide the token explicitly:
83
+
84
+ ```python
85
+ from aimodelshare.moral_compass import MoralcompassApiClient
86
+
87
+ # Explicit token
88
+ client = MoralcompassApiClient(auth_token="your.jwt.token")
89
+
90
+ # Auto-discover from environment
91
+ client = MoralcompassApiClient() # Uses JWT_AUTHORIZATION_TOKEN or AWS_TOKEN
92
+ ```
93
+
94
+ ### Automatic Token Acquisition
95
+
96
+ If no JWT token is found in environment variables, the client will attempt to auto-generate one using username and password credentials. This provides seamless integration when users have already configured credentials via `configure_credentials()`:
97
+
98
+ ```bash
99
+ # Set credentials for automatic JWT generation
100
+ export AIMODELSHARE_USERNAME="your-username"
101
+ export AIMODELSHARE_PASSWORD="your-password"
102
+
103
+ # Alternative variable names also supported
104
+ export username="your-username"
105
+ export password="your-password"
106
+ ```
107
+
108
+ ```python
109
+ from aimodelshare.moral_compass import MoralcompassApiClient
110
+ from aimodelshare.modeluser import get_jwt_token
111
+ import os
112
+
113
+ # Method 1: Let the client auto-generate (recommended)
114
+ # Client will automatically use AIMODELSHARE_USERNAME/AIMODELSHARE_PASSWORD
115
+ client = MoralcompassApiClient()
116
+
117
+ # Method 2: Explicitly generate and set JWT token
118
+ if not os.getenv('JWT_AUTHORIZATION_TOKEN'):
119
+ username = os.getenv('AIMODELSHARE_USERNAME')
120
+ password = os.getenv('AIMODELSHARE_PASSWORD')
121
+ if username and password:
122
+ get_jwt_token(username, password) # Sets JWT_AUTHORIZATION_TOKEN
123
+ # Now JWT_AUTHORIZATION_TOKEN is available for subsequent API calls
124
+
125
+ client = MoralcompassApiClient()
126
+ ```
127
+
128
+ The auto-generation process:
129
+ 1. Checks for existing JWT_AUTHORIZATION_TOKEN (skips if found)
130
+ 2. Looks for AIMODELSHARE_USERNAME/AIMODELSHARE_PASSWORD or username/password
131
+ 3. Calls `get_jwt_token()` to generate and set JWT_AUTHORIZATION_TOKEN
132
+ 4. Uses the generated token for API authentication
133
+ 5. Logs success/failure for debugging
134
+
135
+ ### Table Ownership
136
+
137
+ When authentication is enabled, tables have ownership metadata:
138
+
139
+ ```python
140
+ # Create a table for a playground (stores owner identity)
141
+ response = client.create_table(
142
+ table_id="my-playground-mc",
143
+ display_name="My Moral Compass Table",
144
+ playground_url="https://example.com/playground/my-playground"
145
+ )
146
+ # Response is a dict with structure:
147
+ # {
148
+ # 'tableId': 'my-playground-mc',
149
+ # 'displayName': 'My Moral Compass Table',
150
+ # 'ownerPrincipal': 'user@example.com', # Owner's identity
151
+ # 'playgroundId': 'my-playground',
152
+ # 'message': 'Table created successfully'
153
+ # }
154
+ print(response['ownerPrincipal']) # Shows who created the table
155
+
156
+ # Convenience method to create with naming convention
157
+ response = client.create_table_for_playground(
158
+ playground_url="https://example.com/playground/my-playground",
159
+ suffix="-mc" # Creates table: my-playground-mc
160
+ )
161
+ ```
162
+
163
+ ### Naming Convention
164
+
165
+ When `MC_ENFORCE_NAMING=true` on the server, moral compass tables must follow the pattern: `<playgroundId>-mc`
166
+
167
+ ```python
168
+ # Valid: Follows naming convention
169
+ client.create_table(
170
+ table_id="my-playground-mc",
171
+ playground_url="https://example.com/playground/my-playground"
172
+ )
173
+
174
+ # Invalid: Will return 400 error if MC_ENFORCE_NAMING=true
175
+ client.create_table(
176
+ table_id="random-name",
177
+ playground_url="https://example.com/playground/my-playground"
178
+ )
179
+ ```
180
+
181
+ ### Authorization Rules
182
+
183
+ When `AUTH_ENABLED=true`:
184
+
185
+ - **Table Creation**: Only authenticated users can create tables
186
+ - **Table Deletion**: Only the owner or admin can delete tables (requires `ALLOW_TABLE_DELETE=true`)
187
+ - **User Updates**: Only the user themselves or admin can update their progress
188
+ - **Read Operations**: Public by default when `ALLOW_PUBLIC_READ=true`
189
+
190
+ ```python
191
+ # Delete a table (owner or admin only)
192
+ client.delete_table("my-playground-mc")
193
+
194
+ # Update own progress
195
+ client.update_moral_compass(
196
+ table_id="my-playground-mc",
197
+ username="my-username",
198
+ metrics={"accuracy": 0.85},
199
+ tasks_completed=3,
200
+ total_tasks=6
201
+ )
202
+ ```
203
+
204
+ ### Helper Functions
205
+
206
+ Use the `aimodelshare.auth` module for identity management:
207
+
208
+ ```python
209
+ from aimodelshare.auth import get_primary_token, get_identity_claims
210
+
211
+ # Get token from environment
212
+ token = get_primary_token()
213
+
214
+ # Extract identity claims (DEVELOPMENT ONLY - signature not verified)
215
+ if token:
216
+ # Currently verify=False (stub implementation)
217
+ # TODO: Use verify=True after implementing JWKS verification for production
218
+ claims = get_identity_claims(token, verify=False)
219
+ print(f"User: {claims['principal']}")
220
+ print(f"Email: {claims.get('email')}")
221
+ print(f"Subject: {claims['sub']}")
222
+ ```
223
+
224
+ ## API Base URL Configuration
225
+
226
+ The client discovers the API base URL using the following priority:
227
+
228
+ 1. **Environment Variable**: `MORAL_COMPASS_API_BASE_URL` or `AIMODELSHARE_API_BASE_URL`
229
+ 2. **Cached Terraform Outputs**: `infra/terraform_outputs.json`
230
+ 3. **Terraform Command**: Runs `terraform output -raw api_base_url` in the `infra/` directory
231
+ 4. **Explicit Parameter**: Pass `api_base_url` to the client constructor
232
+
233
+ ```bash
234
+ # Set via environment variable
235
+ export MORAL_COMPASS_API_BASE_URL="https://your-api.example.com"
236
+ ```
237
+
238
+ ## Error Handling
239
+
240
+ ```python
241
+ from aimodelshare.moral_compass import (
242
+ MoralcompassApiClient,
243
+ NotFoundError,
244
+ ServerError,
245
+ ApiClientError
246
+ )
247
+
248
+ client = MoralcompassApiClient()
249
+
250
+ try:
251
+ table = client.get_table("nonexistent-table")
252
+ except NotFoundError:
253
+ print("Table not found (404)")
254
+ except ServerError:
255
+ print("Server error (5xx)")
256
+ except ApiClientError as e:
257
+ print(f"API error: {e}")
258
+ ```
259
+
260
+ ## Pagination
261
+
262
+ ### Manual Pagination
263
+
264
+ ```python
265
+ # Get first page
266
+ response = client.list_tables(limit=10)
267
+ tables = response["tables"]
268
+ last_key = response.get("lastKey")
269
+
270
+ # Get next page if available
271
+ if last_key:
272
+ response = client.list_tables(limit=10, last_key=last_key)
273
+ tables.extend(response["tables"])
274
+ ```
275
+
276
+ ### Automatic Pagination with Iterators
277
+
278
+ ```python
279
+ # Automatically handles pagination behind the scenes
280
+ for table in client.iter_tables(limit=50):
281
+ print(table.table_id)
282
+
283
+ for user in client.iter_users("my-table", limit=50):
284
+ print(user.username)
285
+ ```
286
+
287
+ ## Dataclasses
288
+
289
+ ### MoralcompassTableMeta
290
+
291
+ ```python
292
+ from aimodelshare.moral_compass import MoralcompassTableMeta
293
+
294
+ table = MoralcompassTableMeta(
295
+ table_id="my-table",
296
+ display_name="My Table",
297
+ created_at="2024-01-01T00:00:00Z",
298
+ is_archived=False,
299
+ user_count=42
300
+ )
301
+ ```
302
+
303
+ ### MoralcompassUserStats
304
+
305
+ ```python
306
+ from aimodelshare.moral_compass import MoralcompassUserStats
307
+
308
+ user = MoralcompassUserStats(
309
+ username="user1",
310
+ submission_count=10,
311
+ total_count=100,
312
+ last_updated="2024-01-01T12:00:00Z"
313
+ )
314
+ ```
315
+
316
+ ## Backward Compatibility
317
+
318
+ Both import paths are supported:
319
+
320
+ ```python
321
+ # New path (recommended)
322
+ from aimodelshare.moral_compass import MoralcompassApiClient
323
+
324
+ # Legacy path (backward compatible)
325
+ from moral_compass import MoralcompassApiClient
326
+ ```
327
+
328
+ ## API Methods
329
+
330
+ ### Tables
331
+
332
+ - `create_table(table_id, display_name=None, playground_url=None)` - Create a new table
333
+ - `create_table_for_playground(playground_url, suffix='-mc', display_name=None)` - Create table with naming convention
334
+ - `list_tables(limit=50, last_key=None)` - List tables with pagination
335
+ - `iter_tables(limit=50)` - Iterate all tables with automatic pagination
336
+ - `get_table(table_id)` - Get specific table metadata
337
+ - `patch_table(table_id, display_name=None, is_archived=None)` - Update table metadata
338
+ - `delete_table(table_id)` - Delete a table (requires owner/admin auth)
339
+
340
+ ### Users
341
+
342
+ - `put_user(table_id, username, submission_count, total_count)` - Create/update user
343
+ - `get_user(table_id, username)` - Get user stats
344
+ - `list_users(table_id, limit=50, last_key=None)` - List users with pagination
345
+ - `iter_users(table_id, limit=50)` - Iterate all users with automatic pagination
346
+ - `update_moral_compass(table_id, username, metrics, tasks_completed=0, total_tasks=0, questions_correct=0, total_questions=0, primary_metric=None)` - Update user's moral compass score
347
+
348
+ ### Health
349
+
350
+ - `health()` - Check API health status
351
+
352
+ ## Testing
353
+
354
+ ### Unit Tests
355
+
356
+ ```bash
357
+ # Run all tests except integration tests
358
+ pytest -m "not integration"
359
+ ```
360
+
361
+ ### Integration Tests
362
+
363
+ ```bash
364
+ # Requires deployed API with MORAL_COMPASS_API_BASE_URL set
365
+ export MORAL_COMPASS_API_BASE_URL="https://your-api.example.com"
366
+ pytest -m integration tests/test_moral_compass_client_minimal.py -v
367
+ ```
368
+
369
+ ## CI/CD Integration
370
+
371
+ The deploy-infra workflow automatically:
372
+ 1. Caches terraform outputs
373
+ 2. Verifies API health endpoint is reachable
374
+ 3. Installs the package in editable mode
375
+ 4. Runs integration tests
376
+
377
+ See `.github/workflows/deploy-infra.yml` for details.
378
+
379
+ ## Scripts
380
+
381
+ ### Cache Terraform Outputs
382
+
383
+ ```bash
384
+ bash scripts/cache_terraform_outputs.sh
385
+ ```
386
+
387
+ Exports `MORAL_COMPASS_API_BASE_URL` and writes `infra/terraform_outputs.json`.
388
+
389
+ ### Verify API Health
390
+
391
+ ```bash
392
+ bash scripts/verify_api_reachable.sh [API_BASE_URL]
393
+ ```
394
+
395
+ Checks that the `/health` endpoint is reachable with retries.
396
+
397
+ ## Version
398
+
399
+ Current version: 0.1.0
400
+
401
+ ```python
402
+ from aimodelshare.moral_compass import __version__
403
+ print(__version__) # "0.1.0"
404
+ ```
405
+
406
+ ## License
407
+
408
+ Same as parent aimodelshare package.
@@ -0,0 +1,37 @@
1
+ """
2
+ aimodelshare.moral_compass - Production-ready client for moral_compass REST API
3
+ """
4
+ from ._version import __version__
5
+ from .api_client import (
6
+ MoralcompassApiClient,
7
+ MoralcompassTableMeta,
8
+ MoralcompassUserStats,
9
+ ApiClientError,
10
+ NotFoundError,
11
+ ServerError,
12
+ )
13
+ from .config import get_api_base_url, get_aws_region
14
+ from .challenge import ChallengeManager, JusticeAndEquityChallenge
15
+
16
+ # Optional UI helpers (Gradio may be an optional dependency)
17
+ try:
18
+ from .apps import create_tutorial_app, launch_tutorial_app
19
+ except Exception: # noqa: BLE001
20
+ create_tutorial_app = None
21
+ launch_tutorial_app = None
22
+
23
+ __all__ = [
24
+ "__version__",
25
+ "MoralcompassApiClient",
26
+ "MoralcompassTableMeta",
27
+ "MoralcompassUserStats",
28
+ "ApiClientError",
29
+ "NotFoundError",
30
+ "ServerError",
31
+ "get_api_base_url",
32
+ "get_aws_region",
33
+ "ChallengeManager",
34
+ "JusticeAndEquityChallenge",
35
+ "create_tutorial_app",
36
+ "launch_tutorial_app",
37
+ ]
@@ -0,0 +1,3 @@
1
+ """Version information for aimodelshare.moral_compass"""
2
+
3
+ __version__ = "0.1.1"