'Pinning directory through Pinata API with python

I am trying to use pinata's API to pin a whole directory, as it is specified in their API documentation.

I found some python implementation as well, but that still doesn't seem to work for me.

Basically I am getting all the files from the directory and trying to include them in the post request.

all_files: tp.List[str] = get_all_files(filepath)            
files = {"file": [(file, open(file, "rb")) for file in all_files]}

response: requests.Response = requests.post(url=ipfs_url, files=files, headers=headers)

The error I get is the following:

ValueError: too many values to unpack (expected 4)

I additionally tried it with some hard coded values, but still no luck:

all_files = ['images/0.png', 'images/1.png', 'images/2.png']
files = {"file": [(file, open(file, "rb")) for file in all_files]}

response: requests.Response = requests.post(url=ipfs_url, files=files, headers=headers)

In this case however I got a different error:

TypeError: expected string or bytes-like object

Also as another try I tried to experiment with stuff like this:

files = (
    ("file", ("0.png", open('images/0.png', "rb"))),
    ("file", ("1.png", open('images/1.png', "rb")))
)

response: requests.Response = requests.post(url=ipfs_url, files=files, headers=headers)

In this case, Pinata could not recognize what I wanted and returned the following error:

{'error': 'More than one file and/or directory was provided for pinning.'}

Could someone give me some hints about what am I doing wrong? Thanks!

EDIT: Adding a full example to use:

import os
import requests
import typing as tp

def get_all_files(directory: str) -> tp.List[str]:
    """get a list of absolute paths to every file located in the directory"""
    paths: tp.List[str] = []
    for root, dirs, files_ in os.walk(os.path.abspath(directory)):        
        for file in files_:            
             paths.append(os.path.join(root, file))
    return paths
    
  
print("hello")
# Example 1
all_files: tp.List[str] = get_all_files("images")            
files = {"file": [(file, open(file, "rb")) for file in all_files]}

# Example 2
#all_files = ['images/0.png', 'images/1.png', 'images/2.png']
#files = {"file": [(file, open(file, "rb")) for file in all_files]}

# Example 3
#files = (
#    ("file", ("0.png", open('images/0.png', "rb"))),
#    ("file", ("1.png", open('images/1.png', "rb")))
#)

print ("Files used: \n")
print (files)

headers = {        
    'pinata_api_key': "",
    'pinata_secret_api_key': ""     
}   

ipfs_url = "https://api.pinata.cloud/pinning/pinFileToIPFS"

response: requests.Response = requests.post(url=ipfs_url, files=files, headers=headers)
print(response.json())


Solution 1:[1]

Okay, I managed to upload the whole directory with specifying the files like this:

files = [
        ('file', ('images/0.png', open('images/0.png', "rb"))),
        ('file', ('images/1.png', open('images/1.png', "rb"))),
    ]

So overall to gather all the files and upload them, I was able to use the following code:

all_files: tp.List[str] = get_all_files(directory)            
files = [('file', (file, open(file, "rb"))) for file in all_files]

Solution 2:[2]

with this code i solved the problem. To understand the problem it was enough to upload directly from pinata and see what kind of call the browser was making.

It could be seen how the request payload was different:

  • The metadata was missing, in which the directory name must be contained
  • For each file it is necessary to define the filename in the directory / filename format but the default request library sets the filename with only the file name, if you print the body you can notice this thing

The code:

from os import walk, path, sep
from requests import Session, Request


def pinata_upload(directory):
    # directory is the abs path of dir
    files = []
    ipfs_url = "https://api.pinata.cloud/pinning/pinFileToIPFS"
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36',
        'pinata_api_key': "your_key",
        'pinata_secret_api_key': "your_secret"
    }
    # directory.split(sep)[-1] is the name of directory
    files.append(('pinataMetadata', (None, '{"name":"' + directory.split(sep)[-1] + '"}')))
    for root, dirs, files_ in walk(path.abspath(directory)):
        for f in files_:
            complete_path = path.join(root, f)
            # sep.join(complete_path.split(sep)[-2:]) create the name of file with the syntax directory/filename
            files.append(('file', (sep.join(complete_path.split(sep)[-2:]), open(complete_path, 'rb'))))
    request = Request(
        'POST',
        ipfs_url,
        headers=headers,
        files=files
    ).prepare()
    response = Session().send(request)
    print(response.request.url)
    print(response.request.headers)
    print(response.request.body)
    print(response.json())


if __name__ == '__main__':
    pinata_upload("/Users/simonesganzerla/Desktop/test")

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tamás Szabadi
Solution 2 Simone Sganzerla