Hi @zljmob
Humm, looks like what you want is:
for item in folder.get_items():
if item.type == 'folder':
sub_folder_path = folder_path + '/' + item.name
Does that solve your issue?
Hi rbarbosa, actually no.
Maybe I haven’t provided enought code.
Basically, self.flder_by_id(folder_id) return a folder object.
For mypy, Folder object doesnt have a name property at all.
Looks like name property is added on the fly and that is why I am getting:
error: Item "Folder" of "File | Folder" has no attribute "name" union-attr]
If Folder class is defined with name like this:
class Folder:
name: str
...
mypy wouldn’t complain.
Code with more details follows:
from boxsdk.object.file import File
from boxsdk.object.folder import Folder
folder: Folder = self.folder_by_id(folder_id)
for item in folder.get_items():
if item.type == 'folder':
sub_folder_path = folder_path + '/' + folder.name
For folder and item objects mypy doesn’t see name property at all.
For example folder class is defined as this:
class Folder(Item):
"""Box API endpoint for interacting with folders."""
_item_type = 'folder'
@api_call
def preflight_check(self, size: int, name: str) -> Optionaltstr]:
"""
Make an API call to check if a new file with given name and size can be uploaded to this folder.
Returns an accelerator URL if one is available.
:param size:
The size of the file in bytes. Specify 0 for unknown file-sizes.
:param name:
The name of the file to be uploaded.
:return:
The Accelerator upload url or None if cannot get the Accelerator upload url.
:raises:
:class:`BoxAPIException` when preflight check fails.
"""
return self._preflight_check(
size=size,
name=name,
parent_id=self._object_id,
)
...
No name here, than if you look at the Item class no name there… and so on.
Regards.
Oh! I see what you mean @zljmob
Many times I have to go around to get Python and the Box SDK to be more type explicit.
This is typically what I do:
Consider this example:
from typing import List
from boxsdk import JWTAuth, Client
from boxsdk.object.file import File
from boxsdk.object.folder import Folder
from boxsdk.object.item import Item
def get_client() -> Client:
config = JWTAuth.from_settings_file("config.json")
return Client(config)
def get_folder_items(folder: Folder) -> List[Item]:
return folder.get_items(limit=1000, offset=0)
def get_folder_by_id(client: Client, folder_id: str) -> Folder:
return client.folder(folder_id=folder_id).get()
def get_file_by_id(client: Client, file_id: str) -> File:
return client.file(file_id=file_id).get()
def main():
client = get_client()
folder = get_folder_by_id(client, "0")
print("Current Folder: " + folder.name)
items = get_folder_items(folder)
for item in items:
if isinstance(item, File):
print("\tFile: " + item.name)
elif isinstance(item, Folder):
print("\tFolder: " + item.name)
if __name__ == "__main__":
main()
The output is (I only have folders on my root):
Current Folder: All Files
Folder: 100k
Folder: aaaa
Folder: Bookings
Folder: Box UI Elements Demo
Folder: Classification Service Demo
Folder: JWT Folder for UI Sample Apps
Folder: My Signed Documents
Folder: Shared with RB
Folder: Waivers
Folder: Webhook
and mypy does not complain:
❯ mypy main.py
Success: no issues found in 1 source file
I know this is not ideal, and with the new generated Python SDK , the engineering folks wont make this change on the classic one.
By the way, if you have feedback on these lines for the generated SDK, now is the time to send it.
Let us know.
Cheers
Hi @rbarbosa ,
I wander how come that you haven’t got a mypy error in lines like:
print("\tFile: " + item.name)
print("\tFolder: " + item.name)
I copied your code to my project and checked it with mypy and you are right. There are no errors at all.
How come that in your example mypy doesn’t complain but in my case it complains?
Anyway, authors of the box had the same problem. Please look at the line and notice the comment:
"""
super().__init__(session=session, response_object=response_object)
self._object_id = object_id
@property
def _description(self) -> str:
"""
Base class override. Return a description for the object.
"""
if 'name' in self._response_object:
return f'{self._object_id} ({self.name})' # pylint:disable=no-member
return self._object_id
def get_url(self, *args: Any) -> str:
"""
Base class override.
Return the given object's URL, appending any optional parts as specified by args.
"""
return super().get_url(f'{self._item_type}s', self._object_id, *args)
def get_type_url(self) -> str:
Hi @zljmob
Well, we are trying to use a dynamically typed language and make it more statically typed using type hints.
So while the interpreter will only check the types at run time. The type hints are useful for tools like mypy or others like vscode extensions to check it at design time.
In the previous example notice how I created methods to get the files, folders and items and explicitly included the return types.
This way mypy and in my case a vscode extension, knows the specific type those methods are expected to return. If I don’t respect that I get a visual warning in vscode.
I’m personally a fan of type hints, and try to use them as much as possible. However Python appeals to many developers because it is dynamically typed.
I think you would like Rust, if you haven’t tried it yet. I doesn’t get more statically typed than that.
But I digress…
To your point:
You can probably make mypy happy with a few extra hints or a different way of specifying them, or even creating some abstraction layer like I did.
For this classic python SDK most likely the engineering team will not address the type hints.
You might want to take a look at the new generated SDK and see if the issue is still there.
Cheers