Skip to content

Commit 07629d3

Browse files
Add code quality workflow
1 parent 5948cd5 commit 07629d3

File tree

17 files changed

+735
-516
lines changed

17 files changed

+735
-516
lines changed

.flake8

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[flake8]
2+
max-line-length = 120
3+
exclude = .venv, _pycache_, migrations, build, dist
4+
ignore =
5+
F401
6+
F841
7+
W293
8+
E501
9+
E722
10+
W503
11+
F811
12+
E266
13+
F541

.github/workflows/pylint.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Code Quality Workflow
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
lint:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
python-version: ["3.11"]
11+
12+
steps:
13+
# Step 1: Checkout code
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
# Step 2: Set up Python environment
18+
- name: Set up Python ${{ matrix.python-version }}
19+
uses: actions/setup-python@v3
20+
with:
21+
python-version: ${{ matrix.python-version }}
22+
23+
# Step 3: Run all code quality checks
24+
- name: Run Code Quality Checks
25+
run: |
26+
python -m pip install --upgrade pip
27+
pip install -r requirements.txt
28+
echo "Fixing imports with Isort..."
29+
python -m isort --verbose .
30+
echo "Formatting code with Black..."
31+
python -m black --verbose .
32+
echo "Running Flake8..."
33+
python -m flake8 --config=.flake8 --verbose . || true
34+
echo "Running Pylint..."
35+
python -m pylint --rcfile=.pylintrc --verbose . || true

.pylintrc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[MASTER]
2+
ignore=__init__.py,__pycache__
3+
4+
5+
[MESSAGES CONTROL]
6+
# Retain your disabled warnings and errors
7+
disable=
8+
missing-docstring, # Missing docstrings
9+
invalid-name, # Variable names not in snake_case
10+
too-many-arguments, # Exceeding argument limits
11+
too-many-locals, # Exceeding local variable limits
12+
too-many-branches, # Complex functions
13+
too-many-lines, # Exceeding file line limit
14+
import-error, # Ignored unresolved imports
15+
no-name-in-module, # Missing module names
16+
broad-exception-raised, # Avoid broad exceptions
17+
redefined-outer-name, # Outer variable shadowing
18+
no-else-return, # Remove unnecessary else
19+
unused-import, # Unused imports
20+
21+
[FORMAT]
22+
# Set the maximum line length
23+
max-line-length=120
24+
25+
[DESIGN]
26+
# Adjust thresholds for warnings
27+
max-args=10
28+
max-locals=30
29+
max-branches=20
30+
max-statements=100
31+
32+
[LOGGING]
33+
# Disable logging format errors
34+
logging-format-style=old

backend/auth/auth_utils.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
def get_authenticated_user_details(request_headers):
22
user_object = {}
33

4-
## check the headers for the Principal-Id (the guid of the signed in user)
4+
# check the headers for the Principal-Id (the guid of the signed in user)
55
if "X-Ms-Client-Principal-Id" not in request_headers.keys():
6-
## if it's not, assume we're in development mode and return a default user
6+
# if it's not, assume we're in development mode and return a default user
77
from . import sample_user
88
raw_user_object = sample_user.sample_user
99
else:
10-
## if it is, get the user details from the EasyAuth headers
11-
raw_user_object = {k:v for k,v in request_headers.items()}
10+
# if it is, get the user details from the EasyAuth headers
11+
raw_user_object = {k: v for k, v in request_headers.items()}
1212

13-
user_object['user_principal_id'] = raw_user_object.get('X-Ms-Client-Principal-Id')
14-
user_object['user_name'] = raw_user_object.get('X-Ms-Client-Principal-Name')
15-
user_object['auth_provider'] = raw_user_object.get('X-Ms-Client-Principal-Idp')
13+
user_object['user_principal_id'] = raw_user_object.get(
14+
'X-Ms-Client-Principal-Id')
15+
user_object['user_name'] = raw_user_object.get(
16+
'X-Ms-Client-Principal-Name')
17+
user_object['auth_provider'] = raw_user_object.get(
18+
'X-Ms-Client-Principal-Idp')
1619
user_object['auth_token'] = raw_user_object.get('X-Ms-Token-Aad-Id-Token')
17-
user_object['client_principal_b64'] = raw_user_object.get('X-Ms-Client-Principal')
18-
user_object['aad_id_token'] = raw_user_object.get('X-Ms-Token-Aad-Id-Token')
20+
user_object['client_principal_b64'] = raw_user_object.get(
21+
'X-Ms-Client-Principal')
22+
user_object['aad_id_token'] = raw_user_object.get(
23+
'X-Ms-Token-Aad-Id-Token')
1924

