Skip to content
This repository was archived by the owner on Dec 18, 2023. It is now read-only.

Commit 8733806

Browse files
authored
Merge pull request #25 from credo-ai/release/0.0.8
Release/0.0.8
2 parents d5adf5a + 8efa6a8 commit 8733806

File tree

7 files changed

+212
-12
lines changed

7 files changed

+212
-12
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Merge main into develop
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
9+
env:
10+
SRC_BRANCH: main
11+
TGT_BRANCH: develop
12+
13+
jobs:
14+
main:
15+
name: Merge main into develop
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: git checkout
19+
uses: actions/checkout@v3
20+
with:
21+
token: ${{ secrets.CREDOAIBOT_TOKEN }}
22+
ref: refs/heads/main
23+
fetch-depth: 0
24+
25+
- name: perform merge
26+
run: |
27+
git config --global user.email "${GITHUB_ACTOR}"
28+
git config --global user.name "${GITHUB_ACTOR}@users.noreply.github.com"
29+
git status
30+
git pull
31+
git checkout "$TGT_BRANCH"
32+
git status
33+
git merge "$SRC_BRANCH" --no-edit
34+
git push
35+
git status

connect/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# 1) we don't load dependencies by storing it in __init__.py
33
# 2) we can import it in setup.py for the same reason
44
# 3) we can import it into your module module
5-
__version__ = "0.0.7.1"
5+
__version__ = "0.0.8"

connect/adapters/adapters.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ def __init__(
3030
governance: Governance,
3131
model_name: str,
3232
model_tags: Optional[dict] = None,
33+
model_version: Optional[str] = None,
3334
assessment_dataset_name: str = None,
3435
):
3536

3637
self.governance = governance
37-
self.governance.set_artifacts(model_name, model_tags, assessment_dataset_name)
38+
self.governance.set_artifacts(
39+
model_name, model_tags, model_version, assessment_dataset_name
40+
)
3841

