mira-network 0.1.6__tar.gz → 0.1.7__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.
@@ -1,30 +1,43 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mira-network
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Python SDK for Mira Network API
5
- Author-Email: sarim2000 <sarimbleedblue@gmail.com>
5
+ Author-Email: mira-network <sdk@mira.network>
6
6
  License: MIT
7
- Requires-Python: ==3.11.*
7
+ Requires-Python: <3.14,>=3.9
8
8
  Requires-Dist: httpx>=0.28.1
9
9
  Requires-Dist: pydantic>=2.10.4
10
10
  Requires-Dist: typing-extensions>=4.8.0
11
11
  Requires-Dist: requests>=2.32.3
12
12
  Requires-Dist: pytest-cov>=6.0.0
13
+ Requires-Dist: pytest>=8.3.4
14
+ Requires-Dist: pytest-asyncio>=0.25.0
15
+ Requires-Dist: ruff>=0.9.3
16
+ Requires-Dist: black>=25.1.0
13
17
  Description-Content-Type: text/markdown
14
18
 
15
19
  <div align="center">
16
- <img src="https://your-domain.com/logo.png" alt="Mira Network SDK" width="200"/>
20
+ <img src="https://mira.network/_next/static/media/nav_logo.dfc4db53.svg" alt="Mira Network SDK" width="100"/>
17
21
  <h1>Mira Network Python SDK</h1>
18
22
  <p><strong>Your Universal Gateway to AI Language Models</strong></p>
19
23
  </div>
20
24
 
21
25
  <p align="center">
22
- <a href="https://badge.fury.io/py/mira-network"><img src="https://badge.fury.io/py/mira-network.svg" alt="PyPI version"></a>
23
- <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
24
- <a href="https://github.com/mira-network/python-sdk/actions"><img src="https://github.com/mira-network/python-sdk/workflows/tests/badge.svg" alt="Build Status"></a>
25
- <a href="https://codecov.io/gh/mira-network/python-sdk"><img src="https://codecov.io/gh/mira-network/python-sdk/branch/main/graph/badge.svg" alt="Coverage Status"></a>
26
- <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/dm/mira-network.svg" alt="Downloads"></a>
27
- <a href="https://discord.gg/mira-network"><img src="https://img.shields.io/discord/1234567890?color=7289da&label=discord" alt="Discord"></a>
26
+ <!-- Version and Download stats -->
27
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/v/mira-network" alt="PyPI Version"></a>
28
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/dm/mira-network" alt="Downloads"></a>
29
+
30
+ <!-- Package metadata -->
31
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/format/mira-network" alt="Format"></a>
32
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/implementation/mira-network" alt="Implementation"></a>
33
+
34
+ <!-- License -->
35
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/l/mira-network" alt="License"></a>
36
+
37
+ <!-- Build Status -->
38
+ <a href="https://github.com/Aroha-Labs/mira-network/actions/workflows/ci-sdk.yml">
39
+ <img src="https://github.com/Aroha-Labs/mira-network/actions/workflows/ci-sdk.yml/badge.svg" alt="Build Status">
40
+ </a>
28
41
  </p>
29
42
 
30
43
  <p align="center">
@@ -1,16 +1,25 @@
1
1
  <div align="center">
2
- <img src="https://your-domain.com/logo.png" alt="Mira Network SDK" width="200"/>
2
+ <img src="https://mira.network/_next/static/media/nav_logo.dfc4db53.svg" alt="Mira Network SDK" width="100"/>
3
3
  <h1>Mira Network Python SDK</h1>
4
4
  <p><strong>Your Universal Gateway to AI Language Models</strong></p>
5
5
  </div>
6
6
 
7
7
  <p align="center">