20-
return user_object
25+
return user_object

backend/auth/sample_user.py

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
11
sample_user = {
2-
"Accept": "*/*",
3-
"Accept-Encoding": "gzip, deflate, br",
4-
"Accept-Language": "en",
5-
"Client-Ip": "22.222.222.2222:64379",
6-
"Content-Length": "192",
7-
"Content-Type": "application/json",
8-
"Cookie": "AppServiceAuthSession=/AuR5ENU+pmpoN3jnymP8fzpmVBgphx9uPQrYLEWGcxjIITIeh8NZW7r3ePkG8yBcMaItlh1pX4nzg5TFD9o2mxC/5BNDRe/uuu0iDlLEdKecROZcVRY7QsFdHLjn9KB90Z3d9ZeLwfVIf0sZowWJt03BO5zKGB7vZgL+ofv3QY3AaYn1k1GtxSE9HQWJpWar7mOA64b7Lsy62eY3nxwg3AWDsP3/rAta+MnDCzpdlZMFXcJLj+rsCppW+w9OqGhKQ7uCs03BPeon3qZOdmE8cOJW3+i96iYlhneNQDItHyQqEi1CHbBTSkqwpeOwWP4vcwGM22ynxPp7YFyiRw/X361DGYy+YkgYBkXq1AEIDZ44BCBz9EEaEi0NU+m6yUOpNjEaUtrJKhQywcM2odojdT4XAY+HfTEfSqp0WiAkgAuE/ueCu2JDOfvxGjCgJ4DGWCoYdOdXAN1c+MenT4OSvkMO41YuPeah9qk9ixkJI5s80lv8rUu1J26QF6pstdDkYkAJAEra3RQiiO1eAH7UEb3xHXn0HW5lX8ZDX3LWiAFGOt5DIKxBKFymBKJGzbPFPYjfczegu0FD8/NQPLl2exAX3mI9oy/tFnATSyLO2E8DxwP5wnYVminZOQMjB/I4g3Go14betm0MlNXlUbU1fyS6Q6JxoCNLDZywCoU9Y65UzimWZbseKsXlOwYukCEpuQ5QPT55LuEAWhtYier8LSh+fvVUsrkqKS+bg0hzuoX53X6aqUr7YB31t0Z2zt5TT/V3qXpdyD8Xyd884PqysSkJYa553sYx93ETDKSsfDguanVfn2si9nvDpvUWf6/R02FmQgXiaaaykMgYyIuEmE77ptsivjH3hj/MN4VlePFWokcchF4ciqqzonmICmjEHEx5zpjU2Kwa+0y7J5ROzVVygcnO1jH6ZKDy9bGGYL547bXx/iiYBYqSIQzleOAkCeULrGN2KEHwckX5MpuRaqTpoxdZH9RJv0mIWxbDA0kwGsbMICQd0ZODBkPUnE84qhzvXInC+TL7MbutPEnGbzgxBAS1c2Ct4vxkkjykOeOxTPxqAhxoefwUfIwZZax6A9LbeYX2bsBpay0lScHcA==",
9-
"Disguised-Host": "your_app_service.azurewebsites.net",
10-
"Host": "your_app_service.azurewebsites.net",
11-
"Max-Forwards": "10",
12-
"Origin": "https://your_app_service.azurewebsites.net",
13-
"Referer": "https://your_app_service.azurewebsites.net/",
14-
"Sec-Ch-Ua": "\"Microsoft Edge\";v=\"113\", \"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"",
15-
"Sec-Ch-Ua-Mobile": "?0",
16-
"Sec-Ch-Ua-Platform": "\"Windows\"",
17-
"Sec-Fetch-Dest": "empty",
18-
"Sec-Fetch-Mode": "cors",
19-
"Sec-Fetch-Site": "same-origin",
20-
"Traceparent": "00-24e9a8d1b06f233a3f1714845ef971a9-3fac69f81ca5175c-00",
21-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42",
22-
"Was-Default-Hostname": "your_app_service.azurewebsites.net",
23-
"X-Appservice-Proto": "https",
24-
"X-Arr-Log-Id": "4102b832-6c88-4c7c-8996-0edad9e4358f",
25-
"X-Arr-Ssl": "2048|256|CN=Microsoft Azure TLS Issuing CA 02, O=Microsoft Corporation, C=US|CN=*.azurewebsites.net, O=Microsoft Corporation, L=Redmond, S=WA, C=US",
26-
"X-Client-Ip": "22.222.222.222",
27-
"X-Client-Port": "64379",
28-
"X-Forwarded-For": "22.222.222.22:64379",
29-
"X-Forwarded-Proto": "https",
30-
"X-Forwarded-Tlsversion": "1.2",
31-
"X-Ms-Client-Principal": "your_base_64_encoded_token",
32-
"X-Ms-Client-Principal-Id": "00000000-0000-0000-0000-000000000000",
33-
"X-Ms-Client-Principal-Idp": "aad",
34-
"X-Ms-Client-Principal-Name": "testusername@constoso.com",
35-
"X-Ms-Token-Aad-Id-Token": "your_aad_id_token",
36-
"X-Original-Url": "/chatgpt",
37-
"X-Site-Deployment-Id": "your_app_service",
38-
"X-Waws-Unencoded-Url": "/chatgpt"
2+
"Accept": "*/*",
3+
"Accept-Encoding": "gzip, deflate, br",
4+
"Accept-Language": "en",
5+
"Client-Ip": "22.222.222.2222:64379",
6+
"Content-Length": "192",
7+
"Content-Type": "application/json",
8+
"Cookie": "AppServiceAuthSession=/AuR5ENU+pmpoN3jnymP8fzpmVBgphx9uPQrYLEWGcxjIITIeh8NZW7r3ePkG8yBcMaItlh1pX4nzg5TFD9o2mxC/5BNDRe/uuu0iDlLEdKecROZcVRY7QsFdHLjn9KB90Z3d9ZeLwfVIf0sZowWJt03BO5zKGB7vZgL+ofv3QY3AaYn1k1GtxSE9HQWJpWar7mOA64b7Lsy62eY3nxwg3AWDsP3/rAta+MnDCzpdlZMFXcJLj+rsCppW+w9OqGhKQ7uCs03BPeon3qZOdmE8cOJW3+i96iYlhneNQDItHyQqEi1CHbBTSkqwpeOwWP4vcwGM22ynxPp7YFyiRw/X361DGYy+YkgYBkXq1AEIDZ44BCBz9EEaEi0NU+m6yUOpNjEaUtrJKhQywcM2odojdT4XAY+HfTEfSqp0WiAkgAuE/ueCu2JDOfvxGjCgJ4DGWCoYdOdXAN1c+MenT4OSvkMO41YuPeah9qk9ixkJI5s80lv8rUu1J26QF6pstdDkYkAJAEra3RQiiO1eAH7UEb3xHXn0HW5lX8ZDX3LWiAFGOt5DIKxBKFymBKJGzbPFPYjfczegu0FD8/NQPLl2exAX3mI9oy/tFnATSyLO2E8DxwP5wnYVminZOQMjB/I4g3Go14betm0MlNXlUbU1fyS6Q6JxoCNLDZywCoU9Y65UzimWZbseKsXlOwYukCEpuQ5QPT55LuEAWhtYier8LSh+fvVUsrkqKS+bg0hzuoX53X6aqUr7YB31t0Z2zt5TT/V3qXpdyD8Xyd884PqysSkJYa553sYx93ETDKSsfDguanVfn2si9nvDpvUWf6/R02FmQgXiaaaykMgYyIuEmE77ptsivjH3hj/MN4VlePFWokcchF4ciqqzonmICmjEHEx5zpjU2Kwa+0y7J5ROzVVygcnO1jH6ZKDy9bGGYL547bXx/iiYBYqSIQzleOAkCeULrGN2KEHwckX5MpuRaqTpoxdZH9RJv0mIWxbDA0kwGsbMICQd0ZODBkPUnE84qhzvXInC+TL7MbutPEnGbzgxBAS1c2Ct4vxkkjykOeOxTPxqAhxoefwUfIwZZax6A9LbeYX2bsBpay0lScHcA==",
9+
"Disguised-Host": "your_app_service.azurewebsites.net",
10+
"Host": "your_app_service.azurewebsites.net",
11+
"Max-Forwards": "10",
12+
"Origin": "https://your_app_service.azurewebsites.net",
13+
"Referer": "https://your_app_service.azurewebsites.net/",
14+
"Sec-Ch-Ua": "\"Microsoft Edge\";v=\"113\", \"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"",
15+
"Sec-Ch-Ua-Mobile": "?0",
16+
"Sec-Ch-Ua-Platform": "\"Windows\"",
17+
"Sec-Fetch-Dest": "empty",
18+
"Sec-Fetch-Mode": "cors",
19+
"Sec-Fetch-Site": "same-origin",
20+
"Traceparent": "00-24e9a8d1b06f233a3f1714845ef971a9-3fac69f81ca5175c-00",
21+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42",
22+
"Was-Default-Hostname": "your_app_service.azurewebsites.net",
23+
"X-Appservice-Proto": "https",
24+
"X-Arr-Log-Id": "4102b832-6c88-4c7c-8996-0edad9e4358f",
25+
"X-Arr-Ssl": "2048|256|CN=Microsoft Azure TLS Issuing CA 02, O=Microsoft Corporation, C=US|CN=*.azurewebsites.net, O=Microsoft Corporation, L=Redmond, S=WA, C=US",
26+
"X-Client-Ip": "22.222.222.222",
27+
"X-Client-Port": "64379",
28+
"X-Forwarded-For": "22.222.222.22:64379",
29+
"X-Forwarded-Proto": "https",
30+
"X-Forwarded-Tlsversion": "1.2",
31+
"X-Ms-Client-Principal": "your_base_64_encoded_token",
32+
"X-Ms-Client-Principal-Id": "00000000-0000-0000-0000-000000000000",
33+
"X-Ms-Client-Principal-Idp": "aad",
34+
"X-Ms-Client-Principal-Name": "testusername@constoso.com",
35+
"X-Ms-Token-Aad-Id-Token": "your_aad_id_token",
36+
"X-Original-Url": "/chatgpt",
37+
"X-Site-Deployment-Id": "your_app_service",
38+
"X-Waws-Unencoded-Url": "/chatgpt"
3939
}