3942
def metrics_to_governance(
4043
self,

connect/evidence/evidence.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def __init__(
154154
self.significant = (
155155
True if self.p_value <= self.significance_threshold else False
156156
)
157-
super().__init__("statisticTest", additional_labels, **metadata)
157+
super().__init__("statistical_test", additional_labels, **metadata)
158158

159159
@property
160160
def data(self):

connect/governance/credo_api.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,65 @@ def get_assessment(self, use_case_id: str, id: str):
160160
"""
161161
path = f"use_cases/{use_case_id}/assessments/{id}"
162162
return self._client.get(path)
163+
164+
def update_use_case_model_link_tags(
165+
self, use_case_id: str, model_link_id: str, tags: dict
166+
):
167+
"""
168+
Update tags of use case model link
169+
170+
Parameters
171+
----------
172+
use_case_id : str
173+
use case id
174+
model_link_id : str
175+
use case model link id
176+
tags : dict
177+
model tags like {"key": "value"}
178+
179+
Returns
180+
-------
181+
None
182+
183+
Raises
184+
------
185+
HTTPError
186+
When API request returns error
187+
"""
188+
189+
path = f"use_cases/{use_case_id}/model_links/{model_link_id}"
190+
data = {"tags": tags, "$type": "use_case_model_links", "id": model_link_id}
191+
return self._client.patch(path, data)
192+
193+
def update_use_case_model_link_version(
194+
self, use_case_id: str, model_link_id: str, version: str
195+
):
196+
"""
197+
Update version of use case model link
198+
199+
Parameters
200+
----------
201+
use_case_id : str
202+
use case id
203+
model_link_id : str
204+
use case model link id
205+
version : str
206+
model version
207+
208+
Returns
209+
-------
210+
None
211+
212+
Raises
213+
------
214+
HTTPError
215+
When API request returns error
216+
"""
217+
218+
path = f"use_cases/{use_case_id}/model_links/{model_link_id}"
219+
data = {
220+
"model_version": version,
221+
"$type": "use_case_model_links",
222+
"id": model_link_id,
223+
}
224+
return self._client.patch(path, data)

connect/governance/credo_api_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ def __make_request(self, method: str, path: str, **kwargs):
148148
self.refresh_token()
149149
response = self._session.request(method, endpoint, **kwargs)
150150

151+
if response.status_code >= 400:
152+
data = response.json()
153+
if data:
154+
for error in data.get("errors", []):
155+
global_logger.error(
156+
f"Error happened from [{method.upper()}] {endpoint} : Message={error['title']}, Error Detail={error['detail']}"
157+
)
158+
151159
response.raise_for_status()
152160

153161
if response.content:

connect/governance/governance.py

Lines changed: 101 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,40 @@ def add_evidence(self, evidences: Union[Evidence, List[Evidence]]):
103103
"""
104104
self._evidences += wrap_list(evidences)
105105

106+
def apply_model_changes(self):
107+
"""
108+
Update Platform model's tags and version to CredoAI Governance if changed
109+
110+
This function will update the platform model associated with the assessment plan with
111+
the tags and version associated with the local model associated with Governance. If no
112+
model has been registered on the platform, nothing will be updated.
113+
"""
114+
# association between keys and api calls:
115+
api_calls = {
116+
"tags": self._api.update_use_case_model_link_tags,
117+
"model_version": self._api.update_use_case_model_link_version,
118+
}
119+
120+
# find model_link with model name from assessment plan
121+
plan_model = self._find_plan_model()
122+
if plan_model is None:
123+
return
124+
125+
model_info = self.get_model_info()
126+
plan_model_info = self._get_model_info(plan_model)
127+
for key in model_info.keys():
128+
model_value = model_info[key]
129+
plan_model_value = plan_model[key]
130+
if model_value != plan_model_value:
131+
global_logger.info(
132+
"%s\n%s",
133+
f"Platform model and local model {key} do not match. Platform {key}: {plan_model_value}, Local {key}: {model_value}\n",
134+
f"Updated platform model {key}...",
135+
)
136+
api_call = api_calls[key]
137+
api_call(self._use_case_id, plan_model["id"], model_value)
138+
plan_model[key] = model_value
139+
106140
def clear_evidence(self):
107141
self.set_evidence([])
108142

@@ -174,7 +208,7 @@ def get_evidence_requirements(self, tags: dict = None, verbose=False):
174208
List[EvidenceRequirement]
175209
"""
176210
if tags is None:
177-
tags = self.get_model_tags()
211+
tags = self.get_model_info()["tags"]
178212

179213
reqs = [e for e in self._evidence_requirements if check_subset(e.tags, tags)]
180214
if verbose:
@@ -185,12 +219,9 @@ def get_requirement_tags(self):
185219
"""Return the unique tags used for all evidence requirements"""
186220
return self._unique_tags
187221

188-
def get_model_tags(self):
189-
"""Get the tags for the associated model"""
190-
if self._model:
191-
return self._model["tags"]
192-
else:
193-
return {}
222+
def get_model_info(self):
223+
"""Get the tags and version for the associated model"""
224+
return self._get_model_info(self._model)
194225

195226
def register(
196227
self,
@@ -290,6 +321,7 @@ def set_artifacts(
290321
self,
291322
model: str,
292323
model_tags: dict,
324+
model_version: str = None,
293325
training_dataset: str = None,
294326
assessment_dataset: str = None,
295327
):
@@ -311,15 +343,21 @@ def set_artifacts(
311343
"""
312344

313345
global_logger.info(
314-
f"Adding model ({model}) to governance. Model has tags: {model_tags}"
346+
f"Adding model ({model}) to governance. Model has tags: {model_tags} and version: {model_version}"
315347
)
316-
prepared_model = {"name": model, "tags": model_tags}
348+
prepared_model = {
349+
"name": model,
350+
"tags": model_tags,
351+
"model_version": model_version,
352+
}
317353
if training_dataset:
318354
prepared_model["training_dataset_name"] = training_dataset
319355
if assessment_dataset:
320356
prepared_model["assessment_dataset_name"] = assessment_dataset
321357
self._model = prepared_model
322358

359+
self._print_model_changes_log()
360+
323361
def set_evidence(self, evidences: List[Evidence]):
324362
"""
325363
Update evidences
@@ -348,6 +386,9 @@ def _api_export(self):
348386
f"Uploading {len(self._evidences)} evidences.. for use_case_id={self._use_case_id} policy_pack_id={self._policy_pack_id}"
349387
)
350388

389+
# update when model tags are changed
390+
self.apply_model_changes()
391+
351392
assessment = self._api.create_assessment(
352393
self._use_case_id, self._prepare_export_data()
353394
)
@@ -379,6 +420,32 @@ def _api_export(self):
379420
error = assessment["error"]
380421
global_logger.error(f"Error in uploading evidences : {error}")
381422

423+
def _print_model_changes_log(self):
424+
# find model_link with model name from assessment plan
425+
plan_model = self._find_plan_model()
426+
if plan_model is None:
427+
return
428+
429+
model_info = self.get_model_info()
430+
plan_model_info = self._get_model_info(plan_model)
431+
match = True
432+
for key in model_info.keys():
433+
model_value = model_info[key]
434+
plan_model_value = plan_model[key]
435+
if model_value != plan_model_value:
436+
match = False
437+
global_logger.info(
438+
f"Platform model and local model {key} do not match. Platform {key}: {plan_model_value}, Local {key}: {model_value}"
439+
)
440+
if not match:
441+
global_logger.info(
442+
"""
443+
You can apply changes to governance by calling the following method:
444+
gov.apply_model_changes()
445+
Alternatively, calling gov.export() method will automatically apply changes to governance.
446+
"""
447+
)
448+
382449
def _check_inclusion(self, label, evidence):
383450
matching_evidence = []
384451
for e in evidence:
@@ -408,6 +475,31 @@ def _file_export(self, filename):
408475
with open(filename, "w") as f:
409476
f.write(data)
410477

478+
def _find_plan_model(self):
479+
"""Return model from assessment plan who matches name of associated model"""
480+
if self.model is None or self._plan is None:
481+
return None
482+
483+
model_name = self.model.get("name", None)
484+
if model_name is None:
485+
return None
486+
487+
for link in self._plan.get("model_links", []):
488+
if link["model_name"] == model_name:
489+
return link
490+
491+
return None
492+
493+
def _get_model_info(self, model):
494+
"""Get the tags and version for a model"""
495+
if model:
496+
return {
497+
"tags": model.get("tags", {}),
498+
"model_version": model.get("model_version", None),
499+
}
500+
else:
501+
return {"tags": {}, "model_version": None}
502+
411503
def _match_requirements(self):
412504
missing = []
413505
required_labels = [e.label for e in self.get_evidence_requirements()]

0 commit comments

Comments
 (0)