swiftapi-python 1.2.0__tar.gz → 1.2.1__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 (24) hide show
  1. swiftapi_python-1.2.1/LICENSE +21 -0
  2. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/PKG-INFO +3 -1
  3. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/pyproject.toml +1 -1
  4. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/__init__.py +1 -1
  5. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/anthropic.py +3 -2
  6. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/client.py +2 -1
  7. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/openai.py +3 -2
  8. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi_python.egg-info/PKG-INFO +3 -1
  9. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi_python.egg-info/SOURCES.txt +8 -1
  10. swiftapi_python-1.2.1/tests/test_anthropic.py +33 -0
  11. swiftapi_python-1.2.1/tests/test_client.py +46 -0
  12. swiftapi_python-1.2.1/tests/test_exceptions.py +69 -0
  13. swiftapi_python-1.2.1/tests/test_openai.py +35 -0
  14. swiftapi_python-1.2.1/tests/test_responses.py +77 -0
  15. swiftapi_python-1.2.1/tests/test_verifier.py +47 -0
  16. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/README.md +0 -0
  17. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/setup.cfg +0 -0
  18. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/enforcement.py +0 -0
  19. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/exceptions.py +0 -0
  20. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/utils.py +0 -0
  21. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi/verifier.py +0 -0
  22. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi_python.egg-info/dependency_links.txt +0 -0
  23. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi_python.egg-info/requires.txt +0 -0
  24. {swiftapi_python-1.2.0 → swiftapi_python-1.2.1}/swiftapi_python.egg-info/top_level.txt +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rayan Pal
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swiftapi-python
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: SwiftAPI Python SDK - AI Action Verification Gateway
5
5
  Author-email: Rayan Pal <rayan@swiftapi.ai>
6
6
  License-Expression: MIT
@@ -19,6 +19,7 @@ Classifier: Topic :: Security
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
+ License-File: LICENSE
22
23
  Requires-Dist: requests>=2.31.0
23
24
  Requires-Dist: pynacl>=1.5.0
24
25
  Requires-Dist: colorama>=0.4.6
@@ -28,6 +29,7 @@ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
28
29
  Requires-Dist: black>=23.0.0; extra == "dev"
29
30
  Requires-Dist: mypy>=1.0.0; extra == "dev"
30
31
  Requires-Dist: types-requests>=2.31.0; extra == "dev"
32
+ Dynamic: license-file
31
33
 
32
34
  # SwiftAPI Python SDK
33
35
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swiftapi-python"
7
- version = "1.2.0"
7
+ version = "1.2.1"
8
8
  description = "SwiftAPI Python SDK - AI Action Verification Gateway"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -29,7 +29,7 @@ Usage:
29
29
  db.update(user_id, data)