backend/history/cosmosdbservice.py

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
11
import uuid
22
from datetime import datetime
3-
from azure.cosmos.aio import CosmosClient
3+
44
from azure.cosmos import exceptions
5-
5+
from azure.cosmos.aio import CosmosClient
6+
7+
68
class CosmosConversationClient():
7-
9+
810
def __init__(self, cosmosdb_endpoint: str, credential: any, database_name: str, container_name: str, enable_message_feedback: bool = False):
911
self.cosmosdb_endpoint = cosmosdb_endpoint
1012
self.credential = credential
1113
self.database_name = database_name
1214
self.container_name = container_name
1315
self.enable_message_feedback = enable_message_feedback
1416
try:
15-
self.cosmosdb_client = CosmosClient(self.cosmosdb_endpoint, credential=credential)
17+
self.cosmosdb_client = CosmosClient(
18+
self.cosmosdb_endpoint, credential=credential)
1619
except exceptions.CosmosHttpResponseError as e:
1720
if e.status_code == 401:
1821
raise ValueError("Invalid credentials") from e
1922
else:
2023
raise ValueError("Invalid CosmosDB endpoint") from e
2124

2225
try:
23-
self.database_client = self.cosmosdb_client.get_database_client(database_name)
26+
self.database_client = self.cosmosdb_client.get_database_client(
27+
database_name)
2428
except exceptions.CosmosResourceNotFoundError:
25-
raise ValueError("Invalid CosmosDB database name")
26-
29+
raise ValueError("Invalid CosmosDB database name")
30+
2731
try:
28-
self.container_client = self.database_client.get_container_client(container_name)
32+
self.container_client = self.database_client.get_container_client(
33+
container_name)
2934
except exceptions.CosmosResourceNotFoundError:
30-
raise ValueError("Invalid CosmosDB container name")
31-
35+
raise ValueError("Invalid CosmosDB container name")
3236

