aetherx-oracle 0.3.0__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.
- aetherx_oracle-0.3.0/PKG-INFO +108 -0
- aetherx_oracle-0.3.0/README.md +87 -0
- aetherx_oracle-0.3.0/aetherx/__init__.py +5 -0
- aetherx_oracle-0.3.0/aetherx/client.py +118 -0
- aetherx_oracle-0.3.0/aetherx_oracle.egg-info/PKG-INFO +108 -0
- aetherx_oracle-0.3.0/aetherx_oracle.egg-info/SOURCES.txt +10 -0
- aetherx_oracle-0.3.0/aetherx_oracle.egg-info/dependency_links.txt +1 -0
- aetherx_oracle-0.3.0/aetherx_oracle.egg-info/requires.txt +10 -0
- aetherx_oracle-0.3.0/aetherx_oracle.egg-info/top_level.txt +1 -0
- aetherx_oracle-0.3.0/pyproject.toml +30 -0
- aetherx_oracle-0.3.0/setup.cfg +4 -0
- aetherx_oracle-0.3.0/tests/test_sdk.py +144 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aetherx-oracle
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python SDK for the Aether-X Port Congestion Oracle API
|
|
5
|
+
Author: Aether-X Oracle Engine
|
|
6
|
+
License: Proprietary - Machine-to-Machine Data Distribution
|
|
7
|
+
Project-URL: Homepage, https://aether-x-oracle-production.up.railway.app
|
|
8
|
+
Project-URL: Documentation, https://aether-x-oracle-production.up.railway.app/docs
|
|
9
|
+
Project-URL: Terms, https://aether-x-oracle-production.up.railway.app/terms
|
|
10
|
+
Keywords: port,congestion,oracle,logistics,shipping,quant,freight
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: requests>=2.25.0
|
|
14
|
+
Requires-Dist: pydantic>=2.0.0
|
|
15
|
+
Provides-Extra: async
|
|
16
|
+
Requires-Dist: httpx>=0.24.0; extra == "async"
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
19
|
+
Requires-Dist: responses>=0.23; extra == "test"
|
|
20
|
+
Requires-Dist: httpx>=0.24.0; extra == "test"
|
|
21
|
+
|
|
22
|
+
# aetherx-oracle
|
|
23
|
+
|
|
24
|
+
SDK Python oficial para a **Aether-X Port Congestion Oracle API** — sinais preditivos de congestão portuária, atraso de ETA e volatilidade de frete para portos globais.
|
|
25
|
+
|
|
26
|
+
## Instalação
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install aetherx-oracle
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Uso rápido
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from aetherx import OracleClient
|
|
36
|
+
|
|
37
|
+
client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
38
|
+
|
|
39
|
+
risk = client.get_port_risk("BRSSZ")
|
|
40
|
+
|
|
41
|
+
print(risk.port_name) # Santos
|
|
42
|
+
print(risk.country) # Brasil
|
|
43
|
+
print(risk.congestion_score) # 0.78
|
|
44
|
+
print(risk.eta_delay_days) # 1.6
|
|
45
|
+
print(risk.waiting_vessels) # 12
|
|
46
|
+
print(risk.freight_volatility_index) # 0.42
|
|
47
|
+
print(risk.updated_at) # 2026-09-17 15:46:53
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Campos retornados (`PortRisk`)
|
|
51
|
+
|
|
52
|
+
| Campo | Tipo | Descrição |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `port_id` | `str` | UN/LOCODE do porto (ex: `BRSSZ`) |
|
|
55
|
+
| `port_name` | `str` | Nome do porto |
|
|
56
|
+
| `country` | `str` | País do porto |
|
|
57
|
+
| `congestion_score` | `float` | Score de congestão (0.0 a 1.0) |
|
|
58
|
+
| `eta_delay_days` | `float` | Atraso estimado de ETA em dias |
|
|
59
|
+
| `waiting_vessels` | `int` | Navios aguardando/ancorados |
|
|
60
|
+
| `freight_volatility_index` | `float` | Índice de volatilidade de frete |
|
|
61
|
+
| `updated_at` | `str` | Timestamp da última atualização |
|
|
62
|
+
|
|
63
|
+
## Configuração avançada
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
client = OracleClient(
|
|
67
|
+
api_key="SUA_RAPIDAPI_KEY",
|
|
68
|
+
host="aether-x-port-congestion-oracle.p.rapidapi.com", # default
|
|
69
|
+
timeout=30.0, # segundos
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Portos suportados
|
|
74
|
+
|
|
75
|
+
Portos com dados de exemplo: `BRSSZ`, `BRRIO`, `CNSHA`, `CNNGB`, `SGSIN`, `NLRTM`, `USLAX`, `USNYC`, `DEHAM`, `MPTNG`, `AEDXB`, `KRPUS`, `GBLGP`, `ZACPT`, `MXZLO`.
|
|
76
|
+
|
|
77
|
+
Portos não cadastrados retornam uma estimativa global (`country="Global"`).
|
|
78
|
+
|
|
79
|
+
## Uso assíncrono (async)
|
|
80
|
+
|
|
81
|
+
Para consultar múltiplos portos em paralelo (ideal para bots e fundos quantitativos). Requer o extra `async`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install "aetherx-oracle[async]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import asyncio
|
|
89
|
+
from aetherx import OracleClient
|
|
90
|
+
|
|
91
|
+
async def main():
|
|
92
|
+
client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
93
|
+
|
|
94
|
+
# Um porto
|
|
95
|
+
risk = await client.get_port_risk_async("BRSSZ")
|
|
96
|
+
print(risk.congestion_score)
|
|
97
|
+
|
|
98
|
+
# Vários portos em paralelo
|
|
99
|
+
risks = await client.get_ports_risk_async(["BRSSZ", "CNSHA", "NLRTM"])
|
|
100
|
+
for r in risks:
|
|
101
|
+
print(r.port_id, r.congestion_score)
|
|
102
|
+
|
|
103
|
+
asyncio.run(main())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Termos de uso
|
|
107
|
+
|
|
108
|
+
Os sinais são fornecidos "AS IS", sem garantia e **não constituem aconselhamento de investimento**. Consulte os [Termos de Serviço](https://aether-x-oracle-production.up.railway.app/terms).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# aetherx-oracle
|
|
2
|
+
|
|
3
|
+
SDK Python oficial para a **Aether-X Port Congestion Oracle API** — sinais preditivos de congestão portuária, atraso de ETA e volatilidade de frete para portos globais.
|
|
4
|
+
|
|
5
|
+
## Instalação
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install aetherx-oracle
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Uso rápido
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from aetherx import OracleClient
|
|
15
|
+
|
|
16
|
+
client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
17
|
+
|
|
18
|
+
risk = client.get_port_risk("BRSSZ")
|
|
19
|
+
|
|
20
|
+
print(risk.port_name) # Santos
|
|
21
|
+
print(risk.country) # Brasil
|
|
22
|
+
print(risk.congestion_score) # 0.78
|
|
23
|
+
print(risk.eta_delay_days) # 1.6
|
|
24
|
+
print(risk.waiting_vessels) # 12
|
|
25
|
+
print(risk.freight_volatility_index) # 0.42
|
|
26
|
+
print(risk.updated_at) # 2026-09-17 15:46:53
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Campos retornados (`PortRisk`)
|
|
30
|
+
|
|
31
|
+
| Campo | Tipo | Descrição |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `port_id` | `str` | UN/LOCODE do porto (ex: `BRSSZ`) |
|
|
34
|
+
| `port_name` | `str` | Nome do porto |
|
|
35
|
+
| `country` | `str` | País do porto |
|
|
36
|
+
| `congestion_score` | `float` | Score de congestão (0.0 a 1.0) |
|
|
37
|
+
| `eta_delay_days` | `float` | Atraso estimado de ETA em dias |
|
|
38
|
+
| `waiting_vessels` | `int` | Navios aguardando/ancorados |
|
|
39
|
+
| `freight_volatility_index` | `float` | Índice de volatilidade de frete |
|
|
40
|
+
| `updated_at` | `str` | Timestamp da última atualização |
|
|
41
|
+
|
|
42
|
+
## Configuração avançada
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
client = OracleClient(
|
|
46
|
+
api_key="SUA_RAPIDAPI_KEY",
|
|
47
|
+
host="aether-x-port-congestion-oracle.p.rapidapi.com", # default
|
|
48
|
+
timeout=30.0, # segundos
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Portos suportados
|
|
53
|
+
|
|
54
|
+
Portos com dados de exemplo: `BRSSZ`, `BRRIO`, `CNSHA`, `CNNGB`, `SGSIN`, `NLRTM`, `USLAX`, `USNYC`, `DEHAM`, `MPTNG`, `AEDXB`, `KRPUS`, `GBLGP`, `ZACPT`, `MXZLO`.
|
|
55
|
+
|
|
56
|
+
Portos não cadastrados retornam uma estimativa global (`country="Global"`).
|
|
57
|
+
|
|
58
|
+
## Uso assíncrono (async)
|
|
59
|
+
|
|
60
|
+
Para consultar múltiplos portos em paralelo (ideal para bots e fundos quantitativos). Requer o extra `async`:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install "aetherx-oracle[async]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import asyncio
|
|
68
|
+
from aetherx import OracleClient
|
|
69
|
+
|
|
70
|
+
async def main():
|
|
71
|
+
client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
72
|
+
|
|
73
|
+
# Um porto
|
|
74
|
+
risk = await client.get_port_risk_async("BRSSZ")
|
|
75
|
+
print(risk.congestion_score)
|
|
76
|
+
|
|
77
|
+
# Vários portos em paralelo
|
|
78
|
+
risks = await client.get_ports_risk_async(["BRSSZ", "CNSHA", "NLRTM"])
|
|
79
|
+
for r in risks:
|
|
80
|
+
print(r.port_id, r.congestion_score)
|
|
81
|
+
|
|
82
|
+
asyncio.run(main())
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Termos de uso
|
|
86
|
+
|
|
87
|
+
Os sinais são fornecidos "AS IS", sem garantia e **não constituem aconselhamento de investimento**. Consulte os [Termos de Serviço](https://aether-x-oracle-production.up.railway.app/terms).
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Aether-X Port Congestion Oracle - Python SDK client."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
DEFAULT_RAPIDAPI_HOST = "aether-x-port-congestion-oracle.p.rapidapi.com"
|
|
10
|
+
RISK_ENDPOINT = "/v1/port-risk"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PortRisk(BaseModel):
|
|
14
|
+
"""Predictive congestion signal for a single port."""
|
|
15
|
+
|
|
16
|
+
port_id: str = Field(..., description="UN/LOCODE do porto (ex: BRSSZ)")
|
|
17
|
+
port_name: str = Field(..., description="Nome do porto")
|
|
18
|
+
country: str = Field(..., description="País do porto")
|
|
19
|
+
congestion_score: float = Field(..., description="Score de congestão (0.0 a 1.0)")
|
|
20
|
+
eta_delay_days: float = Field(..., description="Atraso estimado de ETA em dias")
|
|
21
|
+
waiting_vessels: int = Field(..., description="Navios aguardando/ancorados")
|
|
22
|
+
freight_volatility_index: float = Field(..., description="Índice de volatilidade de frete")
|
|
23
|
+
updated_at: str = Field(..., description="Timestamp da última atualização")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OracleClient:
|
|
27
|
+
"""Cliente para a API Aether-X Port Congestion Oracle (via RapidAPI).
|
|
28
|
+
|
|
29
|
+
Exemplo:
|
|
30
|
+
>>> from aetherx import OracleClient
|
|
31
|
+
>>> client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
32
|
+
>>> risk = client.get_port_risk("BRSSZ")
|
|
33
|
+
>>> print(risk.congestion_score, risk.waiting_vessels)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
api_key: str,
|
|
39
|
+
host: str = DEFAULT_RAPIDAPI_HOST,
|
|
40
|
+
base_url: Optional[str] = None,
|
|
41
|
+
timeout: float = 30.0,
|
|
42
|
+
) -> None:
|
|
43
|
+
if not api_key:
|
|
44
|
+
raise ValueError("api_key é obrigatória (sua chave da RapidAPI).")
|
|
45
|
+
|
|
46
|
+
self.api_key = api_key
|
|
47
|
+
self.host = host
|
|
48
|
+
self.base_url = (base_url or f"https://{host}").rstrip("/")
|
|
49
|
+
self.timeout = timeout
|
|
50
|
+
|
|
51
|
+
def get_port_risk(self, port_id: str) -> PortRisk:
|
|
52
|
+
"""Retorna o sinal preditivo de congestão para um porto.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
port_id: Código UN/LOCODE do porto (ex: "BRSSZ", "CNSHA").
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
PortRisk com acesso direto via atributo.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ValueError: se port_id for vazio.
|
|
62
|
+
requests.HTTPError: se a API retornar um status de erro.
|
|
63
|
+
"""
|
|
64
|
+
url, headers, params = self._build_request(port_id)
|
|
65
|
+
response = requests.get(
|
|
66
|
+
url, headers=headers, params=params, timeout=self.timeout
|
|
67
|
+
)
|
|
68
|
+
response.raise_for_status()
|
|
69
|
+
return PortRisk.model_validate(response.json())
|
|
70
|
+
|
|
71
|
+
async def get_port_risk_async(self, port_id: str) -> PortRisk:
|
|
72
|
+
"""Versão assíncrona de :meth:`get_port_risk` (requer o extra ``async``).
|
|
73
|
+
|
|
74
|
+
Uso:
|
|
75
|
+
>>> import asyncio
|
|
76
|
+
>>> client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
77
|
+
>>> risk = asyncio.run(client.get_port_risk_async("BRSSZ"))
|
|
78
|
+
|
|
79
|
+
Instale com: ``pip install aetherx-oracle[async]``
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
import httpx
|
|
83
|
+
except ImportError as exc: # pragma: no cover - depende do ambiente
|
|
84
|
+
raise ImportError(
|
|
85
|
+
"httpx é necessário para métodos assíncronos. "
|
|
86
|
+
"Instale com: pip install aetherx-oracle[async]"
|
|
87
|
+
) from exc
|
|
88
|
+
|
|
89
|
+
url, headers, params = self._build_request(port_id)
|
|
90
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
91
|
+
response = await client.get(url, headers=headers, params=params)
|
|
92
|
+
response.raise_for_status()
|
|
93
|
+
return PortRisk.model_validate(response.json())
|
|
94
|
+
|
|
95
|
+
async def get_ports_risk_async(self, port_ids: List[str]) -> List[PortRisk]:
|
|
96
|
+
"""Consulta vários portos em paralelo via ``asyncio.gather``.
|
|
97
|
+
|
|
98
|
+
Ideal para fundos quantitativos e bots que monitoram uma carteira de portos.
|
|
99
|
+
"""
|
|
100
|
+
if not port_ids:
|
|
101
|
+
return []
|
|
102
|
+
return list(
|
|
103
|
+
await asyncio.gather(
|
|
104
|
+
*(self.get_port_risk_async(port_id) for port_id in port_ids)
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _build_request(self, port_id: str):
|
|
109
|
+
if not port_id:
|
|
110
|
+
raise ValueError("port_id é obrigatório (ex: 'BRSSZ').")
|
|
111
|
+
|
|
112
|
+
url = f"{self.base_url}{RISK_ENDPOINT}"
|
|
113
|
+
headers = {
|
|
114
|
+
"x-rapidapi-key": self.api_key,
|
|
115
|
+
"x-rapidapi-host": self.host,
|
|
116
|
+
}
|
|
117
|
+
params = {"port_id": port_id}
|
|
118
|
+
return url, headers, params
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aetherx-oracle
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python SDK for the Aether-X Port Congestion Oracle API
|
|
5
|
+
Author: Aether-X Oracle Engine
|
|
6
|
+
License: Proprietary - Machine-to-Machine Data Distribution
|
|
7
|
+
Project-URL: Homepage, https://aether-x-oracle-production.up.railway.app
|
|
8
|
+
Project-URL: Documentation, https://aether-x-oracle-production.up.railway.app/docs
|
|
9
|
+
Project-URL: Terms, https://aether-x-oracle-production.up.railway.app/terms
|
|
10
|
+
Keywords: port,congestion,oracle,logistics,shipping,quant,freight
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: requests>=2.25.0
|
|
14
|
+
Requires-Dist: pydantic>=2.0.0
|
|
15
|
+
Provides-Extra: async
|
|
16
|
+
Requires-Dist: httpx>=0.24.0; extra == "async"
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
19
|
+
Requires-Dist: responses>=0.23; extra == "test"
|
|
20
|
+
Requires-Dist: httpx>=0.24.0; extra == "test"
|
|
21
|
+
|
|
22
|
+
# aetherx-oracle
|
|
23
|
+
|
|
24
|
+
SDK Python oficial para a **Aether-X Port Congestion Oracle API** — sinais preditivos de congestão portuária, atraso de ETA e volatilidade de frete para portos globais.
|
|
25
|
+
|
|
26
|
+
## Instalação
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install aetherx-oracle
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Uso rápido
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from aetherx import OracleClient
|
|
36
|
+
|
|
37
|
+
client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
38
|
+
|
|
39
|
+
risk = client.get_port_risk("BRSSZ")
|
|
40
|
+
|
|
41
|
+
print(risk.port_name) # Santos
|
|
42
|
+
print(risk.country) # Brasil
|
|
43
|
+
print(risk.congestion_score) # 0.78
|
|
44
|
+
print(risk.eta_delay_days) # 1.6
|
|
45
|
+
print(risk.waiting_vessels) # 12
|
|
46
|
+
print(risk.freight_volatility_index) # 0.42
|
|
47
|
+
print(risk.updated_at) # 2026-09-17 15:46:53
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Campos retornados (`PortRisk`)
|
|
51
|
+
|
|
52
|
+
| Campo | Tipo | Descrição |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `port_id` | `str` | UN/LOCODE do porto (ex: `BRSSZ`) |
|
|
55
|
+
| `port_name` | `str` | Nome do porto |
|
|
56
|
+
| `country` | `str` | País do porto |
|
|
57
|
+
| `congestion_score` | `float` | Score de congestão (0.0 a 1.0) |
|
|
58
|
+
| `eta_delay_days` | `float` | Atraso estimado de ETA em dias |
|
|
59
|
+
| `waiting_vessels` | `int` | Navios aguardando/ancorados |
|
|
60
|
+
| `freight_volatility_index` | `float` | Índice de volatilidade de frete |
|
|
61
|
+
| `updated_at` | `str` | Timestamp da última atualização |
|
|
62
|
+
|
|
63
|
+
## Configuração avançada
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
client = OracleClient(
|
|
67
|
+
api_key="SUA_RAPIDAPI_KEY",
|
|
68
|
+
host="aether-x-port-congestion-oracle.p.rapidapi.com", # default
|
|
69
|
+
timeout=30.0, # segundos
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Portos suportados
|
|
74
|
+
|
|
75
|
+
Portos com dados de exemplo: `BRSSZ`, `BRRIO`, `CNSHA`, `CNNGB`, `SGSIN`, `NLRTM`, `USLAX`, `USNYC`, `DEHAM`, `MPTNG`, `AEDXB`, `KRPUS`, `GBLGP`, `ZACPT`, `MXZLO`.
|
|
76
|
+
|
|
77
|
+
Portos não cadastrados retornam uma estimativa global (`country="Global"`).
|
|
78
|
+
|
|
79
|
+
## Uso assíncrono (async)
|
|
80
|
+
|
|
81
|
+
Para consultar múltiplos portos em paralelo (ideal para bots e fundos quantitativos). Requer o extra `async`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install "aetherx-oracle[async]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import asyncio
|
|
89
|
+
from aetherx import OracleClient
|
|
90
|
+
|
|
91
|
+
async def main():
|
|
92
|
+
client = OracleClient(api_key="SUA_RAPIDAPI_KEY")
|
|
93
|
+
|
|
94
|
+
# Um porto
|
|
95
|
+
risk = await client.get_port_risk_async("BRSSZ")
|
|
96
|
+
print(risk.congestion_score)
|
|
97
|
+
|
|
98
|
+
# Vários portos em paralelo
|
|
99
|
+
risks = await client.get_ports_risk_async(["BRSSZ", "CNSHA", "NLRTM"])
|
|
100
|
+
for r in risks:
|
|
101
|
+
print(r.port_id, r.congestion_score)
|
|
102
|
+
|
|
103
|
+
asyncio.run(main())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Termos de uso
|
|
107
|
+
|
|
108
|
+
Os sinais são fornecidos "AS IS", sem garantia e **não constituem aconselhamento de investimento**. Consulte os [Termos de Serviço](https://aether-x-oracle-production.up.railway.app/terms).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
aetherx/__init__.py
|
|
4
|
+
aetherx/client.py
|
|
5
|
+
aetherx_oracle.egg-info/PKG-INFO
|
|
6
|
+
aetherx_oracle.egg-info/SOURCES.txt
|
|
7
|
+
aetherx_oracle.egg-info/dependency_links.txt
|
|
8
|
+
aetherx_oracle.egg-info/requires.txt
|
|
9
|
+
aetherx_oracle.egg-info/top_level.txt
|
|
10
|
+
tests/test_sdk.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aetherx
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aetherx-oracle"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Python SDK for the Aether-X Port Congestion Oracle API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "Proprietary - Machine-to-Machine Data Distribution" }
|
|
12
|
+
authors = [{ name = "Aether-X Oracle Engine" }]
|
|
13
|
+
keywords = ["port", "congestion", "oracle", "logistics", "shipping", "quant", "freight"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"requests>=2.25.0",
|
|
16
|
+
"pydantic>=2.0.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
async = ["httpx>=0.24.0"]
|
|
21
|
+
test = ["pytest>=7.0", "responses>=0.23", "httpx>=0.24.0"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://aether-x-oracle-production.up.railway.app"
|
|
25
|
+
Documentation = "https://aether-x-oracle-production.up.railway.app/docs"
|
|
26
|
+
Terms = "https://aether-x-oracle-production.up.railway.app/terms"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["."]
|
|
30
|
+
include = ["aetherx*"]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import pytest
|
|
9
|
+
import requests
|
|
10
|
+
import responses
|
|
11
|
+
|
|
12
|
+
from aetherx import OracleClient, PortRisk
|
|
13
|
+
|
|
14
|
+
HOST = "aether-x-port-congestion-oracle.p.rapidapi.com"
|
|
15
|
+
URL = f"https://{HOST}/v1/port-risk"
|
|
16
|
+
|
|
17
|
+
SAMPLE = {
|
|
18
|
+
"port_id": "BRSSZ",
|
|
19
|
+
"port_name": "Santos",
|
|
20
|
+
"country": "Brasil",
|
|
21
|
+
"congestion_score": 0.78,
|
|
22
|
+
"eta_delay_days": 1.6,
|
|
23
|
+
"waiting_vessels": 12,
|
|
24
|
+
"freight_volatility_index": 0.42,
|
|
25
|
+
"updated_at": "2026-09-17 15:46:53",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
FALLBACK = {
|
|
29
|
+
"port_id": "ZZZ99",
|
|
30
|
+
"port_name": "Unknown Port (Global Estimate)",
|
|
31
|
+
"country": "Global",
|
|
32
|
+
"congestion_score": 0.45,
|
|
33
|
+
"eta_delay_days": 1.0,
|
|
34
|
+
"waiting_vessels": 6,
|
|
35
|
+
"freight_volatility_index": 0.35,
|
|
36
|
+
"updated_at": "2026-09-17 15:47:19",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def make_client() -> OracleClient:
|
|
41
|
+
return OracleClient(api_key="test-key")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_empty_api_key_raises():
|
|
45
|
+
with pytest.raises(ValueError):
|
|
46
|
+
OracleClient(api_key="")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_empty_port_id_raises():
|
|
50
|
+
with pytest.raises(ValueError):
|
|
51
|
+
make_client().get_port_risk("")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@responses.activate
|
|
55
|
+
def test_get_port_risk_parses_response():
|
|
56
|
+
responses.add(responses.GET, URL, json=SAMPLE, status=200)
|
|
57
|
+
|
|
58
|
+
risk = make_client().get_port_risk("BRSSZ")
|
|
59
|
+
|
|
60
|
+
assert isinstance(risk, PortRisk)
|
|
61
|
+
assert risk.port_id == "BRSSZ"
|
|
62
|
+
assert risk.port_name == "Santos"
|
|
63
|
+
assert risk.country == "Brasil"
|
|
64
|
+
assert risk.congestion_score == 0.78
|
|
65
|
+
assert risk.eta_delay_days == 1.6
|
|
66
|
+
assert risk.waiting_vessels == 12
|
|
67
|
+
assert risk.freight_volatility_index == 0.42
|
|
68
|
+
assert risk.updated_at == "2026-09-17 15:46:53"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@responses.activate
|
|
72
|
+
def test_sends_rapidapi_headers_and_query_param():
|
|
73
|
+
responses.add(responses.GET, URL, json=SAMPLE, status=200)
|
|
74
|
+
|
|
75
|
+
make_client().get_port_risk("BRSSZ")
|
|
76
|
+
|
|
77
|
+
request = responses.calls[0].request
|
|
78
|
+
assert request.headers["x-rapidapi-key"] == "test-key"
|
|
79
|
+
assert request.headers["x-rapidapi-host"] == HOST
|
|
80
|
+
assert "port_id=BRSSZ" in request.url
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@responses.activate
|
|
84
|
+
def test_fallback_payload_parses():
|
|
85
|
+
responses.add(responses.GET, URL, json=FALLBACK, status=200)
|
|
86
|
+
|
|
87
|
+
risk = make_client().get_port_risk("ZZZ99")
|
|
88
|
+
|
|
89
|
+
assert risk.country == "Global"
|
|
90
|
+
assert risk.congestion_score == 0.45
|
|
91
|
+
assert risk.waiting_vessels == 6
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@responses.activate
|
|
95
|
+
def test_http_error_is_raised():
|
|
96
|
+
responses.add(responses.GET, URL, json={"detail": "boom"}, status=500)
|
|
97
|
+
|
|
98
|
+
with pytest.raises(requests.HTTPError):
|
|
99
|
+
make_client().get_port_risk("BRSSZ")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _patch_httpx(monkeypatch, handler):
|
|
103
|
+
real_async_client = httpx.AsyncClient
|
|
104
|
+
transport = httpx.MockTransport(handler)
|
|
105
|
+
|
|
106
|
+
def factory(*args, **kwargs):
|
|
107
|
+
kwargs["transport"] = transport
|
|
108
|
+
return real_async_client(*args, **kwargs)
|
|
109
|
+
|
|
110
|
+
monkeypatch.setattr(httpx, "AsyncClient", factory)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_get_port_risk_async_parses(monkeypatch):
|
|
114
|
+
def handler(request):
|
|
115
|
+
assert request.headers["x-rapidapi-key"] == "test-key"
|
|
116
|
+
assert request.headers["x-rapidapi-host"] == HOST
|
|
117
|
+
return httpx.Response(200, json=SAMPLE)
|
|
118
|
+
|
|
119
|
+
_patch_httpx(monkeypatch, handler)
|
|
120
|
+
|
|
121
|
+
risk = asyncio.run(make_client().get_port_risk_async("BRSSZ"))
|
|
122
|
+
|
|
123
|
+
assert risk.port_name == "Santos"
|
|
124
|
+
assert risk.waiting_vessels == 12
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_get_ports_risk_async_gathers_multiple(monkeypatch):
|
|
128
|
+
def handler(request):
|
|
129
|
+
port_id = dict(request.url.params)["port_id"]
|
|
130
|
+
payload = {**SAMPLE, "port_id": port_id, "port_name": f"Port {port_id}"}
|
|
131
|
+
return httpx.Response(200, json=payload)
|
|
132
|
+
|
|
133
|
+
_patch_httpx(monkeypatch, handler)
|
|
134
|
+
|
|
135
|
+
results = asyncio.run(
|
|
136
|
+
make_client().get_ports_risk_async(["BRSSZ", "CNSHA", "NLRTM"])
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
assert len(results) == 3
|
|
140
|
+
assert [r.port_id for r in results] == ["BRSSZ", "CNSHA", "NLRTM"]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_get_ports_risk_async_empty_list():
|
|
144
|
+
assert asyncio.run(make_client().get_ports_risk_async([])) == []
|