easyid-python 1.0.2__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.
@@ -0,0 +1,40 @@
1
+ name: Publish Python Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+
13
+ jobs:
14
+ publish:
15
+ if: github.repository == 'easyid-com-cn/easyid-python'
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Setup Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+
27
+ - name: Install build tooling
28
+ run: python -m pip install --upgrade build
29
+
30
+ - name: Install package with test dependencies
31
+ run: python -m pip install -e .[test]
32
+
33
+ - name: Run tests
34
+ run: pytest
35
+
36
+ - name: Build distributions
37
+ run: python -m build
38
+
39
+ - name: Publish to PyPI
40
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EasyID Team
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.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: easyid-python
3
+ Version: 1.0.2
4
+ Summary: Official Python SDK for the EasyID identity verification API.
5
+ Project-URL: Homepage, https://github.com/easyid-com-cn/easyid-python
6
+ Project-URL: Repository, https://github.com/easyid-com-cn/easyid-python
7
+ Project-URL: Documentation, https://www.easyid.com.cn
8
+ Author: EasyID Team
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: easyid,identity,kyc,risk,verification
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: requests<3,>=2.31.0
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest<9,>=8; extra == 'test'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # EasyID Python SDK
30
+
31
+ Official Python SDK for the EasyID identity verification API.
32
+
33
+ EasyID 易验云 focuses on identity verification and security risk control APIs, including real-name verification, liveness detection, face recognition, phone verification, and fraud-risk related capabilities.
34
+
35
+ 中文文档: [README.zh-CN.md](README.zh-CN.md)
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install easyid-python
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ```python
46
+ from easyid import EasyID
47
+
48
+ client = EasyID("ak_xxx", "sk_xxx")
49
+ result = client.idcard.verify2(name="张三", id_number="110101199001011234")
50
+
51
+ print(result.match)
52
+ ```
53
+
54
+ ## Supported APIs
55
+
56
+ - IDCard: `verify2`, `verify3`, `ocr`
57
+ - Phone: `status`, `verify3`
58
+ - Face: `liveness`, `compare`, `verify`
59
+ - Bank: `verify4`
60
+ - Risk: `score`, `store_fingerprint`
61
+ - Billing: `balance`, `records`
62
+
63
+ ## Configuration
64
+
65
+ - `base_url`
66
+ - `timeout`
67
+ - `session`
68
+
69
+ ## Error Handling
70
+
71
+ Service-side business errors raise `APIError`.
72
+
73
+ ```python
74
+ from easyid import APIError
75
+
76
+ try:
77
+ client.phone.status(phone="13800138000")
78
+ except APIError as exc:
79
+ print(exc.code, exc.message, exc.request_id)
80
+ ```
81
+
82
+ ## Security Notice
83
+
84
+ This is a server-side SDK. Never expose `secret` in browsers, mobile apps, or other untrusted clients.
85
+
86
+ ## Official Resources
87
+
88
+ - Official website: `https://www.easyid.com.cn/`
89
+ - GitHub organization: `https://github.com/easyid-com-cn/`
@@ -0,0 +1,61 @@
1
+ # EasyID Python SDK
2
+
3
+ Official Python SDK for the EasyID identity verification API.
4
+
5
+ EasyID 易验云 focuses on identity verification and security risk control APIs, including real-name verification, liveness detection, face recognition, phone verification, and fraud-risk related capabilities.
6
+
7
+ 中文文档: [README.zh-CN.md](README.zh-CN.md)
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install easyid-python
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ from easyid import EasyID
19
+
20
+ client = EasyID("ak_xxx", "sk_xxx")
21
+ result = client.idcard.verify2(name="张三", id_number="110101199001011234")
22
+
23
+ print(result.match)
24
+ ```
25
+
26
+ ## Supported APIs
27
+
28
+ - IDCard: `verify2`, `verify3`, `ocr`
29
+ - Phone: `status`, `verify3`
30
+ - Face: `liveness`, `compare`, `verify`
31
+ - Bank: `verify4`
32
+ - Risk: `score`, `store_fingerprint`
33
+ - Billing: `balance`, `records`
34
+
35
+ ## Configuration
36
+
37
+ - `base_url`
38
+ - `timeout`
39
+ - `session`
40
+
41
+ ## Error Handling
42
+
43
+ Service-side business errors raise `APIError`.
44
+
45
+ ```python
46
+ from easyid import APIError
47
+
48
+ try:
49
+ client.phone.status(phone="13800138000")
50
+ except APIError as exc:
51
+ print(exc.code, exc.message, exc.request_id)
52
+ ```
53
+
54
+ ## Security Notice
55
+
56
+ This is a server-side SDK. Never expose `secret` in browsers, mobile apps, or other untrusted clients.
57
+
58
+ ## Official Resources
59
+
60
+ - Official website: `https://www.easyid.com.cn/`
61
+ - GitHub organization: `https://github.com/easyid-com-cn/`
@@ -0,0 +1,79 @@
1
+ # EasyID Python SDK
2
+
3
+ EasyID Python SDK 是易验云身份验证 API 的官方 Python 客户端。
4
+
5
+ English README: [README.md](README.md)
6
+
7
+ EasyID 提供身份证核验、手机号核验、人脸识别、银行卡核验、风控评分等能力。本 SDK 适用于服务端应用,自动处理认证头、签名和统一响应解析。
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ pip install easyid-python
13
+ ```
14
+
15
+ 如果是开发调试当前仓库,也可以在 `python/` 目录下执行:
16
+
17
+ ```bash
18
+ pip install -e .
19
+ ```
20
+
21
+ ## 快速开始
22
+
23
+ ```python
24
+ from easyid import EasyID, APIError
25
+
26
+ client = EasyID("ak_xxx", "sk_xxx")
27
+
28
+ try:
29
+ result = client.idcard.verify2(name="张三", id_number="110101199001011234")
30
+ print("是否匹配:", result.match)
31
+ except APIError as exc:
32
+ print(exc.code, exc.message, exc.request_id)
33
+ ```
34
+
35
+ ## 已支持接口
36
+
37
+ - `client.idcard.verify2()`:身份证二要素核验
38
+ - `client.idcard.verify3()`:身份证三要素核验
39
+ - `client.idcard.ocr()`:身份证 OCR
40
+ - `client.phone.status()`:手机号状态查询
41
+ - `client.phone.verify3()`:手机号三要素核验
42
+ - `client.face.liveness()`:人脸活体检测
43
+ - `client.face.compare()`:人脸比对
44
+ - `client.face.verify()`:人脸核验
45
+ - `client.bank.verify4()`:银行卡四要素核验
46
+ - `client.risk.score()`:风控评分
47
+ - `client.risk.store_fingerprint()`:存储设备指纹
48
+ - `client.billing.balance()`:查询账户余额
49
+ - `client.billing.records()`:查询账单记录
50
+
51
+ ## 配置项
52
+
53
+ - `base_url`:自定义 API 地址
54
+ - `timeout`:请求超时,单位秒
55
+ - `session`:自定义 `requests.Session`
56
+
57
+ ## 错误处理
58
+
59
+ 服务端业务错误会抛出 `APIError`。
60
+
61
+ ```python
62
+ from easyid import APIError
63
+
64
+ try:
65
+ client.phone.status(phone="13800138000")
66
+ except APIError as exc:
67
+ print(exc.code, exc.message, exc.request_id)
68
+ ```
69
+
70
+ ## 安全说明
71
+
72
+ - 这是服务端 SDK,不要在浏览器、移动端或其他不可信环境暴露 `secret`
73
+ - `key_id` 必须符合 `ak_[0-9a-f]+`
74
+ - SDK 会自动处理 HMAC-SHA256 签名和认证请求头
75
+
76
+ ## 官方资源
77
+
78
+ - 官网:`https://www.easyid.com.cn/`
79
+ - GitHub:`https://github.com/easyid-com-cn/`
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "easyid-python"
7
+ version = "1.0.2"
8
+ description = "Official Python SDK for the EasyID identity verification API."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "EasyID Team" }
14
+ ]
15
+ keywords = ["easyid", "identity", "verification", "kyc", "risk"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Software Development :: Libraries :: Python Modules"
28
+ ]
29
+ dependencies = [
30
+ "requests>=2.31.0,<3"
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ test = [
35
+ "pytest>=8,<9"
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/easyid-com-cn/easyid-python"
40
+ Repository = "https://github.com/easyid-com-cn/easyid-python"
41
+ Documentation = "https://www.easyid.com.cn"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/easyid"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
@@ -0,0 +1,39 @@
1
+ """EasyID Python SDK."""
2
+
3
+ from .bank import BankService, BankVerify4Result
4
+ from .billing import BillingRecord, BillingRecordsResult, BillingService, BillingBalanceResult
5
+ from .client import EasyID
6
+ from .error import APIError, is_api_error
7
+ from .face import CompareResult, FaceService, FaceVerifyResult, LivenessResult
8
+ from .idcard import IDCardOCRResult, IDCardService, IDCardVerifyResult
9
+ from .phone import PhoneService, PhoneStatusResult, PhoneVerify3Result
10
+ from .risk import RiskDetails, RiskScoreResult, RiskService, StoreFingerprintResult
11
+
12
+ __version__ = EasyID.version
13
+
14
+ __all__ = [
15
+ "APIError",
16
+ "BankService",
17
+ "BankVerify4Result",
18
+ "BillingBalanceResult",
19
+ "BillingRecord",
20
+ "BillingRecordsResult",
21
+ "BillingService",
22
+ "CompareResult",
23
+ "EasyID",
24
+ "FaceService",
25
+ "FaceVerifyResult",
26
+ "IDCardOCRResult",
27
+ "IDCardService",
28
+ "IDCardVerifyResult",
29
+ "LivenessResult",
30
+ "PhoneService",
31
+ "PhoneStatusResult",
32
+ "PhoneVerify3Result",
33
+ "RiskDetails",
34
+ "RiskScoreResult",
35
+ "RiskService",
36
+ "StoreFingerprintResult",
37
+ "__version__",
38
+ "is_api_error",
39
+ ]
@@ -0,0 +1,48 @@
1
+ """Bank APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from .transport import Transport
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class BankVerify4Result:
13
+ result: bool
14
+ match: bool
15
+ bank_name: str
16
+ supplier: str
17
+ score: float
18
+ masked_bank_card: str
19
+ card_type: str
20
+
21
+
22
+ class BankService:
23
+ def __init__(self, transport: Transport) -> None:
24
+ self._transport = transport
25
+
26
+ def verify4(
27
+ self,
28
+ *,
29
+ name: str,
30
+ id_number: str,
31
+ bank_card: str,
32
+ mobile: Optional[str] = None,
33
+ trace_id: Optional[str] = None,
34
+ ) -> BankVerify4Result:
35
+ body = {
36
+ "name": name,
37
+ "id_number": id_number,
38
+ "bank_card": bank_card,
39
+ "mobile": mobile,
40
+ "trace_id": trace_id,
41
+ }
42
+ data = self._transport.request_json(
43
+ "POST",
44
+ "/v1/bank/verify4",
45
+ body={key: value for key, value in body.items() if value is not None},
46
+ )
47
+ return BankVerify4Result(**data)
48
+
@@ -0,0 +1,62 @@
1
+ """Billing APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import List
7
+
8
+ from .transport import Transport
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class BillingBalanceResult:
13
+ app_id: str
14
+ available_cents: int
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class BillingRecord:
19
+ id: int
20
+ app_id: str
21
+ request_id: str
22
+ change_cents: int
23
+ balance_before: int
24
+ balance_after: int
25
+ reason: str
26
+ operator: str
27
+ created_at: int
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class BillingRecordsResult:
32
+ total: int
33
+ page: int
34
+ records: List[BillingRecord]
35
+
36
+
37
+ class BillingService:
38
+ def __init__(self, transport: Transport) -> None:
39
+ self._transport = transport
40
+
41
+ def balance(self, *, app_id: str) -> BillingBalanceResult:
42
+ data = self._transport.request_json(
43
+ "GET",
44
+ "/v1/billing/balance",
45
+ query={"app_id": app_id},
46
+ )
47
+ return BillingBalanceResult(**data)
48
+
49
+ def records(self, *, app_id: str, page: int = 1, page_size: int = 20) -> BillingRecordsResult:
50
+ if page <= 0:
51
+ page = 1
52
+ if page_size <= 0:
53
+ page_size = 20
54
+ else:
55
+ page_size = min(page_size, 100)
56
+ data = self._transport.request_json(
57
+ "GET",
58
+ "/v1/billing/records",
59
+ query={"app_id": app_id, "page": page, "page_size": page_size},
60
+ )
61
+ records = [BillingRecord(**record) for record in data.get("records", [])]
62
+ return BillingRecordsResult(total=data["total"], page=data["page"], records=records)
@@ -0,0 +1,56 @@
1
+ """Public client entrypoint for the EasyID SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Optional
7
+
8
+ import requests
9
+
10
+ from .bank import BankService
11
+ from .billing import BillingService
12
+ from .face import FaceService
13
+ from .idcard import IDCardService
14
+ from .phone import PhoneService
15
+ from .risk import RiskService
16
+ from .transport import DEFAULT_BASE_URL, SDK_VERSION, Transport
17
+
18
+ _KEY_ID_RE = re.compile(r"^ak_[0-9a-f]+$")
19
+
20
+
21
+ class EasyID:
22
+ """EasyID API client.
23
+
24
+ The client is safe to reuse across requests.
25
+ """
26
+
27
+ version = SDK_VERSION
28
+
29
+ def __init__(
30
+ self,
31
+ key_id: str,
32
+ secret: str,
33
+ *,
34
+ base_url: str = DEFAULT_BASE_URL,
35
+ timeout: float = 30.0,
36
+ session: Optional[requests.Session] = None,
37
+ ) -> None:
38
+ if not _KEY_ID_RE.fullmatch(key_id):
39
+ raise ValueError(f"easyid: key_id must match ak_<hex>, got: {key_id}")
40
+ if not secret:
41
+ raise ValueError("easyid: secret must not be empty")
42
+
43
+ self._transport = Transport(
44
+ key_id=key_id,
45
+ secret=secret,
46
+ base_url=base_url,
47
+ timeout=timeout,
48
+ session=session,
49
+ )
50
+ self.idcard = IDCardService(self._transport)
51
+ self.phone = PhoneService(self._transport)
52
+ self.face = FaceService(self._transport)
53
+ self.bank = BankService(self._transport)
54
+ self.risk = RiskService(self._transport)
55
+ self.billing = BillingService(self._transport)
56
+
@@ -0,0 +1,20 @@
1
+ """Error types for the EasyID SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class APIError(Exception):
7
+ """Business error returned by the EasyID API."""
8
+
9
+ def __init__(self, code: int, message: str, request_id: str) -> None:
10
+ self.code = code
11
+ self.message = message
12
+ self.request_id = request_id
13
+ super().__init__(f"easyid: code={code} message={message} request_id={request_id}")
14
+
15
+
16
+ def is_api_error(exc: BaseException) -> bool:
17
+ """Return True when *exc* is an :class:`APIError`."""
18
+
19
+ return isinstance(exc, APIError)
20
+
@@ -0,0 +1,85 @@
1
+ """Face APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from .transport import FileInput, Transport, make_multipart_part
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class LivenessResult:
13
+ liveness: bool
14
+ score: float
15
+ method: str
16
+ frames_analyzed: int
17
+ attack_type: Optional[str]
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class CompareResult:
22
+ match: bool
23
+ score: float
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class FaceVerifyResult:
28
+ result: bool
29
+ supplier: str
30
+ score: float
31
+
32
+
33
+ class FaceService:
34
+ def __init__(self, transport: Transport) -> None:
35
+ self._transport = transport
36
+
37
+ def liveness(
38
+ self,
39
+ *,
40
+ media: FileInput,
41
+ mode: Optional[str] = None,
42
+ filename: Optional[str] = None,
43
+ ) -> LivenessResult:
44
+ fields = {}
45
+ if mode is not None:
46
+ fields["mode"] = mode
47
+ data = self._transport.request_multipart(
48
+ "/v1/face/liveness",
49
+ fields=fields,
50
+ files=[make_multipart_part("media", media, filename)],
51
+ )
52
+ return LivenessResult(**data)
53
+
54
+ def compare(
55
+ self,
56
+ *,
57
+ image1: FileInput,
58
+ image2: FileInput,
59
+ filename1: Optional[str] = None,
60
+ filename2: Optional[str] = None,
61
+ ) -> CompareResult:
62
+ data = self._transport.request_multipart(
63
+ "/v1/face/compare",
64
+ files=[
65
+ make_multipart_part("image1", image1, filename1),
66
+ make_multipart_part("image2", image2, filename2),
67
+ ],
68
+ )
69
+ return CompareResult(**data)
70
+
71
+ def verify(
72
+ self,
73
+ *,
74
+ id_number: str,
75
+ media_key: Optional[str] = None,
76
+ callback_url: Optional[str] = None,
77
+ ) -> FaceVerifyResult:
78
+ body = {"id_number": id_number}
79
+ if media_key is not None:
80
+ body["media_key"] = media_key
81
+ if callback_url is not None:
82
+ body["callback_url"] = callback_url
83
+ data = self._transport.request_json("POST", "/v1/face/verify", body=body)
84
+ return FaceVerifyResult(**data)
85
+