3337
async def ensure(self):
3438
if not self.cosmosdb_client or not self.database_client or not self.container_client:
@@ -37,30 +41,30 @@ async def ensure(self):
3741
database_info = await self.database_client.read()
3842
except:
3943
return False, f"CosmosDB database {self.database_name} on account {self.cosmosdb_endpoint} not found"
40-
44+
4145
try:
4246
container_info = await self.container_client.read()
4347
except:
4448
return False, f"CosmosDB container {self.container_name} not found"
45-
49+
4650
return True, "CosmosDB client initialized successfully"
4751

48-
async def create_conversation(self, user_id, title = ''):
52+
async def create_conversation(self, user_id, title=''):
4953
conversation = {
50-
'id': str(uuid.uuid4()),
54+
'id': str(uuid.uuid4()),
5155
'type': 'conversation',
52-
'createdAt': datetime.utcnow().isoformat(),
53-
'updatedAt': datetime.utcnow().isoformat(),
56+
'createdAt': datetime.utcnow().isoformat(),
57+
'updatedAt': datetime.utcnow().isoformat(),
5458
'userId': user_id,
5559
'title': title
5660
}
57-
## TODO: add some error handling based on the output of the upsert_item call
58-
resp = await self.container_client.upsert_item(conversation)
61+
# TODO: add some error handling based on the output of the upsert_item call
62+
resp = await self.container_client.upsert_item(conversation)
5963
if resp:
6064
return resp
6165
else:
6266
return False
63-
67+
6468
async def upsert_conversation(self, conversation):
6569
resp = await self.container_client.upsert_item(conversation)
6670
if resp:
@@ -69,16 +73,15 @@ async def upsert_conversation(self, conversation):
6973
return False
7074

