tinesight 0.0.2.dev1__py3-none-any.whl

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.
tinesight/__init__.py ADDED
File without changes
tinesight/api.py ADDED
@@ -0,0 +1,14 @@
1
+ import os
2
+
3
+
4
+ class TinesightApiMixin:
5
+
6
+ @property
7
+ def tenant_base_api_uri(self) -> str:
8
+ subdomain = "devapi" if os.getenv("TINESIGHT_DEV") else "api"
9
+ return f"https://{subdomain}.tinesight.com"
10
+
11
+ @property
12
+ def public_ux_api_uri(self) -> str:
13
+ api_ref = "p1pco7l9b1" if os.getenv("TINESIGHT_DEV") else "api"
14
+ return f"https://{api_ref}.execute-api.us-east-1.amazonaws.com"
tinesight/client.py ADDED
@@ -0,0 +1,36 @@
1
+ from collections.abc import Callable
2
+ from functools import partial
3
+ from pathlib import Path
4
+
5
+ import requests
6
+
7
+ from tinesight.api import TinesightApiMixin
8
+
9
+
10
+ class TinesightClient(TinesightApiMixin):
11
+ """
12
+ Client for invoking the Tinesight API from a device.
13
+
14
+ To use this class, a device must be registered with a signed certificate using the
15
+ `TinesightRegistrar`.
16
+
17
+ Examples:
18
+ >>> result = TinesightClient(my_key_path, my_cert_path).classify(my_image_bytes)
19
+ >>> print(result.json)
20
+ >>> {'class': 'deer', 'probability': 0.98}
21
+
22
+ """
23
+
24
+ @property
25
+ def _mtls_post(self) -> Callable:
26
+ """Private wrapper for making invoking requests with a certa"""
27
+ return partial(requests.post, cert=(self.cert_path, self.key_path))
28
+
29
+ def __init__(self, x509_key_path: Path | str, x509_cert_path: Path | str):
30
+ self.key_path: str = str(x509_key_path)
31
+ self.cert_path: str = str(x509_cert_path)
32
+
33
+ def classify(self, image_bytes: bytes) -> requests.Response:
34
+ """Invokes the classification model for the specified image"""
35
+ classification_url = self.tenant_base_api_uri + "/classify/v1"
36
+ return self._mtls_post(classification_url, files={"file": image_bytes})
tinesight/registrar.py ADDED
@@ -0,0 +1,138 @@
1
+ import io
2
+ import os
3
+ from http import HTTPStatus
4
+ from pathlib import Path
5
+
6
+ import requests
7
+ from cryptography import x509
8
+ from cryptography.hazmat.primitives import hashes
9
+ from cryptography.hazmat.primitives import serialization
10
+ from cryptography.x509.oid import NameOID
11
+ from pycognito.utils import RequestsSrpAuth, TokenType
12
+
13
+ from tinesight.api import TinesightApiMixin
14
+
15
+ # Cognito configuration - these should be set before using TinesightRegistrar
16
+ # TODO (NP) figure out how to switch between dev and prod
17
+ COGNITO_USER_POOL_ID = os.environ.get("COGNITO_USER_POOL_ID", "us-east-1_g1yNKyU6h")
18
+ COGNITO_CLIENT_ID = os.environ.get("COGNITO_CLIENT_ID", "3255n0uofh58rqqiqhodtqtdp7")
19
+ AWS_REGION = os.environ.get("AWS_REGION", "us-east-1")
20
+
21
+
22
+ class TinesightRegistrar(TinesightApiMixin):
23
+ """
24
+ Represents a Tinesight tenant, with functionality for generating a signed certificate per device.
25
+
26
+ Examples:
27
+ >>> tsr = TinesightRegistrar()
28
+ >>> tsr.login(my_tinesight_account_user_name, my_tinesight_account_password)
29
+ >>> cert = tsr.register_device(my_local_key_path, device_id)
30
+ >>> with open('mydevice.crt', 'w') as fp:
31
+ >>> fp.write(cert)
32
+
33
+ By following this example you can then instantiate a TinesightClient to invoke the Tinesight API.
34
+ """
35
+
36
+ def __init__(
37
+ self, country_name: str = "US", state: str | None = None, organization: str | None = None
38
+ ):
39
+ if not COGNITO_USER_POOL_ID or not COGNITO_CLIENT_ID:
40
+ raise ValueError(
41
+ "COGNITO_USER_POOL_ID and COGNITO_CLIENT_ID must be set as environment variables"
42
+ )
43
+ self.auth: RequestsSrpAuth | None = None
44
+ self.country_name = country_name
45
+ self.state = state
46
+ self.organization = organization
47
+
48
+ def login(self, username: str, password: str) -> "TinesightRegistrar":
49
+ """
50
+ Basic login to using Cognito IDP with the SRP flow. This method is required to be called
51
+ prior to registering any devices.
52
+ """
53
+ self.auth: RequestsSrpAuth = RequestsSrpAuth(
54
+ username=username,
55
+ password=password,
56
+ user_pool_id=COGNITO_USER_POOL_ID,
57
+ client_id=COGNITO_CLIENT_ID,
58
+ user_pool_region=AWS_REGION,
59
+ auth_token_type=TokenType.ID_TOKEN,
60
+ )
61
+ return self
62
+
63
+ @staticmethod
64
+ def _read_private_key(pem_key_path: Path, key_password: str | None = None):
65
+ # load the private key
66
+ with open(pem_key_path, "rb") as fp:
67
+ pk_contents = fp.read()
68
+ return serialization.load_pem_private_key(
69
+ pk_contents, password=key_password.encode() if key_password else None
70
+ )
71
+
72
+ def register_device(
73
+ self, device_id: str, pem_key_path: Path, key_password: str = None
74
+ ) -> bytes | None:
75
+ """
76
+ Registers a uniquely identified device for your account by creating a certificate
77
+ signing request and returning a signed certificate identifying your device, which will
78
+ enable mTLS invocation of the Tinesight API. Certificates expire after one year.
79
+
80
+ If a device with this device_id has already been registered and its certificate is not expired,
81
+ an exception will be thrown. If the certificate for this device is expired, a new
82
+ certificate will be returned.
83
+
84
+ This method requires the `TinesightRegistrar.login()` method to have been called prior
85
+ to executing.
86
+
87
+ :param device_id: unique device identifier
88
+ :param pem_key_path: path to your secret key
89
+ :param key_password: str, default None - password to your secret key
90
+
91
+ :return: certificate (str)
92
+ """
93
+ if self.auth is None:
94
+ print("Need to call login prior to registering a device")
95
+ return None
96
+
97
+ key = self._read_private_key(pem_key_path, key_password)
98
+
99
+ # create a certificate signing request
100
+ csr = (
101
+ x509.CertificateSigningRequestBuilder()
102
+ .subject_name(
103
+ x509.Name(
104
+ [
105
+ # Provide various details about who we are.
106
+ x509.NameAttribute(NameOID.COUNTRY_NAME, self.country_name),
107
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, self.state or ""),
108
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.organization or ""),
109
+ x509.NameAttribute(NameOID.COMMON_NAME, device_id),
110
+ ]
111
+ )
112
+ )
113
+ .add_extension(
114
+ x509.ExtendedKeyUsage(
115
+ [
116
+ x509.ExtendedKeyUsageOID.CLIENT_AUTH # Specifies the certificate is for client authentication
117
+ ]
118
+ ),
119
+ critical=False,
120
+ )
121
+ .add_extension(
122
+ x509.SubjectAlternativeName([x509.RFC822Name(self.auth.username)]), critical=False
123
+ )
124
+ .sign(key, hashes.SHA256())
125
+ )
126
+
127
+ # request the signed certificate
128
+ target_url = self.public_ux_api_uri + "/register-device/v1"
129
+ response = requests.post(
130
+ target_url,
131
+ data=csr.public_bytes(serialization.Encoding.PEM),
132
+ auth=self.auth,
133
+ )
134
+ if response.status_code == HTTPStatus.OK:
135
+ json_response = response.json()
136
+ return json_response["certificate"].encode("utf-8")
137
+ else:
138
+ return None
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.3
2
+ Name: tinesight
3
+ Version: 0.0.2.dev1
4
+ Summary: Tinesight SDK
5
+ Requires-Dist: cryptography>=46.0.3
6
+ Requires-Dist: pycognito>=2024.5.1
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+
10
+ # Tinesight SDK
11
+
12
+ Standalone python package for Tinesight SDK, published in the Python package `tinesight`.
13
+
14
+ Check out [SDK Documentation](http://devsdk.tinesight.com)
@@ -0,0 +1,7 @@
1
+ tinesight/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tinesight/api.py,sha256=K8AydILylk6iPeKa1_B6Uwf6uNO7SGBt_WzD7F-7l0M,415
3
+ tinesight/client.py,sha256=_J9DrQrjV_JkIyzgPhp7AcPVR7HVZ_MLl541rkI4Ohc,1239
4
+ tinesight/registrar.py,sha256=j2SlkItXhs0e1s3ThrssZpOZUFjSvPFIcqL_5Zphiek,5488
5
+ tinesight-0.0.2.dev1.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
6
+ tinesight-0.0.2.dev1.dist-info/METADATA,sha256=ihqejRsbWRekWA_GRfkcgBz570To8tlaJx35HeTNllE,384
7
+ tinesight-0.0.2.dev1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any