(20241211) Figuring out the inherited permissions system. Stuck at directory management with permissions handling.

This commit is contained in:
2024-12-11 19:00:14 +05:30
parent 448b183662
commit 75d269c9a5
4 changed files with 442 additions and 222 deletions
+344 -209
View File
@@ -31,17 +31,14 @@ import io
# To make sibling directories accessible for imports: # To make sibling directories accessible for imports:
import sys import sys
from sqlalchemy.orm.collections import collection
from utils_v2.mail.mail_parser_v2 import parse_addr
sys.path.append(".") sys.path.append(".")
sys.path.append("..") sys.path.append("..")
# My async utils: # My async utils:
from utils_v2.string import json from utils_v2.string import json
from utils_v2.date_time import date_time from utils_v2.date_time import date_time
from utils_v2.system import files
from utils_v2.security.hash import Hasher
from utils_v2.database.async_mysql_v2 import AsyncMySQL from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo, AsyncMongoStorage from utils_v2.database.async_mongo_v2 import AsyncMongo, AsyncMongoStorage
@@ -53,7 +50,8 @@ from models.data.core.user import CoreUserInfoModel
from models.data.core.file_object import ( from models.data.core.file_object import (
CoreFileObjectInfoModel, CoreFileObjectInfoModel,
CoreFileObjectSharingModel, CoreFileObjectSharingModel,
CoreFileObjectPermissionsModel CoreFileObjectPermissionsModel,
CoreFileObjectAccessResponseModel
) )
# To work with MongoDB: # To work with MongoDB:
@@ -65,8 +63,8 @@ from typing import Any, Literal, List
# To make deep copies: # To make deep copies:
import copy import copy
# To make deep-copies: # For debugging:
import copy from icecream import IceCreamDebugger
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -111,75 +109,39 @@ class FileObjectManagementModel:
# Define class-level variables: # Define class-level variables:
FILE_OBJECTS_COLLECTION = "_fileObjects" FILE_OBJECTS_COLLECTION = "_fileObjects"
def __init__(
self,
debug = True,
debug_prefix = "File Objs. | ",
):
# Debugging:
self._printer = IceCreamDebugger(prefix = debug_prefix, includeContext = True)
if not debug: self._printer.disable()
def enable_debug(self):
self._printer.enable()
def disable_debug(self):
self._printer.disable()
# ┓┏ ┓ # ┓┏ ┓
# ┣┫┏┓┃┏┓┏┓┏┓┏ # ┣┫┏┓┃┏┓┏┓┏┓┏
# ┛┗┗ ┗┣┛┗ ┛ ┛ # ┛┗┗ ┗┣┛┗ ┛ ┛
# ┛ # ┛
def match_lists( @staticmethod
self, def users_match(
list_a: list, user_p: CoreUserInfoModel,
list_b: list user_r: CoreUserInfoModel,
): ignore_null: bool = True
) -> bool:
""" """
To match items in a List. The lists should have values that can be sorted. Mixed value dicts will raise To match if a user that is requesting a resource is the same as the user known to have access to the resource.
exceptions. :param user_p: One of the dicts to check.
:param list_a: One of the lists to check. :param user_r: The other dict to check.
:param list_b: The other list to check. :param ignore_null: Whether to consider only non-null values, or all values.
:return: True if they match, else False.
"""
# Start by assuming success:
are_matching = True
# If the lengths don't match,
# there is no way the lists are exactly the same:
if len(list_a) != len(list_b):
are_matching = False
return are_matching
# Sort the lists first for better comparison:
sorted_list_a = sorted(copy.deepcopy(list_a))
sorted_list_b = sorted(copy.deepcopy(list_b))
# Now loop through them and compare elements at the same index:
for index in range(len(sorted_list_a)):
av = sorted_list_a[index]
bv = sorted_list_b[index]
# If both of them are some sorts of lists:
if isinstance(av, (list, tuple, set)):
if not self.match_lists(list_a = list(av), list_b = list(bv)):
are_matching = False
break
# If both of them are dicts, we go recursive:
elif isinstance(av, dict):
if not self.match_dicts(dict_a = av, dict_b = bv):
are_matching = False
break
# Finally, we make direct comparison for things like strings, ints, etc.
else:
if av != bv:
are_matching = False
break
# Done here:
return are_matching
def match_dicts(
self,
dict_a: dict,
dict_b: dict
):
"""
To match items in a dict. Any lists in the dict should have values that can be sorted. Mixed value dicts will
raise exceptions.
:param dict_a: One of the dicts to check.
:param dict_b: The other dict to check.
:return: True if they match, else False. :return: True if they match, else False.
""" """
@@ -187,35 +149,21 @@ class FileObjectManagementModel:
are_matching = True are_matching = True
# Iterate through the required items: # Iterate through the required items:
for ak, av in dict_a.items(): for rk, rv in user_r.model_dump().items():
# Fetch the corresponding value from the other dict: # Do not consider fields that are nulls if asked to ignore them:
bv = dict_b.get(ak) if ignore_null and rv is None: continue
# If the types are themselves mismatched, # Extract the corresponding value from the other user,
# no point in further comparison: # and test it for being equal:
if not isinstance(av, type(bv)): pv = getattr(user_p, rk, None)
if (
(not isinstance(rv, type(pv))) or
(rv != pv)
):
are_matching = False are_matching = False
break break
# If both of them are some sorts of lists:
if isinstance(av, (list, tuple, set)):
if not self.match_lists(list_a = list(av), list_b = list(bv)):
are_matching = False
break
# If both of them are dicts, we go recursive:
elif isinstance(av, dict):
if not self.match_dicts(dict_a = av, dict_b = bv):
are_matching = False
break
# Finally, we make direct comparison for things like strings, ints, etc.
else:
if av != bv:
are_matching = False
break
# Done here: # Done here:
return are_matching return are_matching
@@ -227,48 +175,85 @@ class FileObjectManagementModel:
self, self,
mongo_conn: AsyncMongoStorage, mongo_conn: AsyncMongoStorage,
file_object_id: ObjectId | str file_object_id: ObjectId | str
) -> bool: ) -> CoreFileObjectAccessResponseModel:
""" """
To check whether, or not, a particular file's record exists in the database. To check whether, or not, a particular file's record exists in the database.
:param mongo_conn: The instance of the database connection to perform this action. :param mongo_conn: The instance of the database connection to perform this action.
:param file_object_id: The id of the file to check. :param file_object_id: The id of the file to check.
:return: True if it exists, else False. :return: A structured response where the existence of the file is noted in the 'result' field.
""" """
# Run the query: # Create a response:
record = await mongo_conn.find_one( response = CoreFileObjectAccessResponseModel()
collection = self.FILE_OBJECTS_COLLECTION,
filter = {"_id": ObjectId(file_object_id)},
projection = {"_id": True, "user": True}
)
# Return the result: try:
if not record: return False
else: return True # Run the query:
record = await mongo_conn.find_one(
collection = self.FILE_OBJECTS_COLLECTION,
filter = {"_id": ObjectId(file_object_id)},
projection = {"_id": True, "user": True, "isDir": True},
raise_exception = True
)
# Note down the result:
if record:
response.result = True
response.success = True
response.message = "ok"
except Exception as exception:
response.exception = exception
response.message = str(exception)
response.success = False
response.result = None
# Done here:
return response
async def info( async def info(
self, self,
mongo_conn: AsyncMongoStorage, mongo_conn: AsyncMongoStorage,
file_object_id: ObjectId file_object_id: ObjectId | str
) -> CoreFileObjectInfoModel | None: ) -> CoreFileObjectAccessResponseModel:
""" """
To get the information about this file. To get the information about this file.
:param mongo_conn: The instance of the database connection to perform this action. :param mongo_conn: The instance of the database connection to perform this action.
:param file_object_id: The id of the file to check. :param file_object_id: The id of the file to check.
:return: None if the file doesn't exist, else its summary. :return: A structured response where the info of the file is noted in the 'result' field.
""" """
# Run the query: # Create a response:
record = await mongo_conn.find_one( response = CoreFileObjectAccessResponseModel()
collection = self.FILE_OBJECTS_COLLECTION,
filter = {"_id": ObjectId(file_object_id)}
)
# return the results: try:
if not record: return None
else: return CoreFileObjectInfoModel(**record) # Run the query:
record = await mongo_conn.find_one(
collection = self.FILE_OBJECTS_COLLECTION,
filter = {"_id": ObjectId(file_object_id)},
raise_exception = True
)
# Note down the result:
response.success = True
if record:
response.result = CoreFileObjectInfoModel(**record)
response.message = "ok"
else:
response.message = "no such file object"
# If something goes wrong:
except Exception as exception:
response.exception = exception
response.message = str(exception)
response.success = False
response.result = None
# Done here:
return response
async def is_private( async def is_private(
self, self,
@@ -283,82 +268,88 @@ class FileObjectManagementModel:
:return: True if private, else False. None if it doesn't exist at all. :return: True if private, else False. None if it doesn't exist at all.
""" """
# Run the query: # Create a response:
record = await mongo_conn.find_one( response = CoreFileObjectAccessResponseModel()
collection = self.FILE_OBJECTS_COLLECTION,
filter = {"_id": ObjectId(file_object_id)},
projection = {"_id": False, "isPrivate": True}
)
# Return the result: try:
if not record: return None
else: return record["isPrivate"]
async def is_public( # Run the query:
self, record = await mongo_conn.find_one(
mongo_conn: AsyncMongoStorage, collection = self.FILE_OBJECTS_COLLECTION,
file_object_id: ObjectId | str filter = {"_id": ObjectId(file_object_id)},
) -> bool | None: projection = {"_id": False, "isPrivate": True},
raise_exception = True
)
""" # Note down the result:
A wrapper around the is_private, method that returns the opposite value. if record:
:param mongo_conn: The instance of the database connection to perform this action. response.result = record["isPrivate"]
:param file_object_id: The id of the file to check. response.success = True
:return: True if public, else False. None if it doesn't exist at all. response.message = "ok"
"""
# Call the existing function: # If something goes wrong:
is_private = await self.is_private( except Exception as exception:
mongo_conn = mongo_conn, response.exception = exception
file_object_id = file_object_id response.message = str(exception)
) response.success = False
response.result = None
# Return the opposite result: # Done here:
if is_private is None: return is_private return response
else: return not is_private
async def is_owner( def is_owner(
self, self,
mongo_conn: AsyncMongoStorage, mongo_conn: AsyncMongoStorage,
user_info: CoreUserInfoModel, user_info: CoreUserInfoModel,
file_object_id: ObjectId | str, file_object_info: CoreFileObjectInfoModel,
) -> bool | None: ignore_null: bool = True
) -> CoreFileObjectAccessResponseModel:
""" """
To check if a specific user is the owner of a specific file. To check if a specific user is the owner of a specific file.
:param mongo_conn: The instance of the database connection to perform this action. :param mongo_conn: The instance of the database connection to perform this action.
:param user_info: The details of the user who needs to have permissions to this file. :param user_info: The details of the user who needs to have permissions to this file.
:param file_object_id: The id of the file to check. :param file_object_info: The information about the file/dir. Fetch it from the 'info' method.
:param ignore_null: Whether to consider only non-null values, or all values.
:return: True if owner, else False. None if something goes wrong. :return: True if owner, else False. None if something goes wrong.
""" """
# Parse the user conditions by ignoring the null values: # Create a response:
user_conditions = {k: v for k, v in user_info.model_dump().items() if v is not None} response = CoreFileObjectAccessResponseModel()
# Run the query: try:
record = await mongo_conn.find_one(
collection = self.FILE_OBJECTS_COLLECTION,
filter = mongo_conn.dict_to_dot_notation({
"_id": ObjectId(file_object_id),
"user": user_conditions,
}),
projection = {
"user": True
}
)
# Return the result: # Test for a match:
if record is None: return False if self.users_match(
else: return True user_p = file_object_info.user,
user_r = user_info,
ignore_null = ignore_null
):
response.result = True
response.message = "user is the owner of this resource"
else:
response.result = False
response.message = "user is not the owner of this resource"
response.success = True
async def has_permission( # If something goes wrong:
except Exception as exception:
response.exception = exception
response.message = str(exception)
response.success = False
response.result = None
# Done here:
return response
def has_permission(
self, self,
mongo_conn: AsyncMongoStorage, mongo_conn: AsyncMongoStorage,
user_info: CoreUserInfoModel, user_info: CoreUserInfoModel,
permission: Literal["read", "delete", "changePermissions"], file_object_info: CoreFileObjectInfoModel,
file_object_id: ObjectId | str = None, permission: Literal["read", "write", "delete", "changePermissions"],
check_ownership: bool = True ignore_null: bool = True
) -> bool | None: ) -> CoreFileObjectAccessResponseModel:
""" """
To check if a particular user has permissions to a given file obj. You may pass either an instance of the file's To check if a particular user has permissions to a given file obj. You may pass either an instance of the file's
@@ -366,40 +357,56 @@ class FileObjectManagementModel:
:param mongo_conn: The instance of the database connection to perform this action. :param mongo_conn: The instance of the database connection to perform this action.
:param user_info: The details of the user who needs to have permissions to this file. :param user_info: The details of the user who needs to have permissions to this file.
:param permission: The name of the permission that the said user must have on this file. :param permission: The name of the permission that the said user must have on this file.
:param file_object_id: The id of the file to check. :param file_object_info: The information about the file/dir. Fetch it from the 'info' method.
:param check_ownership: Owners of files have all rights to their own files. If this field is set to True, :param ignore_null: Whether to consider only non-null values, or all values.
ownership will be tested, and if the user is the owner of the file, the permission is assumed to be granted.
If this field is set to False, only the sharing section of the file's record will be checked.
:return: True if the user has said permission, else False. None if something goes wrong. :return: True if the user has said permission, else False. None if something goes wrong.
""" """
# Parse the user conditions by ignoring the null values: # If this is a public file/dir,
user_conditions = {k: v for k, v in user_info.model_dump().items() if v is not None} # and the permission requested is 'read':
if (not file_object_info.isPrivate) and permission == "read":
# If we have to test ownership: return CoreFileObjectAccessResponseModel(
if check_ownership: success = True,
is_owner = await self.is_owner( message = "this resource is publicly available",
mongo_conn = mongo_conn, result = True,
user_info = user_info, exception = None
file_object_id = file_object_id
) )
if is_owner: return True
# otherwise, we run the query to check for granted permissions: # The owner always has all permissions:
record = await mongo_conn.find_one( response = self.is_owner(
collection = self.FILE_OBJECTS_COLLECTION, mongo_conn = mongo_conn,
filter = mongo_conn.dict_to_dot_notation({ user_info = user_info,
"_id": ObjectId(file_object_id), file_object_info = file_object_info,
"sharedWith.user": user_conditions, ignore_null = ignore_null
}),
projection = {
"sharedWith.$": True
}
) )
if not response.success: return response
if response.result is True: return response
# Return the result: # Note down the failure of the ownership test:
if record is None: return False response.message = "this user does not have the requested permission over this resource"
else: return record["sharedWith"][0]["permissions"][permission]
# Since The person requesting this is not the owner,
# we check with the sharing details:
for sharing_data in file_object_info.sharedWith:
# We match the users.
# If they don't match, we move to the next user:
if not self.users_match(
user_p = sharing_data.user,
user_r = user_info,
ignore_null = ignore_null
): continue
# If we found a matching user,
# we check for the permission:
if getattr(sharing_data.permissions, permission, False):
response.result = True
response.success = True
response.message = "this user has the requested permission over this resource"
break
# Done here:
return response
# ┓ • • # ┓ • •
# ┃ ┓┏╋┓┏┓┏┓ # ┃ ┓┏╋┓┏┓┏┓
@@ -603,11 +610,120 @@ class FileObjectManagementModel:
# ┗┻┛┛ ┗┗┗┛┗┗┫ # ┗┻┛┛ ┗┗┗┛┗┗┫
# ┛ # ┛
async def make_dir(self): pass async def make_dir(
self,
mongo_conn: AsyncMongoStorage,
user_info: CoreUserInfoModel,
dir_name: str,
dir_metadata: dict = None,
dir_tags: list = None,
parent_id: ObjectId | str = None
) -> bool:
async def upload_file(self): pass # Create an instance of the directory's model:
dir_model = CoreFileObjectInfoModel(
_id = mongo_conn.generate_id(),
user = user_info,
isDir = True,
name = dir_name,
createTs = date_time.get_current_utc_date_time(as_string = False),
metadata = dir_metadata,
tags = dir_tags,
parentId = parent_id,
isPrivate = True
)
async def get_file_upload_stream(self): pass print("DIRECTORY:", json.to_string(dir_model.model_dump(), default = str))
# Insert this document into the database:
inserted_id = await mongo_conn.insert_one(
collection = self.FILE_OBJECTS_COLLECTION,
document = dir_model.model_dump()
)
# Done here:
return True if inserted_id else False
async def upload_file(
self,
mongo_conn: AsyncMongoStorage,
file_info: CoreFileObjectInfoModel,
file_data: io.BytesIO | str
):
# Start by assuming failure:
file_uploaded = False
# Start a session:
async with await (await mongo_conn.client).start_session() as session:
# Define the transaction options:
options = {
# "read_concern": {"level": "snapshot"}, # ... Optional: ensures consistent reads.
# "write_concern": {"w": "majority"}, # ...... Ensures writes are acknowledged.
# "read_preference": "primary", # ............ Specify where to read from (e.g., primary).
}
# Start the transaction:
async with session.start_transaction(**options):
try:
# Check that we indeed have a file that we are uploading:
if file_info.isDir: raise ValueError("cannot data to upload directory object")
# Generate an ObjectId:
file_info.fileObjectId = mongo_conn.generate_id(as_str = False)
# Read the file's data into a BytesIO object:
if isinstance(file_data, str):
file_data = io.BytesIO(files.read_file(file_data, mode = "rb"))
file_data.seek(0)
# Hash the file's data:
hasher = Hasher()
hasher.update(file_data.getvalue())
file_info.hash = hasher.hexdigest()
# Now we upload the actual content of the file with the same id:
success = await mongo_conn.easy_upload(
source = file_data,
file_name = file_info.name,
file_metadata = None,
file_id = file_info.fileObjectId,
session = session,
raise_exception = True
)
# Safety check to ensure that the data was written:
if not success:
raise ValueError("file object's bytes weren't uploaded")
# Write the file object's info model now with the same id:
inserted_id = await mongo_conn.insert_one(
collection = self.FILE_OBJECTS_COLLECTION,
document = file_info.model_dump(),
session = session,
raise_exception = True
)
# Safety check to ensure that the info was written:
if inserted_id is None:
raise ValueError("file object's info wasn't inserted")
# If we've reached this far:
file_uploaded = True
# If something goes wrong:
except Exception as exception:
session.abort_transaction()
file_uploaded = False
self._printer(exception)
# Done here:
return file_uploaded
async def upload_from_stream(self): pass
# ┳┓ ┓ • # ┳┓ ┓ •
# ┃┃┏┓┃┏┓╋┓┏┓┏┓ # ┃┃┏┓┃┏┓╋┓┏┓┏┓
@@ -626,7 +742,7 @@ class FileObjectManagementModel:
async def make_public(self): pass async def make_public(self): pass
async def mak_private(self): pass async def make_private(self): pass
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -652,21 +768,40 @@ if __name__ == "__main__":
await files_mongo.connect() await files_mongo.connect()
my_fs = FileObjectManagementModel() my_fs = FileObjectManagementModel()
user_a = CoreUserInfoModel( user_bhopli = CoreUserInfoModel(
userId = 1 fullName = "Bhopli Narangi",
userId = 1,
entityId = 2,
billingAccountId = 3,
departmentId = 4,
branchId = 5,
industry = "technology"
) )
user_b = CoreUserInfoModel( user_polki = CoreUserInfoModel(
userId = 6 fullName = "Polki Muchhwaali",
userId = 6,
entityId = 7,
billingAccountId = 8,
departmentId = 9,
branchId = 10,
# industry = "finance"
) )
owned_files = await my_fs.list_shared_files( file_info = await my_fs.info(
mongo_conn = files_mongo, mongo_conn = files_mongo,
user_info = user_b file_object_id = "67598d48c1bf89b25695f20b"
) )
print("SUCCESS:", file_info.success)
if owned_files is None: print("ERROR!") print("MESSAGE:", file_info.message)
else: print(f"FILES ({len(owned_files)}):", json.to_string(owned_files, default = str)) print("FILE INFO:", json.to_string(file_info.result.model_dump(), default = str))
is_owner = my_fs.has_permission(
mongo_conn = files_mongo,
user_info = user_polki,
file_object_info = file_info.result,
permission = "write"
)
print("HAS PERMISSION:", is_owner.model_dump_json(indent = 4))
asyncio.run(main()) asyncio.run(main())
+86 -11
View File
@@ -126,6 +126,16 @@ class CoreFileObjectSharingModel(BaseModel):
frozen = True frozen = True
) )
permissionType: Literal["explicit", "inherited"] = Field(
description = "to know whether the permission was inherited from a parent dir, or explicitly granted",
frozen = True
)
inheritedFromId: ObjectId | None = Field(
description = "when some permission was inherited, this tells you the id of the parent",
frozen = True
)
permissions: CoreFileObjectPermissionsModel = Field( permissions: CoreFileObjectPermissionsModel = Field(
description = "the permissions that the above mentioned user has to this file", description = "the permissions that the above mentioned user has to this file",
frozen = True frozen = True
@@ -140,6 +150,18 @@ class CoreFileObjectSharingModel(BaseModel):
extra = "allow" extra = "allow"
arbitrary_types_allowed = True arbitrary_types_allowed = True
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator("inheritedFromId", mode = "before")
def parse_oid(cls, value):
try:
if isinstance(value, str):
value = ObjectId(value)
except: pass
return value
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
@@ -148,7 +170,7 @@ class CoreFileObjectInfoModel(BaseModel):
fileObjectId: ObjectId = Field( fileObjectId: ObjectId = Field(
description = "the id of the document in mongodb that holds this information", description = "the id of the document in mongodb that holds this information",
frozen = True, frozen = False,
default = None, default = None,
alias = "_id" alias = "_id"
) )
@@ -187,7 +209,7 @@ class CoreFileObjectInfoModel(BaseModel):
) )
metadata: Dict[str, Any] = Field( metadata: Dict[str, Any] = Field(
description = "any addition data about this file/dir to filter it later", description = "any additional data about this file/dir to filter it later",
frozen = False, frozen = False,
default = {} default = {}
) )
@@ -235,9 +257,21 @@ class CoreFileObjectInfoModel(BaseModel):
def parse_date_time(cls, value): def parse_date_time(cls, value):
return date_time.parse_date_time(input_value = value, timezone = date_time.TIMEZONE_UTC) return date_time.parse_date_time(input_value = value, timezone = date_time.TIMEZONE_UTC)
@field_validator("metadata", mode = "before")
def validate_metadata(cls, value):
if value is None: value = {}
return value
@field_validator("tags", mode = "before")
def validate_tags(cls, value):
if value is None: value = []
return value
@field_validator("fileObjectId", "parentId", mode = "before") @field_validator("fileObjectId", "parentId", mode = "before")
def parse_oid(cls, value): def parse_oid(cls, value):
try: value = ObjectId(value) try:
if isinstance(value, str):
value = ObjectId(value)
except: pass except: pass
return value return value
@@ -249,7 +283,7 @@ class CoreFileObjectInfoModel(BaseModel):
@property @property
def summary(self): def summary(self):
return { return {
"user": self.user, "user": self.user.model_dump(),
"isDir": self.isDir, "isDir": self.isDir,
"name": self.name, "name": self.name,
"createTs": self.createTs, "createTs": self.createTs,
@@ -258,6 +292,45 @@ class CoreFileObjectInfoModel(BaseModel):
} }
# ---------------------------------------------------------------------------------------------------------------------
class CoreFileObjectAccessResponseModel(BaseModel):
success: bool = Field(
description = "whether, or not, the operation was successful",
frozen = False,
default = False
)
message: str | None = Field(
description = "useful to note the reason in case of an unsuccessful operation",
frozen = False,
default = "message not documented"
)
result: Any = Field(
description = "the result of the operation",
frozen = False,
default = None
)
exception: Exception | None = Field(
description = "to document any exceptions that came up",
frozen = False,
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "allow"
arbitrary_types_allowed = True
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
# *** MAIN PROGRAM *** # *** MAIN PROGRAM ***
@@ -270,7 +343,7 @@ if __name__ == "__main__":
from utils_v2.string import json from utils_v2.string import json
file_info = CoreFileObjectInfoModel( file_info = CoreFileObjectInfoModel(
# _id = "67519cf3a7804fcbc6f12452", _id = "67519cf3a7804fcbc6f12452",
user = CoreUserInfoModel( user = CoreUserInfoModel(
fullName = "Bhopli Narangi", fullName = "Bhopli Narangi",
userId = 1, userId = 1,
@@ -278,9 +351,10 @@ if __name__ == "__main__":
billingAccountId = 3, billingAccountId = 3,
departmentId = 4, departmentId = 4,
branchId = 5, branchId = 5,
industry = "fashion" industry = "technology"
), ),
name = "Midnight-Snack.png", isDir = False,
name = "Graduation Certificate.png",
createTs = date_time.get_current_utc_date_time(as_string = False), createTs = date_time.get_current_utc_date_time(as_string = False),
length = 1024, length = 1024,
hash = "abcdefgh12345678", hash = "abcdefgh12345678",
@@ -288,11 +362,10 @@ if __name__ == "__main__":
"camera": "iPhone 1000 Pro Max XS" "camera": "iPhone 1000 Pro Max XS"
}, },
tags = [ tags = [
"image", "important"
"cute",
"cat"
], ],
parentId = "67519cf3a7804fcbc6f12452", # parentId = "67519cf3a7804fcbc6f12452",
parentId = None,
isPrivate = True, isPrivate = True,
sharedWith = [ sharedWith = [
CoreFileObjectSharingModel( CoreFileObjectSharingModel(
@@ -305,6 +378,8 @@ if __name__ == "__main__":
branchId = 10, branchId = 10,
industry = "entertainment" industry = "entertainment"
), ),
permissionType = "explicit",
inheritedFromId = None,
permissions = CoreFileObjectPermissionsModel( permissions = CoreFileObjectPermissionsModel(
read = True, read = True,
write = False, write = False,
+9 -1
View File
@@ -161,6 +161,12 @@ class CoreMessageModel(BaseModel):
frozen = True frozen = True
) )
usedAi: bool | None = Field(
description = "to mark when a sent message was generated by ai; null means the status is not known",
frozen = True,
default = None
)
# ┏┓ ┏• # ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓ # ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫ # ┗┛┗┛┛┗┛┗┗┫
@@ -183,7 +189,9 @@ class CoreMessageModel(BaseModel):
@field_validator("tokenId", mode = "before") @field_validator("tokenId", mode = "before")
def parse_oid(cls, value): def parse_oid(cls, value):
try: value = ObjectId(value) try:
if isinstance(value, str):
value = ObjectId(value)
except: pass except: pass
return value return value
+3 -1
View File
@@ -209,7 +209,9 @@ class CorePaymentModel(BaseModel):
@field_validator("tokenId", mode = "before") @field_validator("tokenId", mode = "before")
def parse_oid(cls, value): def parse_oid(cls, value):
try: value = ObjectId(value) try:
if isinstance(value, str):
value = ObjectId(value)
except: pass except: pass
return value return value