Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
<h4 align="center">
<p>
<b>English</b>
<p>
</h4>


</div>

# DashScope Python SDK

The DashScope Python SDK provides a comprehensive interface to [Alibaba Cloud Model Studio (Bailian)](https://www.alibabacloud.com/help/en/model-studio/) APIs, covering text generation, multi-modal understanding, embeddings, reranking, image/video generation, speech synthesis & recognition, and more.
Expand Down
6 changes: 3 additions & 3 deletions dashscope/aigc/image_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,16 @@ def _get_input( # pylint: disable=too-many-branches
kwargs["headers"] = headers

def __get_i2i_task(task, model) -> str:
# 处理task参数:优先使用有效的task值
# Handle task parameter: prefer valid task value
if task is not None and task != "":
return task

# 根据model确定任务类型
# Determine task type based on model
if model is not None and model != "":
if "imageedit" in model or "wan2.5-i2i" in model:
return "image2image"

# 默认返回文本到图像任务
# Default to text-to-image task
return ImageSynthesis.task

task = __get_i2i_task(task, model)
Expand Down
59 changes: 59 additions & 0 deletions dashscope/api_entities/aio_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alibaba, Inc. and its affiliates.
"""Shared aiohttp session pool with cached SSL context.

Provides connection reuse across async API calls. Each event loop gets
its own ClientSession (aiohttp sessions are loop-bound). The SSL context
is created once and shared across all sessions.
"""
import asyncio
import ssl
import threading
import weakref
from typing import Optional

import aiohttp
import certifi
Comment thread
lzsweb marked this conversation as resolved.

_shared_ssl_context: Optional[ssl.SSLContext] = None
_aio_sessions: "weakref.WeakKeyDictionary" = weakref.WeakKeyDictionary()
_lock = threading.RLock()


def get_ssl_context() -> ssl.SSLContext:
global _shared_ssl_context
with _lock:
if _shared_ssl_context is None:
_shared_ssl_context = ssl.create_default_context(
cafile=certifi.where(),
)
return _shared_ssl_context


async def get_shared_aio_session() -> aiohttp.ClientSession:
"""Return a shared aiohttp.ClientSession bound to the running event loop.

The session is lazily created on first use and reused for all
subsequent calls on the same event loop. Connection pooling (keep-alive)
is handled by the underlying TCPConnector.
"""
loop = asyncio.get_running_loop()

with _lock:
session = _aio_sessions.get(loop)
if session is not None and not session.closed:
return session

connector = aiohttp.TCPConnector(ssl=get_ssl_context())
session = aiohttp.ClientSession(connector=connector)
_aio_sessions[loop] = session
return session


async def close_shared_aio_session() -> None:
"""Close the shared session for the current event loop."""
loop = asyncio.get_running_loop()
with _lock:
session = _aio_sessions.pop(loop, None)
if session is not None and not session.closed:
await session.close()
Comment thread
lzsweb marked this conversation as resolved.
85 changes: 48 additions & 37 deletions dashscope/api_entities/aiohttp_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import aiohttp

from dashscope.api_entities.aio_session import get_shared_aio_session
from dashscope.api_entities.base_request import AioBaseRequest
from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
from dashscope.common.constants import (
Expand Down Expand Up @@ -38,7 +39,9 @@ def __init__(
api_key (str): The api key.
method (str): The http method(GET|POST).
stream (bool, optional): Is stream request. Defaults to True.
timeout (int, optional): Total request timeout.
timeout (int, optional): Request timeout in seconds. For streaming
requests, this is the idle timeout between chunks (sock_read);
for non-streaming requests, this is the total request timeout.
Defaults to DEFAULT_REQUEST_TIMEOUT_SECONDS.
user_agent (str, optional): Additional user agent string to
append. Defaults to ''.
Expand Down Expand Up @@ -245,43 +248,51 @@ async def _handle_response( # pylint: disable=too-many-branches
)

async def _handle_request(self):
async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=self.timeout),
headers=self.headers,
) as session:
logger.debug("Starting request: %s", self.url)
if self.method == HTTPMethod.POST:
is_form, obj = False, {}
if hasattr(self, "data") and self.data is not None:
is_form, obj = self.data.get_aiohttp_payload()
if is_form:
headers = {**self.headers, **obj.headers}
response = await session.post(
url=self.url,
data=obj,
headers=headers,
)
else:
response = await session.request(
"POST",
url=self.url,
json=obj,
headers=self.headers,
)
elif self.method == HTTPMethod.GET:
params = {}
if hasattr(self, "data") and self.data is not None:
params = getattr(self.data, "parameters", {})
response = await session.get(
session = await get_shared_aio_session()
if self.stream:
request_timeout = aiohttp.ClientTimeout(
total=None,
sock_read=self.timeout,
)
else:
request_timeout = aiohttp.ClientTimeout(total=self.timeout)

logger.debug("Starting request: %s", self.url)
if self.method == HTTPMethod.POST:
is_form, obj = False, {}
if hasattr(self, "data") and self.data is not None:
is_form, obj = self.data.get_aiohttp_payload()
if is_form:
headers = {**self.headers, **obj.headers}
response = await session.post(
url=self.url,
params=params,
headers=self.headers,
data=obj,
headers=headers,
timeout=request_timeout,
)
else:
raise UnsupportedHTTPMethod(
f"Unsupported http method: {self.method}",
response = await session.request(
"POST",
url=self.url,
json=obj,
headers=self.headers,
timeout=request_timeout,
)
logger.debug("Response returned: %s", self.url)
async with response:
async for rsp in self._handle_response(response):
yield rsp
elif self.method == HTTPMethod.GET:
params = {}
if hasattr(self, "data") and self.data is not None:
params = getattr(self.data, "parameters", {})
response = await session.get(
url=self.url,
params=params,
headers=self.headers,
timeout=request_timeout,
)
else:
raise UnsupportedHTTPMethod(
f"Unsupported http method: {self.method}",
)
logger.debug("Response returned: %s", self.url)
async with response:
async for rsp in self._handle_response(response):
yield rsp
36 changes: 18 additions & 18 deletions dashscope/api_entities/encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,77 +115,77 @@ def _generate_iv():

@staticmethod
def _encrypt_text_with_aes(plaintext, key, iv):
"""使用AES-GCM加密数据"""
"""Encrypt data with AES-GCM"""

# 创建AES-GCM加密器
# Create AES-GCM encryptor
aes_gcm = Cipher(
algorithms.AES(key),
modes.GCM(iv, tag=None),
backend=default_backend(),
).encryptor()

# 关联数据设为空(根据需求可调整)
# Set associated data to empty (adjustable as needed)
aes_gcm.authenticate_additional_data(b"")

# 加密数据
# Encrypt data
ciphertext = (
aes_gcm.update(plaintext.encode("utf-8")) + aes_gcm.finalize()
)

# 获取认证标签
# Get authentication tag
tag = aes_gcm.tag

# 组合密文和标签
# Combine ciphertext and tag
encrypted_data = ciphertext + tag

# 返回Base64编码结果
# Return Base64 encoded result
return base64.b64encode(encrypted_data).decode("utf-8")

@staticmethod
def _decrypt_text_with_aes(base64_ciphertext, aes_key, iv):
"""使用AES-GCM解密响应"""
"""Decrypt response with AES-GCM"""

# 解码Base64数据
# Decode Base64 data
encrypted_data = base64.b64decode(base64_ciphertext)

# 分离密文和标签(标签长度16字节)
# Separate ciphertext and tag (tag length is 16 bytes)
ciphertext = encrypted_data[:-16]
tag = encrypted_data[-16:]

# 创建AES-GCM解密器
# Create AES-GCM decryptor
aes_gcm = Cipher(
algorithms.AES(aes_key),
modes.GCM(iv, tag),
backend=default_backend(),
).decryptor()

# 验证关联数据(与加密时一致)
# Verify associated data (same as during encryption)
aes_gcm.authenticate_additional_data(b"")

# 解密数据
# Decrypt data
decrypted_bytes = aes_gcm.update(ciphertext) + aes_gcm.finalize()

# 明文
# Plaintext
plaintext = decrypted_bytes.decode("utf-8")

return json.loads(plaintext)

@staticmethod
def _encrypt_aes_key_with_rsa(aes_key, public_key_str):
"""使用RSA公钥加密AES密钥"""
"""Encrypt AES key with RSA public key"""

# 解码Base64格式的公钥
# Decode Base64 formatted public key
public_key_bytes = base64.b64decode(public_key_str)

# 加载公钥
# Load public key
public_key = serialization.load_der_public_key(
public_key_bytes,
backend=default_backend(),
)

base64_aes_key = base64.b64encode(aes_key).decode("utf-8")

# 使用RSA加密
# Encrypt with RSA
encrypted_bytes = public_key.encrypt(
base64_aes_key.encode("utf-8"),
padding.PKCS1v15(),
Expand Down
Loading
Loading