clarity-api 0.0.2__py2.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.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+ from clarity_api.version import __version__, __author__, __credits__
4
+ from clarity_api.clarity_api import Api
5
+ from clarity_api.clarity_models import InputModel, Response
6
+
7
+ """
8
+ Clarity API
9
+
10
+ A Python Library for Exporting Data from Microsoft Clarity
11
+ """
12
+
13
+ __version__ = __version__
14
+ __author__ = __author__
15
+ __credits__ = __credits__
16
+
17
+ __all__ = [
18
+ "Api",
19
+ ]
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/python
2
+ # coding: utf-8
3
+
4
+ import requests
5
+ import urllib3
6
+ from typing import Union
7
+ from pydantic import ValidationError
8
+
9
+ try:
10
+ from clarity_api.clarity_models import InputModel, Response
11
+ except ModuleNotFoundError:
12
+ from clarity_models import InputModel, Response
13
+ try:
14
+ from clarity_api.decorators import require_auth
15
+ except ModuleNotFoundError:
16
+ from decorators import require_auth
17
+ try:
18
+ from clarity_api.exceptions import (
19
+ AuthError,
20
+ UnauthorizedError,
21
+ ParameterError,
22
+ MissingParameterError,
23
+ )
24
+ except ModuleNotFoundError:
25
+ from exceptions import (
26
+ AuthError,
27
+ UnauthorizedError,
28
+ ParameterError,
29
+ MissingParameterError,
30
+ )
31
+ try:
32
+ from clarity_api.utils import process_response
33
+ except ModuleNotFoundError:
34
+ from utils import process_response
35
+
36
+
37
+ class Api(object):
38
+
39
+ def __init__(
40
+ self,
41
+ url: str = None,
42
+ token: str = None,
43
+ verify: bool = True,
44
+ ):
45
+ if url is None:
46
+ raise MissingParameterError
47
+
48
+ self._session = requests.Session()
49
+ self.url = url
50
+ self.headers = None
51
+ self.verify = verify
52
+
53
+ if self.verify is False:
54
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
55
+
56
+ if token:
57
+ self.headers = {
58
+ "Authorization": f"Bearer {token}",
59
+ "Content-Type": "application/json",
60
+ }
61
+ else:
62
+ raise MissingParameterError
63
+
64
+ response = self._session.get(
65
+ url=f"{self.url}/projects", headers=self.headers, verify=self.verify
66
+ )
67
+
68
+ if response.status_code == 403:
69
+ print(f"Unauthorized Error: {response.content}")
70
+ raise UnauthorizedError
71
+ elif response.status_code == 401:
72
+ print(f"Authentication Error: {response.content}")
73
+ raise AuthError
74
+ elif response.status_code == 404:
75
+ print(f"Parameter Error: {response.content}")
76
+ raise ParameterError
77
+
78
+ ####################################################################################################################
79
+ # Data Export API #
80
+ ####################################################################################################################
81
+ @require_auth
82
+ def get_data_export(self, **kwargs) -> Union[Response, requests.Response]:
83
+ """
84
+ Retrieve data insights for a project
85
+
86
+ Args:
87
+ **kwargs: Additional keyword arguments to initialize the BranchModel.
88
+
89
+ Returns:
90
+ Response: The response object from the GET request.
91
+
92
+ Raises:
93
+ ParameterError: If the provided parameters are invalid based on the BranchModel.
94
+ """
95
+ input_model = InputModel(**kwargs)
96
+ try:
97
+ response = self._session.get(
98
+ url=f"{self.url}/export-data/api/v1/project-live-insights",
99
+ params=input_model.api_parameters,
100
+ headers=self.headers,
101
+ verify=self.verify,
102
+ )
103
+
104
+ except ValidationError as e:
105
+ raise ParameterError(f"Invalid parameters: {e.errors()}")
106
+ response = process_response(response=response)
107
+ return response
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/python
2
+ # coding: utf-8
3
+ import logging
4
+
5
+ from typing import Union, Dict, Optional, Any, List
6
+ from pydantic import (
7
+ BaseModel,
8
+ ConfigDict,
9
+ AliasChoices,
10
+ Field,
11
+ field_validator,
12
+ )
13
+
14
+ try:
15
+ from clarity_api.decorators import require_auth
16
+ except ModuleNotFoundError:
17
+ pass
18
+ try:
19
+ from clarity_api.exceptions import (
20
+ AuthError,
21
+ UnauthorizedError,
22
+ ParameterError,
23
+ MissingParameterError,
24
+ )
25
+ except ModuleNotFoundError:
26
+ pass
27
+
28
+ logging.basicConfig(
29
+ level=logging.ERROR, format="%(asctime)s - %(levelname)s - %(message)s"
30
+ )
31
+
32
+
33
+ class InputModel(BaseModel):
34
+ """
35
+ Pydantic model representing information about a branch.
36
+
37
+ Attributes:
38
+ numOfDays (Union[int, str]): The number of days to return.
39
+ dimension1 (str, optional): The first dimension parameters.
40
+ dimension2 (str, optional): The second dimension parameters.
41
+ dimension3 (str, optional): The third dimension parameters.
42
+ api_parameters (str): Additional API parameters for the group.
43
+
44
+ """
45
+
46
+ model_config = ConfigDict(extra="forbid", validate_assignment=True)
47
+
48
+ numOfDays: Optional[Union[int, str]] = Field(
49
+ description="Number of days to save",
50
+ validation_alias=AliasChoices("numOfDays", "number_of_days"),
51
+ default=None,
52
+ )
53
+ dimension1: Optional[str] = Field(
54
+ description="Dimension 1",
55
+ validation_alias=AliasChoices("dimension1", "dimension_1"),
56
+ default=None,
57
+ )
58
+ dimension2: Optional[str] = Field(
59
+ description="Dimension 2",
60
+ validation_alias=AliasChoices("dimension2", "dimension_2"),
61
+ default=None,
62
+ )
63
+ dimension3: Optional[str] = Field(
64
+ description="Dimension 3",
65
+ validation_alias=AliasChoices("dimension3", "dimension_3"),
66
+ default=None,
67
+ )
68
+ api_parameters: Optional[Dict] = Field(description="API Parameters", default=None)
69
+
70
+ def model_post_init(self, __context):
71
+ """
72
+ Build the API parameters
73
+ """
74
+ self.api_parameters = {}
75
+ if self.numOfDays:
76
+ self.api_parameters["numOfDays"] = self.numOfDays
77
+ if self.dimension1:
78
+ self.api_parameters["dimension1"] = self.dimension1
79
+ if self.dimension2:
80
+ self.api_parameters["dimension2"] = self.dimension2
81
+ if self.dimension3:
82
+ self.api_parameters["dimension3"] = self.dimension3
83
+
84
+ @field_validator("numOfDays", mode="before")
85
+ def validate_number_of_days(cls, v):
86
+ """
87
+ Validate the 'number_of_days' parameter to ensure it is a valid integer.
88
+
89
+ Args:
90
+ - v: The value of 'number_of_days'.
91
+
92
+ Returns:
93
+ - int: The validated 'number_of_days'.
94
+
95
+ Raises:
96
+ - ParameterError: If 'number_of_days' is not a valid integer.
97
+ """
98
+ try:
99
+ v = int(v)
100
+ except Exception as e:
101
+ raise e
102
+ return v
103
+
104
+ @field_validator("dimension1", "dimension2", "dimension3", mode="before")
105
+ def validate_dimensions(cls, v):
106
+ """
107
+ Validate the 'dimensions' parameter to ensure it is a valid option.
108
+
109
+ Args:
110
+ - v: The value of 'dimensions'.
111
+
112
+ Returns:
113
+ - str: The validated 'dimensions'.
114
+
115
+ Raises:
116
+ - ParameterError: If 'dimensions' is not a valid option.
117
+ """
118
+ if v:
119
+ valid_dimensions = {
120
+ "browser": "Browser",
121
+ "device": "Device",
122
+ "country": "Country",
123
+ "os": "OS",
124
+ "source": "Source",
125
+ "medium": "Medium",
126
+ "campaign": "Campaign",
127
+ "channel": "Channel",
128
+ "url": "URL",
129
+ }
130
+ try:
131
+ return valid_dimensions[v.lower()]
132
+ except KeyError:
133
+ raise ValueError("Invalid dimension")
134
+
135
+
136
+ class Information(BaseModel):
137
+ model_config = ConfigDict(extra="allow")
138
+ totalSessionCount: str = Field(
139
+ default=None, description="The total number of sessions."
140
+ )
141
+ totalBotSessionCount: str = Field(
142
+ default=None, description="The total number of bot sessions."
143
+ )
144
+ distantUserCount: str = Field(default=None, description="The distant user count.")
145
+ PagesPerSessionPercentage: float = Field(
146
+ default=None, description="The pages per session percentage."
147
+ )
148
+ OS: str = Field(default=None, description="The operating system.")
149
+
150
+
151
+ class Metric(BaseModel):
152
+ model_config = ConfigDict(extra="allow")
153
+ metricName: str = Field(
154
+ default=None, description="The name of the returned metric."
155
+ )
156
+ information: List[Information] = Field(
157
+ default=None, description="Result containing available responses."
158
+ )
159
+
160
+
161
+ class Response(BaseModel):
162
+ data: Optional[List[Metric]] = Field(default=None, description="Metrics returned.")
163
+ error: Optional[Any] = Field(default=None, description="Response error code")
164
+ status_code: Union[str, int] = Field(
165
+ default=None, description="Response status code"
166
+ )
167
+ json_output: Optional[Union[List, Dict]] = Field(
168
+ default=None, description="Response JSON data"
169
+ )
170
+ raw_output: Optional[bytes] = Field(default=None, description="Response Raw bytes")
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/python
2
+ # coding: utf-8
3
+
4
+ import functools
5
+
6
+ try:
7
+ from clarity_api.exceptions import LoginRequiredError
8
+ except ModuleNotFoundError:
9
+ from exceptions import LoginRequiredError
10
+
11
+
12
+ def require_auth(function):
13
+ """
14
+ Wraps API calls in function that ensures headers are passed
15
+ with a token
16
+ """
17
+
18
+ @functools.wraps(function)
19
+ def wrapper(self, *args, **kwargs):
20
+ if not self.headers:
21
+ raise LoginRequiredError
22
+ return function(self, *args, **kwargs)
23
+
24
+ return wrapper
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/python
2
+ # coding: utf-8
3
+
4
+
5
+ class AuthError(Exception):
6
+ """
7
+ Authentication error
8
+ """
9
+
10
+ pass
11
+
12
+
13
+ class UnauthorizedError(AuthError):
14
+ """
15
+ Unauthorized error
16
+ """
17
+
18
+ pass
19
+
20
+
21
+ class MissingParameterError(Exception):
22
+ """
23
+ Missing Parameter error
24
+ """
25
+
26
+ pass
27
+
28
+
29
+ class ParameterError(Exception):
30
+ """
31
+ Parameter error
32
+ """
33
+
34
+ pass
35
+
36
+
37
+ class LoginRequiredError(Exception):
38
+ """
39
+ Authentication error
40
+ """
41
+
42
+ pass
clarity_api/utils.py ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/python
2
+ # coding: utf-8
3
+ import logging
4
+ from typing import Union
5
+
6
+ import requests
7
+
8
+ try:
9
+ from clarity_api.clarity_models import (
10
+ Response,
11
+ )
12
+ except ModuleNotFoundError:
13
+ from clarity_models import (
14
+ Response,
15
+ )
16
+ logging.basicConfig(
17
+ level=logging.ERROR, format="%(asctime)s - %(levelname)s - %(message)s"
18
+ )
19
+
20
+
21
+ def process_response(response: requests.Response) -> Union[Response, requests.Response]:
22
+ response_error = None
23
+ try:
24
+ response.raise_for_status()
25
+ except Exception as response_error:
26
+ logging.error(f"Response Error: {response_error}")
27
+ status_code = response.status_code
28
+ raw_output = response.content
29
+ headers = response.headers
30
+ try:
31
+ response = response.json()
32
+ except Exception as response_error:
33
+ logging.error(f"JSON Conversion Error: {response_error}")
34
+ try:
35
+ response = Response(
36
+ information=response,
37
+ status_code=status_code,
38
+ raw_output=raw_output,
39
+ json_output=response,
40
+ headers=headers,
41
+ error=response_error,
42
+ )
43
+ except Exception as response_error:
44
+ logging.error(f"Response Model Application Error: {response_error}")
45
+
46
+ return response
clarity_api/version.py ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ __version__ = "0.0.2"
5
+ __author__ = "Audel Rouhi"
6
+ __credits__ = "Audel Rouhi"
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-2023 Audel Rouhi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.1
2
+ Name: clarity-api
3
+ Version: 0.0.2
4
+ Summary: Microsoft Clarity Data Export API
5
+ Home-page: https://github.com/Knuckles-Team/clarity-api
6
+ Author: Audel Rouhi
7
+ Author-email: knucklessg1@gmail.com
8
+ License: MIT
9
+ Platform: UNKNOWN
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: License :: Public Domain
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: pydantic[email] (>=2.5.2)
22
+ Requires-Dist: requests (>=2.28.1)
23
+ Requires-Dist: urllib3 (>=1.26.13)
24
+
25
+ # Microsoft Clarity API
26
+
27
+ ![PyPI - Version](https://img.shields.io/pypi/v/clarity-api)
28
+ ![PyPI - Downloads](https://img.shields.io/pypi/dd/clarity-api)
29
+ ![GitHub Repo stars](https://img.shields.io/github/stars/Knuckles-Team/clarity-api)
30
+ ![GitHub forks](https://img.shields.io/github/forks/Knuckles-Team/clarity-api)
31
+ ![GitHub contributors](https://img.shields.io/github/contributors/Knuckles-Team/clarity-api)
32
+ ![PyPI - License](https://img.shields.io/pypi/l/clarity-api)
33
+ ![GitHub](https://img.shields.io/github/license/Knuckles-Team/clarity-api)
34
+
35
+ ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/Knuckles-Team/clarity-api)
36
+ ![GitHub pull requests](https://img.shields.io/github/issues-pr/Knuckles-Team/clarity-api)
37
+ ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/Knuckles-Team/clarity-api)
38
+ ![GitHub issues](https://img.shields.io/github/issues/Knuckles-Team/clarity-api)
39
+
40
+ ![GitHub top language](https://img.shields.io/github/languages/top/Knuckles-Team/clarity-api)
41
+ ![GitHub language count](https://img.shields.io/github/languages/count/Knuckles-Team/clarity-api)
42
+ ![GitHub repo size](https://img.shields.io/github/repo-size/Knuckles-Team/clarity-api)
43
+ ![GitHub repo file count (file type)](https://img.shields.io/github/directory-file-count/Knuckles-Team/clarity-api)
44
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/clarity-api)
45
+ ![PyPI - Implementation](https://img.shields.io/pypi/implementation/clarity-api)
46
+
47
+ *Version: 0.0.2*
48
+
49
+ **Microsoft Clarity Data Export API**
50
+
51
+ This Python library allows you to work with the dashboard data. The data can be structured over a specified date range
52
+ and can break down insights by up to three dimensions.
53
+
54
+ Find out more about the [Clarity Data Export API](https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-data-export-api)
55
+
56
+ This repository is actively maintained - Contributions are welcome!
57
+
58
+ <details>
59
+ <summary><b>Getting Started:</b></summary>
60
+
61
+ ### Prerequisites
62
+ - An active Microsoft Clarity account. [Learn how to sign up for Clarity](https://clarity.microsoft.com).
63
+ - An API access token generated by the project's admin from the settings page.
64
+ - Python3.8+
65
+
66
+ ### Obtaining Access Tokens
67
+ **Note**: Only project admins can manage access tokens.
68
+
69
+ 1. Go to your Clarity project. Select `Settings` -> `Data Export` -> `Generate new API token`.
70
+ 2. Provide a descriptive name for the token for easy identification.
71
+
72
+ ## Parameters
73
+ - `numOfDays`: (1, 2, or 3) The number of days for the data export since the API call, relating to the last 24, 48, or 72 hours, respectively.
74
+ - `dimension1`: The first dimension to break down insights.
75
+ - `dimension2`: The second dimension to break down insights.
76
+ - `dimension3`: The third dimension to break down insights.
77
+
78
+ #### Dimension Options:
79
+ - Browser
80
+ - Device
81
+ - Country
82
+ - OS
83
+ - Source
84
+ - Medium
85
+ - Campaign
86
+ - Channel
87
+ - URL
88
+
89
+ </details>
90
+
91
+ <details>
92
+ <summary><b>Usage:</b></summary>
93
+
94
+ ```python
95
+ #!/usr/bin/python
96
+ # coding: utf-8
97
+ import clarity_api
98
+
99
+ # Use token generated from the steps above
100
+ token = "<TOKEN>"
101
+ url = "https://www.clarity.ms"
102
+ client = clarity_api.Api(url=url, token=token)
103
+
104
+ data = client.get_data_export(number_of_days=2, dimension_1="OS", dimension_2="Channel")
105
+ print("Pydantic Object:", data)
106
+ print("Raw Request Output:", data.raw_output)
107
+ print("JSON Request Output:", data.json_output)
108
+ print("Pydantic Object Model Dump:", data.model_dump())
109
+ print("Request Status Code:", data.status_code)
110
+ print("Request Error:", data.error)
111
+ ```
112
+
113
+ </details>
114
+
115
+ <details>
116
+ <summary><b>Installation Instructions:</b></summary>
117
+
118
+ Install Python Package
119
+
120
+ ```bash
121
+ python -m pip install clarity-api
122
+ ```
123
+
124
+ </details>
125
+
126
+ <details>
127
+ <summary><b>Tests:</b></summary>
128
+
129
+ pre-commit check
130
+ ```bash
131
+ pre-commit run --all-files
132
+ ```
133
+
134
+ pytest
135
+ ```bash
136
+ pytest ./test/test_clarity_models.py
137
+ ```
138
+ </details>
139
+
140
+
141
+ <details>
142
+ <summary><b>Repository Owners:</b></summary>
143
+
144
+
145
+ <img width="100%" height="180em" src="https://github-readme-stats.vercel.app/api?username=Knucklessg1&show_icons=true&hide_border=true&&count_private=true&include_all_commits=true" />
146
+
147
+ ![GitHub followers](https://img.shields.io/github/followers/Knucklessg1)
148
+ ![GitHub User's stars](https://img.shields.io/github/stars/Knucklessg1)
149
+ </details>
150
+
151
+
@@ -0,0 +1,12 @@
1
+ clarity_api/__init__.py,sha256=1RbQDB040YNxdutHk3zNzd-3rpEAZXxdsq4HxJzUdmc,391
2
+ clarity_api/clarity_api.py,sha256=RNROb_rZ2d-iMQicWI2MGV3lJ_eq7OENFuHOtzuZNPE,3360
3
+ clarity_api/clarity_models.py,sha256=llLMupvobbtAaa0FFDD7NFUEity3BUHBmiQcf9Ac1nI,5269
4
+ clarity_api/decorators.py,sha256=teR4yHuA0h7QxKJFkwe4u_2n_-BR-oMXRZw1hhCCf_s,522
5
+ clarity_api/exceptions.py,sha256=y0R-KHDuSSh1gmeRSuzlFrq6UHl8FPffi2-Sgq1AeIM,469
6
+ clarity_api/utils.py,sha256=SnX5csZ7BP2mdx6ru904MtrOsIaJxTyalR5h-r7jWeE,1254
7
+ clarity_api/version.py,sha256=tcG0SGm7vrK-3XSjl3-NHRbQZhx3tWy90U_dzAHPjpg,116
8
+ clarity_api-0.0.2.dist-info/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
9
+ clarity_api-0.0.2.dist-info/METADATA,sha256=B9zvMcYwAMz3W0_nm_7WjsNpMPePxnb_yO-3VuSi-WI,5076
10
+ clarity_api-0.0.2.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110
11
+ clarity_api-0.0.2.dist-info/top_level.txt,sha256=GzU1WAhzKvq2LNliEefbjBiag7nCE3A2W-vE2az0RiE,12
12
+ clarity_api-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,6 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.37.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
6
+
@@ -0,0 +1 @@
1
+ clarity_api