30
30
  """
31
31
 
32
- __version__ = "1.2.0"
32
+ __version__ = "1.2.1"
33
33
  __author__ = "Rayan Pal"
34
34
 
35
35
  # Core exports
@@ -17,6 +17,7 @@ import json
17
17
  import requests
18
18
  from typing import List, Dict, Optional, Any
19
19
 
20
+ from . import __version__
20
21
  from .exceptions import SwiftAPIError, AuthenticationError, NetworkError
21
22
 
22
23
 
@@ -113,7 +114,7 @@ class Messages:
113
114
  headers={
114
115
  "X-SwiftAPI-Authority": self._swiftapi_key,
115
116
  "Content-Type": "application/json",
116
- "User-Agent": "swiftapi-python/1.2.0",
117
+ "User-Agent": f"swiftapi-python/{__version__}",
117
118
  },
118
119
  timeout=self._timeout,
119
120
  )
@@ -152,7 +153,7 @@ class Messages:
152
153
  "x-api-key": self._anthropic_key,
153
154
  "anthropic-version": "2023-06-01",
154
155
  "Content-Type": "application/json",
155
- "User-Agent": "swiftapi-python/1.2.0",
156
+ "User-Agent": f"swiftapi-python/{__version__}",
156
157
  },
157
158
  timeout=self._timeout,
158
159
  )
@@ -7,6 +7,7 @@ This module provides the HTTP client for communicating with SwiftAPI.
7
7
  import requests
8
8
  from typing import Dict, Any, Optional, List
9
9
 
10
+ from . import __version__
10
11
  from .exceptions import (
11
12
  SwiftAPIError,
12
13
  AuthenticationError,
@@ -54,7 +55,7 @@ class SwiftAPI:
54
55
  self._session.headers.update({
55
56
  "X-SwiftAPI-Authority": key,
56
57
  "Content-Type": "application/json",
57
- "User-Agent": "swiftapi-python/1.0.0",
58
+ "User-Agent": f"swiftapi-python/{__version__}",
58
59
  })
59
60
 
60
61
  def _request(
@@ -17,6 +17,7 @@ import json
17
17
  import requests
18
18
  from typing import List, Dict, Optional, Any
19
19
 
20
+ from . import __version__
20
21
  from .exceptions import SwiftAPIError, AuthenticationError, NetworkError
21
22
 
22
23
 
@@ -117,7 +118,7 @@ class Completions:
117
118
  headers={
118
119
  "X-SwiftAPI-Authority": self._swiftapi_key,
119
120
  "Content-Type": "application/json",
120
- "User-Agent": "swiftapi-python/1.1.1",
121
+ "User-Agent": f"swiftapi-python/{__version__}",
121
122
  },
122
123
  timeout=self._timeout,
123
124
  )
@@ -152,7 +153,7 @@ class Completions:
152
153
  headers={
153
154
  "Authorization": f"Bearer {self._openai_key}",
154
155
  "Content-Type": "application/json",
155
- "User-Agent": "swiftapi-python/1.1.1",
156
+ "User-Agent": f"swiftapi-python/{__version__}",
156
157
  },
157
158
  timeout=self._timeout,
158
159
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swiftapi-python
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: SwiftAPI Python SDK - AI Action Verification Gateway
5
5
  Author-email: Rayan Pal <rayan@swiftapi.ai>
6
6
  License-Expression: MIT
@@ -19,6 +19,7 @@ Classifier: Topic :: Security
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
+ License-File: LICENSE
22
23
  Requires-Dist: requests>=2.31.0
23
24
  Requires-Dist: pynacl>=1.5.0
24
25
  Requires-Dist: colorama>=0.4.6
@@ -28,6 +29,7 @@ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
28
29
  Requires-Dist: black>=23.0.0; extra == "dev"
29
30
  Requires-Dist: mypy>=1.0.0; extra == "dev"
30
31
  Requires-Dist: types-requests>=2.31.0; extra == "dev"
32
+ Dynamic: license-file
31
33
 
32
34
  # SwiftAPI Python SDK
33
35
 
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  swiftapi/__init__.py
@@ -12,4 +13,10 @@ swiftapi_python.egg-info/PKG-INFO
12
13
  swiftapi_python.egg-info/SOURCES.txt
13
14
  swiftapi_python.egg-info/dependency_links.txt
14
15
  swiftapi_python.egg-info/requires.txt
15
- swiftapi_python.egg-info/top_level.txt
16
+ swiftapi_python.egg-info/top_level.txt
17
+ tests/test_anthropic.py
18
+ tests/test_client.py
19
+ tests/test_exceptions.py
20
+ tests/test_openai.py
21
+ tests/test_responses.py
22
+ tests/test_verifier.py
@@ -0,0 +1,33 @@
1
+ """Tests for SwiftAPI Anthropic drop-in replacement initialization."""
2
+
3
+ import pytest
4
+ from swiftapi import Anthropic
5
+ from swiftapi.exceptions import AuthenticationError
6
+
7
+ VALID_SWIFTAPI_KEY = "swiftapi_live_" + "a" * 64
8
+
9
+
10
+ class TestAnthropicInit:
11
+ def test_requires_swiftapi_key(self):
12
+ with pytest.raises(AuthenticationError, match="swiftapi_key is required"):
13
+ Anthropic(anthropic_key="sk-ant-test")
14
+
15
+ def test_requires_anthropic_key(self):
16
+ with pytest.raises(AuthenticationError, match="anthropic_key is required"):
17
+ Anthropic(swiftapi_key=VALID_SWIFTAPI_KEY)
18
+
19
+ def test_rejects_invalid_swiftapi_key(self):
20
+ with pytest.raises(AuthenticationError, match="must start with"):
21
+ Anthropic(swiftapi_key="invalid_key", anthropic_key="sk-ant-test")
22
+
23
+ def test_accepts_valid_keys(self):
24
+ client = Anthropic(swiftapi_key=VALID_SWIFTAPI_KEY, anthropic_key="sk-ant-test")
25
+ assert client.messages is not None
26
+
27
+ def test_api_key_alias(self):
28
+ client = Anthropic(swiftapi_key=VALID_SWIFTAPI_KEY, api_key="sk-ant-test")
29
+ assert client.messages is not None
30
+
31
+ def test_messages_namespace(self):
32
+ client = Anthropic(swiftapi_key=VALID_SWIFTAPI_KEY, anthropic_key="sk-ant-test")
33
+ assert hasattr(client.messages, "create")
@@ -0,0 +1,46 @@
1
+ """Tests for SwiftAPI client initialization and key validation."""
2
+
3
+ import pytest
4
+ from swiftapi import SwiftAPI, __version__
5
+ from swiftapi.exceptions import AuthenticationError
6
+
7
+ VALID_SWIFTAPI_KEY = "swiftapi_live_" + "a" * 64
8
+
9
+
10
+ class TestClientInit:
11
+ def test_requires_key(self):
12
+ with pytest.raises(AuthenticationError, match="API key is required"):
13
+ SwiftAPI(key="")
14
+
15
+ def test_requires_key_none(self):
16
+ with pytest.raises(AuthenticationError, match="API key is required"):
17
+ SwiftAPI(key=None)
18
+
19
+ def test_rejects_invalid_prefix(self):
20
+ with pytest.raises(AuthenticationError, match="must start with"):
21
+ SwiftAPI(key="sk-invalid-key-format")
22
+
23
+ def test_accepts_valid_key(self):
24
+ client = SwiftAPI(key=VALID_SWIFTAPI_KEY)
25
+ assert client.key == VALID_SWIFTAPI_KEY
26
+
27
+ def test_default_base_url(self):
28
+ client = SwiftAPI(key=VALID_SWIFTAPI_KEY)
29
+ assert client.base_url == "https://swiftapi.ai"
30
+
31
+ def test_custom_base_url(self):
32
+ client = SwiftAPI(key=VALID_SWIFTAPI_KEY, base_url="https://custom.example.com/")
33
+ assert client.base_url == "https://custom.example.com"
34
+
35
+ def test_user_agent_matches_version(self):
36
+ client = SwiftAPI(key=VALID_SWIFTAPI_KEY)
37
+ ua = client._session.headers["User-Agent"]
38
+ assert ua == f"swiftapi-python/{__version__}"
39
+
40
+ def test_authority_header_set(self):
41
+ client = SwiftAPI(key=VALID_SWIFTAPI_KEY)
42
+ assert client._session.headers["X-SwiftAPI-Authority"] == VALID_SWIFTAPI_KEY
43
+
44
+ def test_context_manager(self):
45
+ with SwiftAPI(key=VALID_SWIFTAPI_KEY) as client:
46
+ assert client.key == VALID_SWIFTAPI_KEY
@@ -0,0 +1,69 @@
1
+ """Tests for exception hierarchy and custom attributes."""
2
+
3
+ from swiftapi.exceptions import (
4
+ SwiftAPIError,
5
+ AuthenticationError,
6
+ PolicyViolation,
7
+ SignatureVerificationError,
8
+ AttestationExpiredError,
9
+ AttestationRevokedError,
10
+ RateLimitError,
11
+ NetworkError,
12
+ )
13
+
14
+
15
+ class TestExceptionHierarchy:
16
+ def test_all_inherit_from_base(self):
17
+ exceptions = [
18
+ AuthenticationError,
19
+ PolicyViolation,
20
+ SignatureVerificationError,
21
+ AttestationExpiredError,
22
+ AttestationRevokedError,
23
+ RateLimitError,
24
+ NetworkError,
25
+ ]
26
+ for exc_class in exceptions:
27
+ assert issubclass(exc_class, SwiftAPIError)
28
+
29
+ def test_base_has_status_code(self):
30
+ err = SwiftAPIError("test", status_code=403)
31
+ assert err.status_code == 403
32
+
33
+ def test_base_has_response(self):
34
+ err = SwiftAPIError("test", response={"error": "denied"})
35
+ assert err.response == {"error": "denied"}
36
+
37
+
38
+ class TestPolicyViolation:
39
+ def test_has_action_type(self):
40
+ err = PolicyViolation("denied", action_type="file_delete")
41
+ assert err.action_type == "file_delete"
42
+
43
+ def test_has_denial_reason(self):
44
+ err = PolicyViolation("denied", denial_reason="requires human approval")
45
+ assert err.denial_reason == "requires human approval"
46
+
47
+
48
+ class TestRateLimitError:
49
+ def test_has_retry_after(self):
50
+ err = RateLimitError(retry_after=30)
51
+ assert err.retry_after == 30
52
+
53
+ def test_message_includes_retry(self):
54
+ err = RateLimitError(retry_after=60)
55
+ assert "60s" in str(err)
56
+
57
+ def test_no_retry_after(self):
58
+ err = RateLimitError()
59
+ assert err.retry_after is None
60
+
61
+
62
+ class TestAttestationRevokedError:
63
+ def test_has_jti(self):
64
+ err = AttestationRevokedError(jti="abc-123")
65
+ assert err.jti == "abc-123"
66
+
67
+ def test_message_includes_jti(self):
68
+ err = AttestationRevokedError(jti="abc-123")
69
+ assert "abc-123" in str(err)
@@ -0,0 +1,35 @@
1
+ """Tests for SwiftAPI OpenAI drop-in replacement initialization."""
2
+
3
+ import pytest
4
+ from swiftapi import OpenAI
5
+ from swiftapi.exceptions import AuthenticationError
6
+
7
+ VALID_SWIFTAPI_KEY = "swiftapi_live_" + "a" * 64
8
+
9
+
10
+ class TestOpenAIInit:
11
+ def test_requires_swiftapi_key(self):
12
+ with pytest.raises(AuthenticationError, match="swiftapi_key is required"):
13
+ OpenAI(openai_key="sk-test")
14
+
15
+ def test_requires_openai_key(self):
16
+ with pytest.raises(AuthenticationError, match="openai_key is required"):
17
+ OpenAI(swiftapi_key=VALID_SWIFTAPI_KEY)
18
+
19
+ def test_rejects_invalid_swiftapi_key(self):
20
+ with pytest.raises(AuthenticationError, match="must start with"):
21
+ OpenAI(swiftapi_key="invalid_key", openai_key="sk-test")
22
+
23
+ def test_accepts_valid_keys(self):
24
+ client = OpenAI(swiftapi_key=VALID_SWIFTAPI_KEY, openai_key="sk-test")
25
+ assert client.chat is not None
26
+ assert client.chat.completions is not None
27
+
28
+ def test_api_key_alias(self):
29
+ client = OpenAI(swiftapi_key=VALID_SWIFTAPI_KEY, api_key="sk-test")
30
+ assert client.chat.completions is not None
31
+
32
+ def test_chat_completions_namespace(self):
33
+ client = OpenAI(swiftapi_key=VALID_SWIFTAPI_KEY, openai_key="sk-test")
34
+ assert hasattr(client.chat, "completions")
35
+ assert hasattr(client.chat.completions, "create")
@@ -0,0 +1,77 @@
1
+ """Tests for response objects and void behavior.
2
+
3
+ The void (empty string on policy denial) is the core SwiftAPI value proposition.
4
+ No attestation, no output. These tests document that behavior as intentional.
5
+ """
6
+
7
+ from swiftapi.openai import ChatCompletion, Choice, Message
8
+ from swiftapi.anthropic import MessageResponse, ContentBlock
9
+
10
+
11
+ class TestChatCompletion:
12
+ """OpenAI-compatible response objects."""
13
+
14
+ def test_normal_response(self):
15
+ r = ChatCompletion(content="Hello world", model="gpt-4o")
16
+ assert r.choices[0].message.content == "Hello world"
17
+ assert r.choices[0].finish_reason == "stop"
18
+ assert r.model == "gpt-4o"
19
+
20
+ def test_void_response(self):
21
+ """Policy denial returns empty string. The void IS the feature."""
22
+ r = ChatCompletion(content="", model="gpt-4o")
23
+ assert r.choices[0].message.content == ""
24
+ assert r.choices[0].finish_reason == "void"
25
+
26
+ def test_content_shortcut(self):
27
+ r = ChatCompletion(content="test", model="gpt-4o")
28
+ assert r.content == r.choices[0].message.content
29
+
30
+ def test_attested_flag(self):
31
+ r = ChatCompletion(content="Hi", model="gpt-4o", attested=True, jti="abc-123")
32
+ assert r.attested is True
33
+ assert r.jti == "abc-123"
34
+
35
+ def test_not_attested_by_default(self):
36
+ r = ChatCompletion(content="Hi", model="gpt-4o")
37
+ assert r.attested is False
38
+ assert r.jti is None
39
+
40
+ def test_choice_structure(self):
41
+ r = ChatCompletion(content="test", model="gpt-4o")
42
+ assert len(r.choices) == 1
43
+ assert r.choices[0].index == 0
44
+ assert isinstance(r.choices[0], Choice)
45
+ assert isinstance(r.choices[0].message, Message)
46
+ assert r.choices[0].message.role == "assistant"
47
+
48
+
49
+ class TestMessageResponse:
50
+ """Anthropic-compatible response objects."""
51
+
52
+ def test_normal_response(self):
53
+ r = MessageResponse(content="Hello world", model="claude-sonnet-4-5-20250929")
54
+ assert r.content[0].text == "Hello world"
55
+ assert r.stop_reason == "end_turn"
56
+ assert r.role == "assistant"
57
+
58
+ def test_void_response(self):
59
+ """Policy denial returns empty string. The void IS the feature."""
60
+ r = MessageResponse(content="", model="claude-sonnet-4-5-20250929")
61
+ assert r.content[0].text == ""
62
+ assert r.stop_reason == "void"
63
+
64
+ def test_text_shortcut(self):
65
+ r = MessageResponse(content="test", model="claude-sonnet-4-5-20250929")
66
+ assert r.text == r.content[0].text
67
+
68
+ def test_attested_flag(self):
69
+ r = MessageResponse(content="Hi", model="claude-sonnet-4-5-20250929", attested=True, jti="abc-123")
70
+ assert r.attested is True
71
+ assert r.jti == "abc-123"
72
+
73
+ def test_content_block_structure(self):
74
+ r = MessageResponse(content="test", model="claude-sonnet-4-5-20250929")
75
+ assert len(r.content) == 1
76
+ assert isinstance(r.content[0], ContentBlock)
77
+ assert r.content[0].type == "text"
@@ -0,0 +1,47 @@
1
+ """Tests for Ed25519 attestation verification."""
2
+
3
+ import pytest
4
+ from swiftapi import verify_signature, is_valid, get_public_key
5
+ from swiftapi.exceptions import SignatureVerificationError
6
+
7
+
8
+ class TestPublicKey:
9
+ def test_public_key_exists(self):
10
+ key = get_public_key()
11
+ assert key is not None
12
+ assert len(key) > 0
13
+
14
+ def test_public_key_is_base64(self):
15
+ import base64
16
+ key = get_public_key()
17
+ decoded = base64.b64decode(key)
18
+ assert len(decoded) == 32 # Ed25519 public key is 32 bytes
19
+
20
+
21
+ class TestVerifySignature:
22
+ def test_rejects_missing_signature(self):
23
+ with pytest.raises(SignatureVerificationError, match="Missing signature"):
24
+ verify_signature({})
25
+
26
+ def test_rejects_incomplete_attestation(self):
27
+ with pytest.raises(SignatureVerificationError, match="missing required fields"):
28
+ verify_signature({"signature": "AAAA"})
29
+
30
+ def test_rejects_bad_signature(self, sample_attestation):
31
+ with pytest.raises(SignatureVerificationError):
32
+ verify_signature(sample_attestation)
33
+
34
+ def test_rejects_empty_signature(self):
35
+ with pytest.raises(SignatureVerificationError, match="Missing signature"):
36
+ verify_signature({"signature": "", "jti": "x", "action_fingerprint": "y", "expires_at": "z"})
37
+
38
+
39
+ class TestIsValid:
40
+ def test_returns_false_on_empty(self):
41
+ assert is_valid({}) is False
42
+
43
+ def test_returns_false_on_bad_attestation(self, sample_attestation):
44
+ assert is_valid(sample_attestation) is False
45
+
46
+ def test_returns_false_on_garbage(self):
47
+ assert is_valid({"garbage": True}) is False