Skip to main content

Hi,



let’s say that I have a folder id as string.


I can get folder object from folder id as:


folder = self._client.folder(folder_id=folder_id).get()



Now I would like to iterate over folder items that can be files or (sub)folders.



        for item in folder.get_items():

if item.type == 'folder':

sub_folder_path = folder_path + '/' + folder.name



For last line mypy reports:


64: error: “Folder” has no attribute “name” attr-defined]



Looks like folder (the same is for file as well) has .name property, but it is injected somehow on the fly.



I don’t like putting #noqa nor doing hacks.


Is there anything I can do to pass mypy check with this line?



Regards,



Zoran

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:




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


Reply