8
- <a href="https://badge.fury.io/py/mira-network"><img src="https://badge.fury.io/py/mira-network.svg" alt="PyPI version"></a>
9
- <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
10
- <a href="https://github.com/mira-network/python-sdk/actions"><img src="https://github.com/mira-network/python-sdk/workflows/tests/badge.svg" alt="Build Status"></a>
11
- <a href="https://codecov.io/gh/mira-network/python-sdk"><img src="https://codecov.io/gh/mira-network/python-sdk/branch/main/graph/badge.svg" alt="Coverage Status"></a>
12
- <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/dm/mira-network.svg" alt="Downloads"></a>
13
- <a href="https://discord.gg/mira-network"><img src="https://img.shields.io/discord/1234567890?color=7289da&label=discord" alt="Discord"></a>
8
+ <!-- Version and Download stats -->
9
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/v/mira-network" alt="PyPI Version"></a>
10
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/dm/mira-network" alt="Downloads"></a>
11
+
12
+ <!-- Package metadata -->
13
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/format/mira-network" alt="Format"></a>
14
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/implementation/mira-network" alt="Implementation"></a>
15
+
16
+ <!-- License -->
17
+ <a href="https://pypi.org/project/mira-network/"><img src="https://img.shields.io/pypi/l/mira-network" alt="License"></a>
18
+
19
+ <!-- Build Status -->
20
+ <a href="https://github.com/Aroha-Labs/mira-network/actions/workflows/ci-sdk.yml">
21
+ <img src="https://github.com/Aroha-Labs/mira-network/actions/workflows/ci-sdk.yml/badge.svg" alt="Build Status">
22
+ </a>
14
23
  </p>
15
24
 
16
25
  <p align="center">
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  authors = [
3
- { name = "sarim2000", email = "sarimbleedblue@gmail.com" },
3
+ { name = "mira-network", email = "sdk@mira.network" },
4
4
  ]
