Skip to content

Commit 8d8e410

Browse files
Extend permissions service support to Tuned Models (#219)
* Add metaclass to inject Permission methods Set PermissionAdapterMeta as a metaclass for Corpus and TunedModel classes to inject permission methods dynamically. * Add method to validate permission name * Allow accepting partial name for get_permission methods * Fix consistency issues * Update tests * Remove metaclasses and use inheritance instead * format * Update tests and fix pytpye error * Update tests * disable attribute error for PermissionsAdapter - pytpye for `self.name` field * Format * Handle partial names in get_permissions methods * Update tests * Update docsting for get_permission method * format * Add guard against invalid full name formats * Update tests * format * Update Valid Permission Name regex * Separate resource name validation for get_permission methods * Make o_proto method internal for Permission class o_proto to _to_proto and update invlaid error message for permission_id. * Update error messages * started editing the permissions class Change-Id: Idf1b9e8b0fff498a1c4fd4785c09eb49963c1d47 * Fix tests Change-Id: Id8cc1cf6e59009f06e8db3ff59edd7ef6ac76943 * Add type hints. Change-Id: I91f759fe3ebe4e3011440a3983a4cbfc317856d0 * Fix transfer_ownership for Corpora Change-Id: I791514660881ce967c405e4c76f44739968362f9 * format Change-Id: I0f98fc80fe81a8fc69aea9f00386aa2c519cb7e0 * Simplify error msgs * Simplify name checks * Add deprecation stubs on Corpus * Fix decorator type checking ast.Call.func can be of types ast.Attribute and ast.Name, previously only ast.Name was being considered. * fix types * fix types (#2) * Update retriever_types.py * Update retriever_types.py --------- Co-authored-by: Mark Daoust <[email protected]>
1 parent 3a9c286 commit 8d8e410

File tree

7 files changed

+638
-146
lines changed

7 files changed

+638
-146
lines changed

google/generativeai/permission.py

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,156 @@
1414
# limitations under the License.
1515
from __future__ import annotations
1616

17+
from typing import Callable
18+
1719
import google.ai.generativelanguage as glm
1820

1921
from google.generativeai.types import permission_types
22+
from google.generativeai.types import retriever_types
23+
from google.generativeai.types import model_types
24+
25+
26+
_RESOURCE_TYPE: dict[str, str] = {
27+
"corpus": "corpora",
28+
"corpora": "corpora",
29+
"tunedmodel": "tunedModels",
30+
"tunedmodels": "tunedModels",
31+
}
32+
33+
34+
def _to_resource_type(x: str) -> str:
35+
if isinstance(x, str):
36+
x = x.lower()
37+
resource_type = _RESOURCE_TYPE.get(x, None)
38+
if not resource_type:
39+
raise ValueError(f"Unsupported resource type. Got: `{x}` instead.")
40+
41+
return resource_type
42+
43+
44+
def _validate_resource_name(x: str, resource_type: str) -> None:
45+
if resource_type == "corpora":
46+
if not retriever_types.valid_name(x):
47+
raise ValueError(retriever_types.NAME_ERROR_MSG.format(length=len(x), name=x))
48+
49+
elif resource_type == "tunedModels":
50+
if not model_types.valid_tuned_model_name(x):
51+
raise ValueError(model_types.TUNED_MODEL_NAME_ERROR_MSG.format(length=len(x), name=x))
52+
53+
else:
54+
raise ValueError(f"Unsupported resource type: {resource_type}")
55+
56+
57+
def _validate_permission_id(x: str) -> None:
58+
if not permission_types.valid_id(x):
59+
raise ValueError(permission_types.INVALID_PERMISSION_ID_MSG.format(permission_id=x))
60+
61+
62+
def _get_valid_name_components(name: str) -> str:
63+
# name is of the format: resource_type/resource_name/permissions/permission_id
64+
name_path_components = name.split("/")
65+
if len(name_path_components) != 4:
66+
raise ValueError(
67+
f"Invalid name format. Expected format: \
68+
`resource_type/<resource_name>/permissions/<permission_id>`. Got: `{name}` instead."
69+
)
70+
71+
resource_type, resource_name, permission_placeholder, permission_id = name_path_components
72+
resource_type = _to_resource_type(resource_type)
73+
74+
permission_id = "/".join([permission_placeholder, permission_id])
75+
76+
_validate_resource_name(resource_name, resource_type)
77+
_validate_permission_id(permission_id)
78+
79+
return "/".join([resource_type, resource_name, permission_id])
80+
81+
82+
def _construct_name(
83+
name: str | None = None,
84+
resource_name: str | None = None,
85+
permission_id: str | int | None = None,
86+
resource_type: str | None = None,
87+
) -> str:
88+
# resource_name is the name of the supported resource (corpus or tunedModel as of now) for which the permission is being created.
89+
if not name:
90+
# if name is not provided, then try to construct name via provided resource_name and permission_id.
91+
if not (resource_name and permission_id):
92+
raise ValueError(
93+
"Either `name` or (`resource_name` and `permission_id`) must be provided."
94+
)
95+
96+
if resource_type:
97+
resource_type = _to_resource_type(resource_type)
98+
else:
99+
# if resource_type is not provided, then try to infer it from resource_name.
100+
resource_path_components = resource_name.split("/")
101+
if len(resource_path_components) != 2:
102+
raise ValueError(
103+
f"Invalid `resource_name` format. Expected format: \
104+
`resource_type/resource_name`. Got: `{resource_name}` instead."
105+
)
106+
resource_type = _to_resource_type(resource_path_components[0])
107+
108+
if f"{resource_type}/" in resource_name:
109+
name = f"{resource_name}/"
110+
else:
111+
name = f"{resource_type}/{resource_name}/"
112+
113+
if isinstance(permission_id, int) or "permissions/" not in permission_id:
114+
name += f"permissions/{permission_id}"
115+
else:
116+
name += permission_id
117+
118+
# if name is provided, override resource_name and permission_id
119+
name = _get_valid_name_components(name)
120+
return name
20121

21122

22123
def get_permission(
23-
name: str,
124+
name: str | None = None,
125+
*,
24126
client: glm.PermissionServiceClient | None = None,
127+
resource_name: str | None = None,
128+
permission_id: str | int | None = None,
129+
resource_type: str | None = None,
25130
) -> permission_types.Permission:
26-
"""Get a permission by name.
131+
"""Get information about a permission by name.
27132
28133
Args:
29134
name: The name of the permission.
135+
resource_name: The name of the supported resource for which the permission details are needed.
136+
permission_id: The name of the permission.
137+
resource_type: The type of the resource (corpus or tunedModel as of now) for which the permission details are needed.
138+
If not provided, it will be inferred from `resource_name`.
30139
31140
Returns:
32141
The permission as an instance of `permission_types.Permission`.
33142
"""
143+
name = _construct_name(
144+
name=name,
145+
resource_name=resource_name,
146+
permission_id=permission_id,
147+
resource_type=resource_type,
148+
)
34149
return permission_types.Permission.get(name=name, client=client)
35150

36151

37152
async def get_permission_async(
38-
name: str,
153+
name: str | None = None,
154+
*,
39155
client: glm.PermissionServiceAsyncClient | None = None,
156+
resource_name: str | None = None,
157+
permission_id: str | int | None = None,
158+
resource_type: str | None = None,
40159
) -> permission_types.Permission:
41160
"""
42161
This is the async version of `permission.get_permission`.
43162
"""
163+
name = _construct_name(
164+
name=name,
165+
resource_name=resource_name,
166+
permission_id=permission_id,
167+
resource_type=resource_type,
168+
)
44169
return await permission_types.Permission.get_async(name=name, client=client)

google/generativeai/types/model_types.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from typing_extensions import TypedDict
3030

3131
import google.ai.generativelanguage as glm
32+
from google.generativeai.types import permission_types
3233
from google.generativeai import string_utils
3334

3435

@@ -47,6 +48,17 @@
4748

4849
TunedModelStateOptions = Union[None, str, int, TunedModelState]
4950

51+
_TUNED_MODEL_VALID_NAME = r"[a-z](([a-z0-9-]{0,61}[a-z0-9])?)$"
52+
TUNED_MODEL_NAME_ERROR_MSG = """The `name` must consist of alphanumeric characters (or -) and be at most 63 characters; The name you entered:
53+
\tlen(name)== {length}
54+
\tname={name}
55+
"""
56+
57+
58+
def valid_tuned_model_name(name: str) -> bool:
59+
return re.match(_TUNED_MODEL_VALID_NAME, name) is not None
60+
61+
5062
# fmt: off
5163
_TUNED_MODEL_STATES: dict[TunedModelStateOptions, TunedModelState] = {
5264
TunedModelState.ACTIVE: TunedModelState.ACTIVE,
@@ -183,6 +195,10 @@ class TunedModel:
183195
update_time: datetime.datetime | None = None
184196
tuning_task: TuningTask | None = None
185197

198+
@property
199+
def permissions(self) -> permission_types.Permissions:
200+
return permission_types.Permissions(self)
201+
186202

187203
@string_utils.prettyprint
188204
@dataclasses.dataclass

0 commit comments

Comments
 (0)