7175
async def delete_conversation(self, user_id, conversation_id):
72-
conversation = await self.container_client.read_item(item=conversation_id, partition_key=user_id)
76+
conversation = await self.container_client.read_item(item=conversation_id, partition_key=user_id)
7377
if conversation:
7478
resp = await self.container_client.delete_item(item=conversation_id, partition_key=user_id)
7579
return resp
7680
else:
7781
return True
7882

79-
8083
async def delete_messages(self, conversation_id, user_id):
81-
## get a list of all the messages in the conversation
84+
# get a list of all the messages in the conversation
8285
messages = await self.get_messages(user_id, conversation_id)
8386
response_list = []
8487
if messages:
@@ -87,8 +90,7 @@ async def delete_messages(self, conversation_id, user_id):
8790
response_list.append(resp)
8891
return response_list
8992

90-
91-
async def get_conversations(self, user_id, limit, sort_order = 'DESC', offset = 0):
93+
async def get_conversations(self, user_id, limit, sort_order='DESC', offset=0):
9294
parameters = [
9395
{
9496
'name': '@userId',
@@ -97,12 +99,12 @@ async def get_conversations(self, user_id, limit, sort_order = 'DESC', offset =
9799
]
98100
query = f"SELECT * FROM c where c.userId = @userId and c.type='conversation' order by c.updatedAt {sort_order}"
99101
if limit is not None:
100-
query += f" offset {offset} limit {limit}"
101-
102+
query += f" offset {offset} limit {limit}"
103+
102104
conversations = []
103105
async for item in self.container_client.query_items(query=query, parameters=parameters):
104106
conversations.append(item)
105-
107+
106108
return conversations
107109

108110
async def get_conversation(self, user_id, conversation_id):
@@ -121,30 +123,30 @@ async def get_conversation(self, user_id, conversation_id):
121123
async for item in self.container_client.query_items(query=query, parameters=parameters):
122124
conversations.append(item)
123125

124-
## if no conversations are found, return None
126+
# if no conversations are found, return None
125127
if len(conversations) == 0:
126128
return None
127129
else:
128130
return conversations[0]
129-
131+
130132
async def create_message(self, uuid, conversation_id, user_id, input_message: dict):
131133
message = {
132134
'id': uuid,
133135
'type': 'message',
134-
'userId' : user_id,
136+
'userId': user_id,
135137
'createdAt': datetime.utcnow().isoformat(),
136138
'updatedAt': datetime.utcnow().isoformat(),
137-
'conversationId' : conversation_id,
139+
'conversationId': conversation_id,
138140
'role': input_message['role'],
139141
'content': input_message['content']
140142
}
141143

142144
if self.enable_message_feedback:
143145
message['feedback'] = ''
144-
145-
resp = await self.container_client.upsert_item(message)
146+
147+
resp = await self.container_client.upsert_item(message)
146148
if resp:
147-
## update the parent conversations's updatedAt field with the current message's createdAt datetime value
149+
# update the parent conversations's updatedAt field with the current message's createdAt datetime value
148150
conversation = await self.get_conversation(user_id, conversation_id)
149151
if not conversation:
150152
return "Conversation not found"
@@ -153,7 +155,7 @@ async def create_message(self, uuid, conversation_id, user_id, input_message: di
153155
return resp
154156
else:
155157
return False
156-
158+
157159
async def update_message_feedback(self, user_id, message_id, feedback):
158160
message = await self.container_client.read_item(item=message_id, partition_key=user_id)
159161
if message:
@@ -180,4 +182,3 @@ async def get_messages(self, user_id, conversation_id):
180182
messages.append(item)
181183

182184
return messages
183-

0 commit comments

Comments
 (0)