5
5
  dependencies = [
6
6
  "httpx>=0.28.1",
@@ -8,12 +8,16 @@ dependencies = [
8
8
  "typing-extensions>=4.8.0",
9
9
  "requests>=2.32.3",
10
10
  "pytest-cov>=6.0.0",
11
+ "pytest>=8.3.4",
12
+ "pytest-asyncio>=0.25.0",
13
+ "ruff>=0.9.3",
14
+ "black>=25.1.0",
11
15
  ]
12
16
  description = "Python SDK for Mira Network API"
13
17
  name = "mira-network"
14
18
  readme = "README.md"
15
- requires-python = "==3.11.*"
16
- version = "0.1.6"
19
+ requires-python = ">=3.9,<3.14"
20
+ version = "0.1.7"
17
21
 
18
22
  [project.license]
19
23
  text = "MIT"
@@ -27,6 +31,11 @@ requires = [
27
31
  [tool.pdm]
28
32
  distribution = true
29
33
 
34
+ [tool.pdm.scripts]
35
+ format = "ruff format ."
36
+ format-check = "ruff format . --check"
37
+ lint = "ruff check ."
38
+
30
39
  [dependency-groups]
31
40
  test = [
32
41
  "pytest>=8.3.4",
@@ -1,5 +1,4 @@
1
1
  from .client import MiraClient
2
- from .sync_client import MiraSyncClient
3
2
  from .models import (
4
3
  Message,
5
4
  ModelProvider,
@@ -1,4 +1,4 @@
1
- from typing import AsyncIterator, Optional, List, Dict, AsyncGenerator, Union, Any
1
+ from typing import AsyncIterator, Optional, List, Dict, Union, Any
2
2
  import httpx
3
3
  from .models import (
4
4
  AiRequest,
@@ -57,8 +57,6 @@ class MiraClient:
57
57
  **kwargs,
58
58
  )
59
59
 
60
- print("\n\n\n======>", request.model_dump(), "\n\n\n")
61
-
62
60
  response = await self._client.post(
63
61
  f"{self.base_url}/v1/chat/completions",
64
62
  headers=self._get_headers(),
@@ -1,4 +1,4 @@
1
- from typing import Optional, List, Dict
1
+ from typing import Optional, List
2
2
  from pydantic import BaseModel, Field, field_validator
3
3
 
4
4
 
@@ -10,7 +10,7 @@ class MiraSyncClient:
10
10
  def __init__(
11
11
  self,
12
12
  base_url: str = "https://apis.mira.network",
13
- api_token: Optional[str] = None,
13
+ api_key: Optional[str] = None,
14
14
  ):
15
15
  """Initialize Mira synchronous client.
16
16
 
@@ -19,7 +19,7 @@ class MiraSyncClient:
19
19
  api_token: Optional API token for authentication
20
20
  """
21
21
  self.base_url = base_url
22
- self.api_token = api_token
22
+ self.api_key = api_key
23
23
  self._session = requests.Session()
24
24
 
25
25
  def __enter__(self):
@@ -30,8 +30,8 @@ class MiraSyncClient:
30
30
 
31
31
  def _get_headers(self) -> Dict[str, str]:
32
32
  headers = {"Content-Type": "application/json"}
33
- if self.api_token:
34
- headers["Authorization"] = f"Bearer {self.api_token}"
33
+ if self.api_key:
34
+ headers["Authorization"] = f"Bearer {self.api_key}"
35
35
  return headers
36
36
 
37
37
  def list_models(self) -> List[str]:
@@ -0,0 +1,126 @@
1
+ import pytest
2
+ import pytest_asyncio
3
+ import httpx
4
+ import json
5
+ from mira_network import MiraClient, Message, ApiTokenRequest
6
+
7
+ pytestmark = pytest.mark.asyncio
8
+
9
+
10
+ @pytest_asyncio.fixture
11
+ async def client():
12
+ async with MiraClient(api_key="test-key") as client:
13
+ yield client
14
+
15
+
16
+ @pytest.fixture
17
+ def mock_response():
18
+ return {"choices": [{"message": {"content": "Hello, world!"}}]}
19
+
20
+
21
+ async def test_client_initialization():
22
+ client = MiraClient(api_key="test-key")
23
+ assert client.api_key == "test-key"
24
+ assert client.base_url == "https://apis.mira.network"
25
+
26
+ custom_client = MiraClient(api_key="test-key", base_url="https://custom.url")
27
+ assert custom_client.base_url == "https://custom.url"
28
+
29
+
30
+ async def test_chat_completions_create(client, mock_response, monkeypatch):
31
+ async def mock_post(*args, **kwargs):
32
+ request = httpx.Request("POST", "https://apis.mira.network/v1/chat/completions")
33
+ return httpx.Response(status_code=200, json=mock_response, request=request)
34
+
35
+ monkeypatch.setattr(httpx.AsyncClient, "post", mock_post)
36
+
37
+ response = await client.chat_completions_create(
38
+ model="test-model", messages=[Message(role="user", content="Hello")]
39
+ )
40
+
41
+ assert response == mock_response
42
+
43
+
44
+ async def test_chat_completions_create_streaming(client, monkeypatch):
45
+ responses = [
46
+ b'{"choices":[{"delta":{"content":"Hello"}}]}\n\n',
47
+ b'{"choices":[{"delta":{"content":" World"}}]}\n\n',
48
+ b'{"choices":[{"delta":{},"finish_reason":"stop"}]}\n\n',
49
+ ]
50
+ response_iter = iter(responses)
51
+
52
+ async def mock_post(*args, **kwargs):
53
+ request = httpx.Request("POST", "https://apis.mira.network/v1/chat/completions")
54
+
55
+ async def aiter_lines():
56
+ for chunk in response_iter:
57
+ yield chunk.decode().strip()
58
+
59
+ response = httpx.Response(
60
+ status_code=200,
61
+ headers={"content-type": "text/event-stream"},
62
+ request=request,
63
+ )
64
+ response.aiter_lines = aiter_lines
65
+ return response
66
+
67
+ monkeypatch.setattr(httpx.AsyncClient, "post", mock_post)
68
+
69
+ stream = await client.chat_completions_create(
70
+ model="test-model",
71
+ messages=[Message(role="user", content="Hello")],
72
+ stream=True,
73
+ )
74
+
75
+ chunks = []
76
+ async for chunk in stream:
77
+ # The client's _format_stream_response wraps the line in a choices array
78
+ # So we need to parse the content string as JSON first
79
+ try:
80
+ parsed = json.loads(chunk["choices"][0]["delta"]["content"])
81
+ if parsed["choices"][0]["delta"].get("content"):
82
+ chunks.append(parsed["choices"][0]["delta"]["content"])
83
+ except (json.JSONDecodeError, KeyError):
84
+ continue
85
+
86
+ assert chunks == ["Hello", " World"]
87
+
88
+
89
+ async def test_create_api_token(client, monkeypatch):
90
+ mock_token_response = {"token": "new-token"}
91
+
92
+ async def mock_post(*args, **kwargs):
93
+ request = httpx.Request("POST", "https://apis.mira.network/v1/api-tokens")
94
+ return httpx.Response(
95
+ status_code=200, json=mock_token_response, request=request
96
+ )
97
+
98
+ monkeypatch.setattr(httpx.AsyncClient, "post", mock_post)
99
+
100
+ response = await client.create_api_token(ApiTokenRequest(description="Test token"))
101
+ assert response == mock_token_response
102
+
103
+
104
+ async def test_list_api_tokens(client, monkeypatch):
105
+ mock_tokens = [{"token": "token1"}, {"token": "token2"}]
106
+
107
+ async def mock_get(*args, **kwargs):
108
+ request = httpx.Request("GET", "https://apis.mira.network/v1/api-tokens")
109
+ return httpx.Response(status_code=200, json=mock_tokens, request=request)
110
+
111
+ monkeypatch.setattr(httpx.AsyncClient, "get", mock_get)
112
+
113
+ response = await client.list_api_tokens()
114
+ assert response == mock_tokens
115
+
116
+
117
+ async def test_error_handling(client, monkeypatch):
118
+ async def mock_error_response(*args, **kwargs):
119
+ raise httpx.HTTPError("API Error")
120
+
121
+ monkeypatch.setattr(httpx.AsyncClient, "post", mock_error_response)
122
+
123
+ with pytest.raises(httpx.HTTPError):
124
+ await client.chat_completions_create(
125
+ model="test-model", messages=[Message(role="user", content="Hello")]
126
+ )
@@ -1,113 +0,0 @@
1
- import pytest
2
- import httpx
3
- from src.mira_network.client import MiraClient
4
- from src.mira_network.models import (
5
- Message,
6
- AiRequest,
7
- ApiTokenRequest,
8
- )
9
-
10
-
11
- @pytest.fixture
12
- def client():
13
- return MiraClient(
14
- base_url="https://mira-network.alts.dev",
15
- api_token="sk-mira-b9ecd5f43ef0363e691322df3295c2b98bebd1c1edb0b6d8",
16
- )
17
-
18
-
19
- @pytest.mark.asyncio
20
- async def test_list_models(client):
21
- result = await client.list_models()
22
- assert isinstance(result, dict)
23
- assert result["object"] == "list"
24
- assert isinstance(result["data"], list)
25
- assert len(result["data"]) > 0
26
- assert all(isinstance(model, dict) for model in result["data"])
27
- assert all("id" in model and "object" in model for model in result["data"])
28
-
29
-
30
- @pytest.mark.asyncio
31
- async def test_generate(client):
32
- request = AiRequest(
33
- model="gpt-4o",
34
- messages=[Message(role="user", content="Hi Who are you!")],
35
- stream=False,
36
- model_provider=None,
37
- )
38
-
39
- result = await client.generate(request)
40
- assert isinstance(result, str)
41
- assert len(result) > 0
42
-
43
-
44
- @pytest.mark.asyncio
45
- async def test_generate_stream(client):
46
- request = AiRequest(
47
- model="gpt-4o",
48
- messages=[Message(role="user", content="Hi!")],
49
- stream=True,
50
- model_provider=None,
51
- )
52
- print("Making generate request with streaming...")
53
- response = await client.generate(request=request)
54
- chunks = []
55
- print("Starting to receive stream chunks...")
56
- async for chunk in response:
57
- print(f"Received chunk: {chunk}")
58
- assert isinstance(chunk, str)
59
- assert len(chunk) > 0
60
- chunks.append(chunk)
61
- print(f"Received {len(chunks)} total chunks")
62
- assert len(chunks) > 0
63
-
64
-
65
- @pytest.mark.asyncio
66
- async def test_list_flows(client):
67
- result = await client.list_flows()
68
- assert isinstance(result, list)
69
-
70
-
71
- # @pytest.mark.asyncio
72
- # async def test_create_and_delete_flow(client):
73
- # # Create flow
74
- # request = FlowRequest(system_prompt="You are a helpful assistant", name="test_flow")
75
-
76
- # flow = await client.create_flow(request)
77
- # assert flow.get("name") == "test_flow"
78
-
79
- # # Delete the created flow
80
- # flow_id = flow.get("id")
81
- # await client.delete_flow(flow_id)
82
-
83
-
84
- @pytest.mark.asyncio
85
- async def test_create_api_token(client):
86
- request = ApiTokenRequest(description="Test token")
87
- result = await client.create_api_token(request)
88
- assert "token" in result
89
-
90
-
91
- @pytest.mark.asyncio
92
- async def test_get_user_credits(client):
93
- result = await client.get_user_credits()
94
- assert "amount" in result
95
-
96
-
97
- @pytest.mark.asyncio
98
- async def test_error_handling(client):
99
- with pytest.raises(httpx.HTTPError):
100
- # Test with invalid model name to trigger error
101
- request = AiRequest(
102
- model="invalid_model",
103
- messages=[Message(role="user", content="Hi!")],
104
- stream=False,
105
- model_provider=None,
106
- )
107
- await client.generate(request)
108
-
109
-
110
- # @pytest.mark.asyncio
111
- # async def test_client_context_manager():
112
- # async with MiraClient("https://mira-client-balancer.alts.dev") as client:
113
- # assert isinstance(client._client, httpx.AsyncClient)
@@ -1,100 +0,0 @@
1
- import pytest
2
- import requests
3
- from src.mira_network.sync_client import MiraSyncClient
4
- from src.mira_network.models import (
5
- Message,
6
- AiRequest,
7
- ApiTokenRequest,
8
- )
9
-
10
-
11
- @pytest.fixture
12
- def client():
13
- return MiraSyncClient(
14
- base_url="https://mira-network.alts.dev",
15
- api_token="sk-mira-b9ecd5f43ef0363e691322df3295c2b98bebd1c1edb0b6d8",
16
- )
17
-
18
-
19
- def test_list_models(client):
20
- result = client.list_models()
21
- assert isinstance(result, dict)
22
- assert result["object"] == "list"
23
- assert isinstance(result["data"], list)
24
- assert len(result["data"]) > 0
25
- assert all(isinstance(model, dict) for model in result["data"])
26
- assert all("id" in model and "object" in model for model in result["data"])
27
-
28
-
29
- def test_generate(client):
30
- request = AiRequest(
31
- model="gpt-4o",
32
- messages=[Message(role="user", content="Hi Who are you!")],
33
- stream=False,
34
- model_provider=None,
35
- )
36
-
37
- result = client.generate(request)
38
- assert isinstance(result, str)
39
- assert len(result) > 0
40
-
41
-
42
- def test_generate_stream(client):
43
- request = AiRequest(
44
- model="gpt-4o",
45
- messages=[Message(role="user", content="Hi!")],
46
- stream=True,
47
- model_provider=None,
48
- )
49
- print("Making generate request with streaming...")
50
- response = client.generate(request=request)
51
- chunks = []
52
- print("Starting to receive stream chunks...")
53
- for chunk in response:
54
- print(f"Received chunk: {chunk}")
55
- assert isinstance(chunk, str)
56
- assert len(chunk) > 0
57
- chunks.append(chunk)
58
- print(f"Received {len(chunks)} total chunks")
59
- assert len(chunks) > 0
60
-
61
-
62
- def test_create_api_token(client):
63
- request = ApiTokenRequest(description="Test token")
64
- result = client.create_api_token(request)
65
- assert "token" in result
66
-
67
-
68
- def test_get_user_credits(client):
69
- result = client.get_user_credits()
70
- assert "amount" in result
71
-
72
-
73
- def test_error_handling(client):
74
- with pytest.raises(requests.HTTPError):
75
- # Test with invalid model name to trigger error
76
- request = AiRequest(
77
- model="invalid_model",
78
- messages=[Message(role="user", content="Hi!")],
79
- stream=False,
80
- model_provider=None,
81
- )
82
- client.generate(request)
83
-
84
-
85
- def test_client_context_manager():
86
- with MiraSyncClient("https://mira-network.alts.dev") as client:
87
- assert isinstance(client._session, requests.Session)
88
- # Make a test request to ensure session works
89
- result = client.list_models()
90
- assert isinstance(result, dict)
91
-
92
-
93
- def test_list_api_tokens(client):
94
- tokens = client.list_api_tokens()
95
- assert isinstance(tokens, list)
96
-
97
-
98
- def test_get_credits_history(client):
99
- history = client.get_credits_history()
100
- assert isinstance(history, list)
File without changes