Skip to content

Projects

Important

Projects within TickTick are known as Lists. However, in the API Lists are referred to as Projects to limit confusion with the list built in type.

Info

Project methods are accessed through the project public member of your TickTickClient instance.

# Assumes that 'client' is the name that references the TickTickClient instance.

project = client.project.method()

Question About Logging In or Other Functionality Available?

API and Important Information

Tip

All supported methods are documented below with usage examples, take a look!

All usage examples assume that client is the name referencing the TickTickClient instance

Example TickTick Project Dictionary

Members

Descriptions

It is possible that not all possible fields are present in the table.

Property Description Example Value Type Useful Values
id The ID of the project '5ffe93f3b04b35082bbce7b0' str N/A
name The name of the project 'Hobbies' str N/A
isOwner Whether you are the owner of the project or not. True bool N/A
color The hex color code for the project. '#6fcbdf' str N/A
inAll N/A True bool N/A
sortOrder A sort ID relative to other tasks in the project -1099511627776 int Lower sortOrder == Higher Position
sortType Sort type of the project dueDate str dueDate, sortOrder, title, tag, priority
userCount How many users have access to the project. 1 int N/A
etag Etag identifier. 'ji35exmv' str N/A
modifiedTime Time last modified. '2021-01-13T07:18:21.000+0000' str N/A
closed Archive status. False bool none and False = Not Archived, True = Archived
muted Whether the project is 'hidden' (no notifications) False bool N/A
transferred Possibly if the ownership of the project has changed. None bool N/A
groupId ID of the project folder if it exists. '5ffe11b7b04b356ce74d49da' str N/A
viewMode View mode of the project. 'kanban' str kanban or list
notificationOptions N/A None N/A N/A
teamId ID of your team. 342537403 int N/A
permission N/A None N/A N/A
kind If the project is a normal TASK project or NOTE project 'TASK' str 'TASK' or 'NOTE'
{'id': '5ffe24a18f081003f3294c44',
'name': 'Reading',
'isOwner': True,
'color': '#6fcbdf',
'inAll': True,
'sortOrder': 0,
'sortType': None,
'userCount': 1,
'etag': 'qbj4z0gl',
'modifiedTime': '2021-01-12T22:37:21.823+0000',
'closed': None,
'muted': False,
'transferred': None,
'groupId': '5ffe11b7b04b356ce74d49da',
'viewMode': None,
'notificationOptions': None,
'teamId': None,
'permission': None,
'kind': 'TASK'}

Example TickTick Project Folder (group) Dictionary

Members

Descriptions

It is possible that not all possible fields are present in the table.

Property Description Example Value Type Useful Values
id The ID of the project folder '5ffe93f3b04b35082bbce7b0' str N/A
etag Etag identifier. 'ji35exmv' str N/A
name The name of the project folder 'Hobbies' str N/A
showAll N/A True bool N/A
sortOrder A sort ID relative to other tasks in the project -1099511627776 int Lower sortOrder == Higher Position
deleted Whether the project folder is deleted or not. 0 int N/A
userId User ID of the creator 586938759 int N/A
sortType Sort type of the project folder dueDate str dueDate, project, title, tag, priority
teamId ID of your team. 342537403 int N/A
{'id': '600008f2b04b355792c7a42d',
'etag': 'yeozz5v8',
'name': 'Test',
'showAll': True,
'sortOrder': -6786182545408,
'deleted': 0,
'userId': 586934829,
'sortType': 'project',
'teamId': None}

ProjectManager

Handles all interactions for projects.

archive(self, ids)

Moves the project(s) to a project folder created by TickTick called "Archived Lists"

To unarchive a project, change its 'closed' field to True, then update

Parameters:

Name Type Description Default
ids str or list

Single Project (str): ID string of the project to archive.

Multiple Projects (list): List of ID strings of the projects to archive.

required

Returns:

Type Description
dict or list

Single Project (dict): Dictionary of the archived object.

Multiple Projects (list): List of dictionaries of the archived objects.

Exceptions:

Type Description
TypeError

If ids is not a string or list.

ValueError

If the project(s) don't already exist

RuntimeError

If the project(s) could not be successfully archived.

Example

# Lets assume there is a project that exists named "Reading"
reading_project = client.get_by_fields(name="Reading", search="projects")
reading_project_id = reading_project['id']
archived = client.project.archive(reading_project_id)
Result

A single dictionary is returned.

{'id': '5ffe1673e4b062d60dd29dc0', 'name': 'Reading', 'isOwner': True, 'color': '#51b9e3', 'inAll': True,
'sortOrder': 0, 'sortType': 'sortOrder', 'userCount': 1, 'etag': 'c9tgze9b',
'modifiedTime': '2021-01-13T00:34:50.449+0000', 'closed': True, 'muted': False,
'transferred': None, 'groupId': None, 'viewMode': None, 'notificationOptions': None,
'teamId': None, 'permission': None, 'kind': 'TASK'}

Before

archive-before.png

After

archived-after.png

# Lets assume there is a project that exists named "Reading"
reading_project = client.get_by_fields(name="Reading", search="projects")
reading_project_id = reading_project['id']
# Lets assume another project exists named "Writing"
writing_project = client.get_by_fields(name='Writing', search='projects')
writing_project_id = writing_project['id']
# Archiving multiple requires putting the ID's in a list.
archive_list = [reading_project_id, writing_project_id]
archived = client.project.archive(archive_list)
Result

A list of dictionary objects is returned.

[{'id': '5ffe1673e4b062d60dd29dc0', 'name': 'Reading', 'isOwner': True,
'color': '#51b9e3', 'inAll': True, 'sortOrder': -7335938359296,
'sortType': 'sortOrder', 'userCount': 1, 'etag': 'qrga45as',
'modifiedTime': '2021-01-13T00:40:49.839+0000', 'closed': True,
'muted': False, 'transferred': None, 'groupId': None, 'viewMode': None,
'notificationOptions': None, 'teamId': None, 'permission': None, 'kind': 'TASK'},

{'id': '5ffe41328f08237f3d147e33', 'name': 'Writing', 'isOwner': True,
'color': '#F2B04B', 'inAll': True, 'sortOrder': -7885694173184, 'sortType': 'sortOrder',
'userCount': 1, 'etag': 'aenkajam', 'modifiedTime': '2021-01-13T00:40:49.843+0000',
'closed': True, 'muted': False, 'transferred': None, 'groupId': None, 'viewMode': None,
'notificationOptions': None, 'teamId': None, 'permission': None, 'kind': 'TASK'}]

Before

archive-multiple-before.png

After

archived-multiple-after.png

Source code in managers/projects.py
def archive(self, ids):
    """
    Moves the project(s) to a project folder created by `TickTick` called "Archived Lists"

    To unarchive a project, change its `'closed'` field to `True`, then [update][managers.projects.ProjectManager.update]

    Arguments:
        ids (str or list):
            **Single Project (str)**: ID string of the project to archive.

            **Multiple Projects (list)**: List of ID strings of the projects to archive.

    Returns:
        dict or list:

        **Single Project (dict)**: Dictionary of the archived object.

        **Multiple Projects (list)**: List of dictionaries of the archived objects.

    Raises:
        TypeError: If `ids` is not a string or list.
        ValueError: If the project(s) don't already exist
        RuntimeError: If the project(s) could not be successfully archived.

    !!! example
        === "Single Project Archive"
            ```python
            # Lets assume there is a project that exists named "Reading"
            reading_project = client.get_by_fields(name="Reading", search="projects")
            reading_project_id = reading_project['id']
            archived = client.project.archive(reading_project_id)
            ```

            ??? success "Result"
                A single dictionary is returned.
                ```python
                {'id': '5ffe1673e4b062d60dd29dc0', 'name': 'Reading', 'isOwner': True, 'color': '#51b9e3', 'inAll': True,
                'sortOrder': 0, 'sortType': 'sortOrder', 'userCount': 1, 'etag': 'c9tgze9b',
                'modifiedTime': '2021-01-13T00:34:50.449+0000', 'closed': True, 'muted': False,
                'transferred': None, 'groupId': None, 'viewMode': None, 'notificationOptions': None,
                'teamId': None, 'permission': None, 'kind': 'TASK'}
                ```

                **Before**

                [![archive-before.png](https://i.postimg.cc/R0jfVt7W/archive-before.png)](https://postimg.cc/B8BtmXw3)

                **After**

                [![archived-after.png](https://i.postimg.cc/xjPkBh4J/archived-after.png)](https://postimg.cc/K4RvMqFx)

        === "Multiple Project Archive"
            ```python
            # Lets assume there is a project that exists named "Reading"
            reading_project = client.get_by_fields(name="Reading", search="projects")
            reading_project_id = reading_project['id']
            # Lets assume another project exists named "Writing"
            writing_project = client.get_by_fields(name='Writing', search='projects')
            writing_project_id = writing_project['id']
            # Archiving multiple requires putting the ID's in a list.
            archive_list = [reading_project_id, writing_project_id]
            archived = client.project.archive(archive_list)
            ```

            ??? success "Result"
                A list of dictionary objects is returned.
                ```python
                [{'id': '5ffe1673e4b062d60dd29dc0', 'name': 'Reading', 'isOwner': True,
                'color': '#51b9e3', 'inAll': True, 'sortOrder': -7335938359296,
                'sortType': 'sortOrder', 'userCount': 1, 'etag': 'qrga45as',
                'modifiedTime': '2021-01-13T00:40:49.839+0000', 'closed': True,
                'muted': False, 'transferred': None, 'groupId': None, 'viewMode': None,
                'notificationOptions': None, 'teamId': None, 'permission': None, 'kind': 'TASK'},

                {'id': '5ffe41328f08237f3d147e33', 'name': 'Writing', 'isOwner': True,
                'color': '#F2B04B', 'inAll': True, 'sortOrder': -7885694173184, 'sortType': 'sortOrder',
                'userCount': 1, 'etag': 'aenkajam', 'modifiedTime': '2021-01-13T00:40:49.843+0000',
                'closed': True, 'muted': False, 'transferred': None, 'groupId': None, 'viewMode': None,
                'notificationOptions': None, 'teamId': None, 'permission': None, 'kind': 'TASK'}]
                ```

                **Before**

                [![archive-multiple-before.png](https://i.postimg.cc/sgHHmnrb/archive-multiple-before.png)](https://postimg.cc/qNnGMxgG)

                **After**

                [![archived-multiple-after.png](https://i.postimg.cc/tg1SMhRJ/archived-multiple-after.png)](https://postimg.cc/rdkNdRr2)
    """
    if not isinstance(ids, str) and not isinstance(ids, list):
        raise TypeError('Ids Must Be A String or List Of Strings')

    objs = []
    if isinstance(ids, str):
        proj = self._client.get_by_fields(id=ids, search='projects')
        if not proj:
            raise ValueError(f"Project '{ids}' Does Not Exist To Archive")
        #  Change the list to archived
        proj['closed'] = True
        objs = [proj]
    else:
        for i in ids:
            proj = self._client.get_by_fields(id=i, search='projects')
            if not proj:
                raise ValueError(f"Project '{i}' Does Not Exist To Archive")
            proj['closed'] = True
            objs.append(proj)

    return self.update(objs)

builder(self, name, color='random', project_type='TASK', folder_id=None)

Creates and returns a local project object. Helper method for create to make batch creating projects easier.

Note

The project folder must already exist prior to calling this method.

Parameters:

Name Type Description Default
name str

Desired name of the project - project names cannot be repeated

required
color str

Hex color string. A random color will be generated if no color is specified.

'random'
project_type str

'TASK' or 'NOTE'

'TASK'
folder_id str

The project folder id that the project should be placed under (if desired)

None

Returns:

Type Description
dict

A dictionary containing all the fields necessary to create a remote project.

Exceptions:

Type Description
TypeError

If any of the types of the arguments are wrong.

ValueError

Project name already exists

ValueError

Project Folder corresponding to the ID does not exist.

ValueError

The hex string color inputted is invalid.

Argument rules are shared with create, so for more examples on how to use the arguments see that method.

Example

project_name = 'Work'  # The name of our project

# Lets assume that we have a project group folder that already exists named 'Productivity'
productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
productivity_id = productivity_folder['id']

# Build the object
project_object = client.project.builder(project_name, folder_id=productivity_id)
Result
# The fields needed for a successful project creation are set.
{'name': 'Work', 'color': '#665122', 'kind': 'TASK', 'groupId': '5ffe11b7b04b356ce74d49da'}
Source code in managers/projects.py
def builder(self, name: str, color: str = 'random', project_type: str = 'TASK', folder_id: str = None) -> dict:
    """
    Creates and returns a local project object. Helper method for [create][managers.projects.ProjectManager.create]
    to make batch creating projects easier.

    !!! note
        The project [folder][managers.projects.ProjectManager.create_folder] must already exist prior to calling this method.

    Arguments:
        name: Desired name of the project - project names cannot be repeated
        color: Hex color string. A random color will be generated if no color is specified.
        project_type: 'TASK' or 'NOTE'
        folder_id: The project folder id that the project should be placed under (if desired)

    Returns:
        A dictionary containing all the fields necessary to create a remote project.

    Raises:
        TypeError: If any of the types of the arguments are wrong.
        ValueError: Project name already exists
        ValueError: Project Folder corresponding to the ID does not exist.
        ValueError: The hex string color inputted is invalid.

    Argument rules are shared with [create][managers.projects.ProjectManager.create], so for more examples on how
    to use the arguments see that method.

    !!! example
        ```python
        project_name = 'Work'  # The name of our project

        # Lets assume that we have a project group folder that already exists named 'Productivity'
        productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
        productivity_id = productivity_folder['id']

        # Build the object
        project_object = client.project.builder(project_name, folder_id=productivity_id)
        ```

        ??? success "Result"
            ```python
            # The fields needed for a successful project creation are set.
            {'name': 'Work', 'color': '#665122', 'kind': 'TASK', 'groupId': '5ffe11b7b04b356ce74d49da'}
            ```
    """
    if not isinstance(name, str):
        raise TypeError("Name must be a string")
    if not isinstance(color, str) and color is not None:
        raise TypeError("Color must be a string")
    if not isinstance(project_type, str):
        raise TypeError("Project type must be a string")
    if not isinstance(folder_id, str) and folder_id is not None:
        raise TypeError("Folder id must be a string")

    # Go through self.state['lists'] and determine if the name already exists
    id_list = self._client.get_by_fields(search='projects', name=name)
    if id_list:
        raise ValueError(f"Invalid Project Name '{name}' -> It Already Exists")

    # Determine if parent list exists
    if folder_id is not None:
        parent = self._client.get_by_id(folder_id, search='project_folders')
        if not parent:
            raise ValueError(f"Parent Id {folder_id} Does Not Exist")

    # Make sure project type is valid
    if project_type != 'TASK' and project_type != 'NOTE':
        raise ValueError(f"Invalid Project Type '{project_type}' -> Should be 'TASK' or 'NOTE'")

    # Check color_id
    if color == 'random':
        color = generate_hex_color()  # Random color will be generated
    elif color is not None:
        if not check_hex_color(color):
            raise ValueError('Invalid Hex Color String')

    return {'name': name, 'color': color, 'kind': project_type, 'groupId': folder_id}

create(self, name, color='random', project_type='TASK', folder_id=None)

Creates a project remotely. Supports single project creation or batch project creation.

Parameters:

Name Type Description Default
name str or list

Single Project (str) : The desired name of the project. Project names cannot be repeated.

Multiple Projects (list) : A list of project objects created using the builder method.

required
color str

Hex color string. A random color will be generated if no color is specified.

'random'
project_type str

'TASK' or 'NOTE'

'TASK'
folder_id str

The project folder id that the project should be placed under (if desired)

None

Returns:

Type Description
dict or list

Single Project: Return the dictionary of the object.

Multiple Projects: Return a list of dictionaries containing all the created objects in the same order as created.

Exceptions:

Type Description
TypeError

If any of the types of the arguments are wrong.

ValueError

Project name already exists

ValueError

Project Folder corresponding to the ID does not exist.

ValueError

The hex string color inputted is invalid.

RuntimeError

The project(s) could not be created.

Single Project

project = client.project.create('School')
Result

# The dictionary object of the created project is returned.
{'id': '5ffe1673e4b062d60dd29dc0', 'name': 'School', 'isOwner': True, 'color': '#51b9e3', 'inAll': True,
'sortOrder': 0, 'sortType': None, 'userCount': 1, 'etag': 'uerkdkcd',
'modifiedTime': '2021-01-12T21:36:51.890+0000', 'closed': None, 'muted': False,
'transferred': None, 'groupId': None, 'viewMode': None, 'notificationOptions': None,
'teamId': None, 'permission': None, 'kind': 'TASK'}
Our project is created.

project-create.png

A random color can be generated using generate_hex_color. However, just not specifying a color will automatically generate a random color (as seen in the previous tab). You can always specify the color that you want.

project = client.project.create('Work', color='#86bb6d')
Result

Our project is created with the color specified.

project-color.png

The default project type is for Tasks. To change the type to handle Notes, pass in the string 'NOTE'

project = client.project.create('Hobbies', project_type='NOTE')
Result

The project type is now for notes.

project-note.png

Note For folder_id

The project folder must already exist prior to calling this method.

project_name = 'Day Job'  # The name of our project

# Lets assume that we have a project group folder that already exists named 'Productivity'
productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
productivity_id = productivity_folder['id']

# Create the object
project_object = client.project.create(project_name, folder_id=productivity_id)
Result

The project was created in the group folder.

project-create-with-folder.png

Multiple Projects (batch)

To create multiple projects, you will need to build the projects locally prior to calling the create method. This can be accomplished using the builder method. Pass in a list of the locally created project objects to create them remotely.

(Again About Folders)

The project folders should already be created prior to calling the create method.

# Lets assume that we have a project group folder that already exists named 'Productivity'
productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
productivity_id = productivity_folder['id']
# Names of our projects
name_1 = 'Reading'
name_2 = 'Writing'
# Build the local projects
project1 = client.project.builder(name_1, folder_id=productivity_id)
project2 = client.project.builder(name_2, folder_id=productivity_id)
project_list = [project1, project2]
# Create the projects
project_object = client.project.create(project_list)
Result

When multiple projects are created, the dictionaries will be returned in a list in the same order as the input.

[{'id': '5ffe24a18f081003f3294c44', 'name': 'Reading', 'isOwner': True, 'color': '#6fcbdf',
'inAll': True, 'sortOrder': 0, 'sortType': None, 'userCount': 1, 'etag': 'qbj4z0gl',
'modifiedTime': '2021-01-12T22:37:21.823+0000', 'closed': None, 'muted': False, 'transferred': None,
'groupId': '5ffe11b7b04b356ce74d49da', 'viewMode': None, 'notificationOptions': None, 'teamId': None,
'permission': None, 'kind': 'TASK'},

{'id': '5ffe24a18f081003f3294c46', 'name': 'Writing', 'isOwner': True,
'color': '#9730ce', 'inAll': True, 'sortOrder': 0, 'sortType': None, 'userCount': 1, 'etag': 'u0loxz2v',
'modifiedTime': '2021-01-12T22:37:21.827+0000', 'closed': None, 'muted': False, 'transferred': None,
'groupId': '5ffe11b7b04b356ce74d49da', 'viewMode': None, 'notificationOptions': None, 'teamId': None,
'permission': None, 'kind': 'TASK'}]
project-batch-create.png

Source code in managers/projects.py
def create(self, name, color: str = 'random', project_type: str = 'TASK', folder_id: str = None):
    """
    Creates a project remotely. Supports single project creation or batch project creation.

    Arguments:
        name (str or list):
            **Single Project** (str) : The desired name of the project. Project names cannot be repeated.

            **Multiple Projects** (list) : A list of project objects created using the [builder][managers.projects.ProjectManager.builder] method.
        color: Hex color string. A random color will be generated if no color is specified.
        project_type: 'TASK' or 'NOTE'
        folder_id: The project folder id that the project should be placed under (if desired)

    Returns:
        dict or list: **Single Project**: Return the dictionary of the object.

        **Multiple Projects**: Return a list of dictionaries containing all the created objects in the same order as created.

    Raises:
        TypeError: If any of the types of the arguments are wrong.
        ValueError: Project name already exists
        ValueError: Project Folder corresponding to the ID does not exist.
        ValueError: The hex string color inputted is invalid.
        RuntimeError: The project(s) could not be created.

    !!! example "Single Project"

        === "Just A Name"
            ```python
            project = client.project.create('School')
            ```

            ??? success "Result"
                ```python
                # The dictionary object of the created project is returned.
                {'id': '5ffe1673e4b062d60dd29dc0', 'name': 'School', 'isOwner': True, 'color': '#51b9e3', 'inAll': True,
                'sortOrder': 0, 'sortType': None, 'userCount': 1, 'etag': 'uerkdkcd',
                'modifiedTime': '2021-01-12T21:36:51.890+0000', 'closed': None, 'muted': False,
                'transferred': None, 'groupId': None, 'viewMode': None, 'notificationOptions': None,
                'teamId': None, 'permission': None, 'kind': 'TASK'}
                ```
                Our project is created.

                [![project-create.png](https://i.postimg.cc/d1NNqN7F/project-create.png)](https://postimg.cc/PpZQy4zV)

        === "Specify a Color"
            A random color can be generated using [generate_hex_color][helpers.hex_color.generate_hex_color].
            However, just not specifying a color will automatically generate a random color (as seen in the previous tab).
            You can always specify the color that you want.

            ```python
            project = client.project.create('Work', color='#86bb6d')
            ```

            ??? success "Result"
                Our project is created with the color specified.

                [![project-color.png](https://i.postimg.cc/K8ppnvrb/project-color.png)](https://postimg.cc/5XvmJJRK)

        === "Changing the Project Type"
            The default project type is for Tasks. To change the type to handle Notes, pass in the string 'NOTE'

            ```python
            project = client.project.create('Hobbies', project_type='NOTE')
            ```

            ??? success "Result"
                The project type is now for notes.

                [![project-note.png](https://i.postimg.cc/fy0Mhrzt/project-note.png)](https://postimg.cc/rRcB1gtM)

        === "Creating Inside of A Folder"
            !!! warning "Note For `folder_id`"
                The project [folder][managers.projects.ProjectManager.create_folder] must already exist prior to calling this method.

            ```python
            project_name = 'Day Job'  # The name of our project

            # Lets assume that we have a project group folder that already exists named 'Productivity'
            productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
            productivity_id = productivity_folder['id']

            # Create the object
            project_object = client.project.create(project_name, folder_id=productivity_id)
            ```

            ??? success "Result"
                The project was created in the group folder.

                [![project-create-with-folder.png](https://i.postimg.cc/mr53rmfN/project-create-with-folder.png)](https://postimg.cc/rd5RnCpK)

    !!! example "Multiple Projects (batch)"
        To create multiple projects, you will need to build the projects locally prior to calling the `create` method. This
        can be accomplished using the [builder][managers.projects.ProjectManager.builder] method. Pass in a list of the locally created
        project objects to create them remotely.

        !!! warning "(Again About Folders)"
            The project folders should already be created prior to calling the create method.

        ```python
        # Lets assume that we have a project group folder that already exists named 'Productivity'
        productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
        productivity_id = productivity_folder['id']
        # Names of our projects
        name_1 = 'Reading'
        name_2 = 'Writing'
        # Build the local projects
        project1 = client.project.builder(name_1, folder_id=productivity_id)
        project2 = client.project.builder(name_2, folder_id=productivity_id)
        project_list = [project1, project2]
        # Create the projects
        project_object = client.project.create(project_list)
        ```

        ??? success "Result"
            When multiple projects are created, the dictionaries will be returned in a list in the same order as
            the input.

            ```python
            [{'id': '5ffe24a18f081003f3294c44', 'name': 'Reading', 'isOwner': True, 'color': '#6fcbdf',
            'inAll': True, 'sortOrder': 0, 'sortType': None, 'userCount': 1, 'etag': 'qbj4z0gl',
            'modifiedTime': '2021-01-12T22:37:21.823+0000', 'closed': None, 'muted': False, 'transferred': None,
            'groupId': '5ffe11b7b04b356ce74d49da', 'viewMode': None, 'notificationOptions': None, 'teamId': None,
            'permission': None, 'kind': 'TASK'},

            {'id': '5ffe24a18f081003f3294c46', 'name': 'Writing', 'isOwner': True,
            'color': '#9730ce', 'inAll': True, 'sortOrder': 0, 'sortType': None, 'userCount': 1, 'etag': 'u0loxz2v',
            'modifiedTime': '2021-01-12T22:37:21.827+0000', 'closed': None, 'muted': False, 'transferred': None,
            'groupId': '5ffe11b7b04b356ce74d49da', 'viewMode': None, 'notificationOptions': None, 'teamId': None,
            'permission': None, 'kind': 'TASK'}]
            ```
            [![project-batch-create.png](https://i.postimg.cc/8CHH8xSZ/project-batch-create.png)](https://postimg.cc/d7hdrHDC)

    """
    if isinstance(name, list):
        # If task name is a list, we will batch create objects
        obj = name
        batch = True
    # Create the single project object
    elif isinstance(name, str):
        batch = False
        obj = self.builder(name=name,
                           color=color,
                           project_type=project_type,
                           folder_id=folder_id)
        obj = [obj]

    else:
        raise TypeError(f"Required Positional Argument Must Be A String or List of Project Objects")

    url = self._client.BASE_URL + 'batch/project'
    payload = {
        'add': obj
    }
    response = self._client.http_post(url, json=payload, cookies=self._client.cookies, headers=self.headers)
    self._client.sync()
    if len(obj) == 1:
        return self._client.get_by_id(self._client.parse_id(response), search='projects')
    else:
        etag = response['id2etag']
        etag2 = list(etag.keys())  # Get the ids
        items = [''] * len(obj)  # Create enough spots for the objects
        for proj_id in etag2:
            found = self._client.get_by_id(proj_id, search='projects')
            for original in obj:
                if found['name'] == original['name']:
                    # Get the index of original
                    index = obj.index(original)
                    # Place found at the index in return list
                    items[index] = found
        return items

create_folder(self, name)

Creates a project folder to allow for project grouping. Project folder names can be repeated.

Parameters:

Name Type Description Default
name str or list

Single Folder (str): A string for the name of the folder

Multiple Folders (list): A list of strings for names of the folders.

required

Returns:

Type Description
dict or list

Single Folder (dict): A dictionary for the created folder.

Multiple Folders (list): A list of dictionaries for the created folders.

Exceptions:

Type Description
TypeError

If name is not a string or list

RuntimeError

If the folder(s) could not be created.

Example

A single string for the name is the only parameter needed.

project_folder = client.project.create_folder('Productivity')
Result

A single dictionary is returned.

{'id': '5ffe44528f089fb5795c45bf', 'etag': '9eun9kyc', 'name': 'Productivity', 'showAll': True,
'sortOrder': 0, 'deleted': 0, 'userId': 115781412, 'sortType': 'project', 'teamId': None}

folder.png

The desired names of the folders are passed to create as a list.

names = ['Productivity', 'School', 'Hobbies']
project_folder = client.project.create_folder(names)
Result

A list of dictionaries containing the foler objects is returned.

[{'id': '5ffe45d6e4b062d60dd3ce15', 'etag': '4nvnuiw1', 'name': 'Productivity',
'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
'teamId': None},

{'id': '5ffe45d6e4b062d60dd3ce16', 'etag': 's072l3pu', 'name': 'School',
'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
'teamId': None},

{'id': '5ffe45d6e4b062d60dd3ce17', 'etag': '12t1xmt9', 'name': 'Hobbies',
'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
'teamId': None}]

folders-multiple.png

Source code in managers/projects.py
def create_folder(self, name):
    """
    Creates a project folder to allow for project grouping. Project folder names can be repeated.

    Arguments:
        name (str or list):
            **Single Folder (str)**: A string for the name of the folder

            **Multiple Folders (list)**: A list of strings for names of the folders.

    Returns:
        dict or list:
        **Single Folder (dict)**: A dictionary for the created folder.

        **Multiple Folders (list)**: A list of dictionaries for the created folders.

    Raises:
        TypeError: If `name` is not a string or list
        RuntimeError: If the folder(s) could not be created.

    !!! example

        === "Creating a Single Folder"
            A single string for the name is the only parameter needed.

            ```python
            project_folder = client.project.create_folder('Productivity')
            ```

            ??? success "Result"
                A single dictionary is returned.

                ```python
                {'id': '5ffe44528f089fb5795c45bf', 'etag': '9eun9kyc', 'name': 'Productivity', 'showAll': True,
                'sortOrder': 0, 'deleted': 0, 'userId': 115781412, 'sortType': 'project', 'teamId': None}
                ```

                [![folder.png](https://i.postimg.cc/HWRTjtRW/folder.png)](https://postimg.cc/c6RpbfdP)



        === "Creating Multiple Folders"
            The desired names of the folders are passed to create as a list.

            ```python
            names = ['Productivity', 'School', 'Hobbies']
            project_folder = client.project.create_folder(names)
            ```


            ??? success "Result"
                A list of dictionaries containing the foler objects is returned.

                ```python
                [{'id': '5ffe45d6e4b062d60dd3ce15', 'etag': '4nvnuiw1', 'name': 'Productivity',
                'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
                'teamId': None},

                {'id': '5ffe45d6e4b062d60dd3ce16', 'etag': 's072l3pu', 'name': 'School',
                'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
                'teamId': None},

                {'id': '5ffe45d6e4b062d60dd3ce17', 'etag': '12t1xmt9', 'name': 'Hobbies',
                'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
                'teamId': None}]
                ```

                [![folders-multiple.png](https://i.postimg.cc/2jwXKjds/folders-multiple.png)](https://postimg.cc/0rzf6sBn)
    """
    if not isinstance(name, str) and not isinstance(name, list):
        raise TypeError('Name Must Be A String or List Of Strings')

    objs = []
    if isinstance(name, str):
        names = {
                'name': name,
                'listType': 'group'
                }
        objs = [names]

    else:
        for nm in name:
            objs.append({
                'name': nm,
                'listType': 'group'
            })

    url = self._client.BASE_URL + 'batch/projectGroup'
    payload = {
        'add': objs
    }
    response = self._client.http_post(url, json=payload, cookies=self._client.cookies, headers=self.headers)
    self._client.sync()
    if len(objs) == 1:
        return self._client.get_by_id(self._client.parse_id(response), search='project_folders')
    else:
        etag = response['id2etag']
        etag2 = list(etag.keys())  # Get the ids
        items = [''] * len(objs)  # Create enough spots for the objects
        for proj_id in etag2:
            found = self._client.get_by_id(proj_id, search='project_folders')
            for original in objs:
                if found['name'] == original['name']:
                    # Get the index of original
                    index = objs.index(original)
                    # Place found at the index in return list
                    items[index] = found
        return items

delete(self, ids)

Deletes the project(s) with the passed ID string.

Warning

Tasks will be deleted from the project. If you want to preserve the tasks before deletion, use move_all

Parameters:

Name Type Description Default
ids str or list

Single Deletion (str): ID string of the project

Multiple Deletions (list): List of ID strings of projects to be deleted.

required

Returns:

Type Description
dict or list

Single Deletion (dict): Dictionary of the deleted project.

Multiple Deletions (list): A list of dictionaries of the deleted projects.

Exceptions:

Type Description
TypeError

If ids is not a string or list of strings

ValueError

If ids does not exist.

RuntimeError

If the deletion was not successful.

Example

# Lets assume that we have a project that exists named 'School'
school = client.get_by_fields(name='School', search='projects')  # Get the project object
project_id = school['id']  # Get the project id
delete = client.project.delete(project_id)

A dictionary of the deleted project object will be returned.

# Lets assume that we have two projects that we want to delete: 'School' and 'Work'
school = client.get_by_fields(name='School', search='projects')  # Get the project object
work = client.get_by_fields(name='Work', search='projects')
delete_ids = [school['id'], work['id']]  # A list of the ID strings of the projects to be deleted
delete = client.project.delete(delete_ids)

A list of the deleted dictionary objects will be returned.

Source code in managers/projects.py
def delete(self, ids):
    """
    Deletes the project(s) with the passed ID string.

    !!! warning
        [Tasks](tasks.md) will be deleted from the project. If you want to preserve the
        tasks before deletion, use [move_all][managers.tasks.TaskManager.move_all]

    Arguments:
        ids (str or list):
            **Single Deletion (str)**: ID string of the project

            **Multiple Deletions (list)**: List of ID strings of projects to be deleted.

    Returns:
        dict or list:
        **Single Deletion (dict)**: Dictionary of the deleted project.

        **Multiple Deletions (list)**: A list of dictionaries of the deleted projects.

    Raises:
        TypeError: If `ids` is not a string or list of strings
        ValueError: If `ids` does not exist.
        RuntimeError: If the deletion was not successful.

    !!! example

        === "Single Project Deletion"

            ```python
            # Lets assume that we have a project that exists named 'School'
            school = client.get_by_fields(name='School', search='projects')  # Get the project object
            project_id = school['id']  # Get the project id
            delete = client.project.delete(project_id)
            ```

            A dictionary of the deleted project object will be returned.

        === "Multiple Project Deletion"
            ```python
            # Lets assume that we have two projects that we want to delete: 'School' and 'Work'
            school = client.get_by_fields(name='School', search='projects')  # Get the project object
            work = client.get_by_fields(name='Work', search='projects')
            delete_ids = [school['id'], work['id']]  # A list of the ID strings of the projects to be deleted
            delete = client.project.delete(delete_ids)
            ```

            A list of the deleted dictionary objects will be returned.

    """
    if not isinstance(ids, str) and not isinstance(ids, list):
        raise TypeError('Ids Must Be A String or List Of Strings')

    if isinstance(ids, str):
        proj = self._client.get_by_fields(id=ids, search='projects')
        if not proj:
            raise ValueError(f"Project '{ids}' Does Not Exist To Delete")
        ids = [ids]
    else:
        for i in ids:
            proj = self._client.get_by_fields(id=i, search='projects')
            if not proj:
                raise ValueError(f"Project '{i}' Does Not Exist To Delete")

    # Delete the task
    url = self._client.BASE_URL + 'batch/project'
    payload = {
        'delete': ids
    }
    self._client.http_post(url, json=payload, cookies=self._client.cookies, headers=self.headers)
    # Delete the list
    deleted_list = []
    for current_id in ids:
        tasks = self._client.task.get_from_project(current_id)
        for task in tasks:
            self._client.delete_from_local_state(id=task['id'], search='tasks')
        deleted_list.append(self._client.delete_from_local_state(id=current_id, search='projects'))

    if len(deleted_list) == 1:
        return deleted_list[0]
    else:
        return deleted_list

delete_folder(self, ids)

Deletes the folder(s).

Tip

Any projects inside of the folder will be preserved - they will just not be grouped anymore.

Parameters:

Name Type Description Default
ids str or list

Single Folder (str): The ID of the folder to be deleted.

Multiple Folders (list): A list containing the ID strings of the folders to be deleted.

required

Returns:

Type Description
dict or list

Single Folder (dict): The dictionary object for the deleted folder.

Multiple Folders (list): A list of dictionary objects of the deleted folders.

Exceptions:

Type Description
TypeError

If ids is not a str or list

ValueError

If ids does not match an actual folder object.

RunTimeError

If the folders could not be successfully deleted.

Folder Deletion

Pass in the ID of the folder object to delete it remotely.

# Lets assume we have a folder named "Productivity"
project_folder = client.get_by_fields(name='Productivity', search='project_folders')  # Get the project folder
deleted_folder = client.project.delete_folder(project_folder['id'])
Result

The folder is deleted, and a single dictionary of the deleted folder object is returned.

{'id': '5ffe75008f089fb5795d544a', 'etag': 'e95rdzi7', 'name': 'Productivity',
'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584,
'sortType': 'project', 'teamId': None}

Before

image

After

The project inside still exists.

image

Pass in the list of ID strings of the folders to be deleted.

# Lets assume that we have two folders that already exist: "Productivity" and "Hobbies"
productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
hobbies_folder = client.get_by_fields(name='Hobbies', search='project_folders')
ids = [productivity_folder['id'], hobbies_folder['id']]
deleted_folders = client.project.delete_folder(ids)
Result

The folders are deleted, and a list of dictionaries for the deleted folder objects are returned.

[{'id': '5ffe79d78f08237f3d1636ad', 'etag': '2o2dn2al', 'name': 'Productivity',
'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
'teamId': None},

{'id': '5ffe79d78f08237f3d1636ae', 'etag': 'mah5a78l', 'name': 'Hobbies',
'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
'teamId': None}]

Before

image

After

All folders deleted and all projects retained.

image

Source code in managers/projects.py
def delete_folder(self, ids):
    """
    Deletes the folder(s).

    !!! tip
        Any projects inside of the folder will be preserved - they will just not be grouped anymore.

    Arguments:
        ids (str or list):
            **Single Folder (str)**: The ID of the folder to be deleted.

            **Multiple Folders (list)**: A list containing the ID strings of the folders to be deleted.

    Returns:
        dict or list:
        **Single Folder (dict)**: The dictionary object for the deleted folder.

        **Multiple Folders (list)**: A list of dictionary objects of the deleted folders.

    Raises:
        TypeError: If `ids` is not a str or list
        ValueError: If `ids` does not match an actual folder object.
        RunTimeError: If the folders could not be successfully deleted.

    !!! example "Folder Deletion"

        === "Single Folder Deletion"
            Pass in the ID of the folder object to delete it remotely.

            ```python
            # Lets assume we have a folder named "Productivity"
            project_folder = client.get_by_fields(name='Productivity', search='project_folders')  # Get the project folder
            deleted_folder = client.project.delete_folder(project_folder['id'])
            ```

            ??? success "Result"
                The folder is deleted, and a single dictionary of the deleted folder object is returned.

                ```python
                {'id': '5ffe75008f089fb5795d544a', 'etag': 'e95rdzi7', 'name': 'Productivity',
                'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584,
                'sortType': 'project', 'teamId': None}
                ```

                **Before**

                ![image](https://user-images.githubusercontent.com/56806733/104407093-b5573e80-5515-11eb-99dc-16ca4f33d06a.png)

                **After**

                The project inside still exists.

                ![image](https://user-images.githubusercontent.com/56806733/104407123-c607b480-5515-11eb-92ff-15df1d41b404.png)


        === "Multiple Folder Deletion"

            Pass in the list of ID strings of the folders to be deleted.

            ```python
            # Lets assume that we have two folders that already exist: "Productivity" and "Hobbies"
            productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
            hobbies_folder = client.get_by_fields(name='Hobbies', search='project_folders')
            ids = [productivity_folder['id'], hobbies_folder['id']]
            deleted_folders = client.project.delete_folder(ids)
            ```

            ??? success "Result"
                The folders are deleted, and a list of dictionaries for the deleted folder objects are returned.

                ```python
                [{'id': '5ffe79d78f08237f3d1636ad', 'etag': '2o2dn2al', 'name': 'Productivity',
                'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
                'teamId': None},

                {'id': '5ffe79d78f08237f3d1636ae', 'etag': 'mah5a78l', 'name': 'Hobbies',
                'showAll': True, 'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project',
                'teamId': None}]
                ```

                **Before**

                ![image](https://user-images.githubusercontent.com/56806733/104407469-8097b700-5516-11eb-9919-069e5beb3b8a.png)

                **After**

                All folders deleted and all projects retained.

                ![image](https://user-images.githubusercontent.com/56806733/104407546-a8871a80-5516-11eb-815b-4df41e3d797a.png)
    """
    if not isinstance(ids, str) and not isinstance(ids, list):
        raise TypeError('Ids Must Be A String or List Of Strings')

    if isinstance(ids, str):
        proj = self._client.get_by_fields(id=ids, search='project_folders')
        if not proj:
            raise ValueError(f"Project Folder '{ids}' Does Not Exist To Delete")
        ids = [ids]
    else:
        for i in ids:
            proj = self._client.get_by_fields(id=i, search='project_folders')
            if not proj:
                raise ValueError(f"Project Folder '{i}' Does Not Exist To Delete")

    url = self._client.BASE_URL + 'batch/projectGroup'
    payload = {
        'delete': ids
    }
    self._client.http_post(url, json=payload, cookies=self._client.cookies, headers=self.headers)
    # Delete the list
    deleted_list = []
    for current_id in ids:
        deleted_list.append(self._client.get_by_id(current_id, search='project_folders'))
    self._client.sync()

    if len(deleted_list) == 1:
        return deleted_list[0]
    else:
        return deleted_list

update(self, obj)

Updates the passed project(s). Supports single project update and multiple project update (batch)

Make local changes to the project objects that you want to change first, then pass the actual objects to the method.

Info

Every potential update to a project's attributes have not been tested. See Example TickTick Project Dictionary for a listing of the fields present in a project.

Parameters:

Name Type Description Default
obj dict or list

Single Project (dict): The project dictionary.

Multiple Projects (list): A list of project dictionaries.

required

Returns:

Type Description
dict or list

Single Project (dict): The updated project dictionary

Multiple Projects (list): A list containing the updated project dictionaries.

Exceptions:

Type Description
TypeError

If the input is not a dict or a list.

RuntimeError

If the projects could not be updated successfully.

Updates are done by changing the fields in the objects locally first.

Single Project Update

# Lets assume that we have a project named "Reading" that we want to change to "Summer Reading"
project = client.get_by_fields(name='Reading', search='projects')  # Get the project
# Now lets change the name
project['name'] = 'Summer Reading'
# Updating a single project requires just passing in the entire dictionary.
updated = client.project.update(project)
Result

The dictionary is returned and the name changed remotely.

{'id': '5ffe24a18f081003f3294c44', 'name': 'Summer Reading', 'isOwner': True,
'color': '#6fcbdf', 'inAll': True, 'sortOrder': -6236426731520,
'sortType': 'sortOrder', 'userCount': 1, 'etag': '0vbsvn8e', 'modifiedTime': '2021-01-12T23:38:16.456+0000',
'closed': None, 'muted': False, 'transferred': None, 'groupId': '5ffe2d37b04b35082bbcdf74',
'viewMode': 'list', 'notificationOptions': None, 'teamId': None,
'permission': None, 'kind': 'TASK'}
Before

project-update-before.png

After

project-update-after.png

Multiple Project Update

# Lets assume that we have a project named "Writing" that we want to change to "Summer Reading"
project = client.get_by_fields(name='Writing', search='projects')  # Get the project
project['name'] = 'Summer Writing'
# Lets assume that we have a project named "Movies" that we want to change to "Summer Movies"
movie_project = client.get_by_fields(name='Movies', search='projects')
movie_project['name'] = 'Summer Movies'
# Updating multiple projects requires passing the projects in a list.
update_list = [project, movie_project]
# Lets update remotely now
updated_projects = client.project.update(update_list)
Result

A list containing the updated projects is returned.

[{'id': '5ffe24a18f081003f3294c46', 'name': 'Summer Reading',
'isOwner': True, 'color': '#9730ce', 'inAll': True, 'sortOrder': 0,
'sortType': None, 'userCount': 1, 'etag': 'bgl0pkm8',
'modifiedTime': '2021-01-13T00:13:29.796+0000', 'closed': None,
'muted': False, 'transferred': None, 'groupId': '5ffe11b7b04b356ce74d49da',
'viewMode': None, 'notificationOptions': None, 'teamId': None, 'permission': None,
'kind': 'TASK'},

{'id': '5ffe399c8f08237f3d144ece', 'name': 'Summer Movies', 'isOwner': True,
'color': '#F18181', 'inAll': True, 'sortOrder': -2843335458816, 'sortType': 'sortOrder',
'userCount': 1, 'etag': 'jmjy1xtc', 'modifiedTime': '2021-01-13T00:13:29.800+0000',
'closed': None, 'muted': False, 'transferred': None, 'groupId': '5ffe11b7b04b356ce74d49da',
'viewMode': None, 'notificationOptions': None, 'teamId': None, 'permission': None, 'kind': 'TASK'}]

Before

project-update-multiople.png

After

project-update-multiple-after.png

Source code in managers/projects.py
def update(self, obj):
    """
    Updates the passed project(s). Supports single project update and multiple project update (batch)

    Make local changes to the project objects that you want to change first, then pass the actual objects to the method.

    !!! info
        Every potential update to a project's attributes have not been tested. See [Example `TickTick` Project Dictionary](projects.md#example-ticktick-project-dictionary) for
        a listing of the fields present in a project.

    Arguments:
        obj (dict or list):
            **Single Project (dict)**: The project dictionary.

            **Multiple Projects (list)**: A list of project dictionaries.

    Returns:
        dict or list:
        **Single Project (dict)**: The updated project dictionary

        **Multiple Projects (list)**: A list containing the updated project dictionaries.

    Raises:
        TypeError: If the input is not a dict or a list.
        RuntimeError: If the projects could not be updated successfully.

    Updates are done by changing the fields in the objects locally first.

    !!! example "Single Project Update"

        === "Changing The Name"
            ```python
            # Lets assume that we have a project named "Reading" that we want to change to "Summer Reading"
            project = client.get_by_fields(name='Reading', search='projects')  # Get the project
            # Now lets change the name
            project['name'] = 'Summer Reading'
            # Updating a single project requires just passing in the entire dictionary.
            updated = client.project.update(project)
            ```

            ??? success "Result"
                The dictionary is returned and the name changed remotely.
                ```python
                {'id': '5ffe24a18f081003f3294c44', 'name': 'Summer Reading', 'isOwner': True,
                'color': '#6fcbdf', 'inAll': True, 'sortOrder': -6236426731520,
                'sortType': 'sortOrder', 'userCount': 1, 'etag': '0vbsvn8e', 'modifiedTime': '2021-01-12T23:38:16.456+0000',
                'closed': None, 'muted': False, 'transferred': None, 'groupId': '5ffe2d37b04b35082bbcdf74',
                'viewMode': 'list', 'notificationOptions': None, 'teamId': None,
                'permission': None, 'kind': 'TASK'}
                ```
                **Before**

                [![project-update-before.png](https://i.postimg.cc/K8hcpzvP/project-update-before.png)](https://postimg.cc/crTNrd3C)

                **After**

                [![project-update-after.png](https://i.postimg.cc/DwcWqsdJ/project-update-after.png)](https://postimg.cc/FY7svY6N)

    !!! example "Multiple Project Update"

        === "Changing Multiple Names"

            ```python
            # Lets assume that we have a project named "Writing" that we want to change to "Summer Reading"
            project = client.get_by_fields(name='Writing', search='projects')  # Get the project
            project['name'] = 'Summer Writing'
            # Lets assume that we have a project named "Movies" that we want to change to "Summer Movies"
            movie_project = client.get_by_fields(name='Movies', search='projects')
            movie_project['name'] = 'Summer Movies'
            # Updating multiple projects requires passing the projects in a list.
            update_list = [project, movie_project]
            # Lets update remotely now
            updated_projects = client.project.update(update_list)
            ```

        ??? success "Result"
            A list containing the updated projects is returned.

            ```python
            [{'id': '5ffe24a18f081003f3294c46', 'name': 'Summer Reading',
            'isOwner': True, 'color': '#9730ce', 'inAll': True, 'sortOrder': 0,
            'sortType': None, 'userCount': 1, 'etag': 'bgl0pkm8',
            'modifiedTime': '2021-01-13T00:13:29.796+0000', 'closed': None,
            'muted': False, 'transferred': None, 'groupId': '5ffe11b7b04b356ce74d49da',
            'viewMode': None, 'notificationOptions': None, 'teamId': None, 'permission': None,
            'kind': 'TASK'},

            {'id': '5ffe399c8f08237f3d144ece', 'name': 'Summer Movies', 'isOwner': True,
            'color': '#F18181', 'inAll': True, 'sortOrder': -2843335458816, 'sortType': 'sortOrder',
            'userCount': 1, 'etag': 'jmjy1xtc', 'modifiedTime': '2021-01-13T00:13:29.800+0000',
            'closed': None, 'muted': False, 'transferred': None, 'groupId': '5ffe11b7b04b356ce74d49da',
            'viewMode': None, 'notificationOptions': None, 'teamId': None, 'permission': None, 'kind': 'TASK'}]
            ```

            **Before**

            [![project-update-multiople.png](https://i.postimg.cc/9QbcJH81/project-update-multiople.png)](https://postimg.cc/zyLmG61R)

            **After**

            [![project-update-multiple-after.png](https://i.postimg.cc/3RVGNv2y/project-update-multiple-after.png)](https://postimg.cc/0MGjHrWx)

    """
    # Check the types
    if not isinstance(obj, dict) and not isinstance(obj, list):
        raise TypeError("Project objects must be a dict or list of dicts.")

    if isinstance(obj, dict):
        tasks = [obj]
    else:
        tasks = obj

    url = self._client.BASE_URL + 'batch/project'
    payload = {
        'update': tasks
    }
    response = self._client.http_post(url, json=payload, cookies=self._client.cookies, headers=self.headers)
    self._client.sync()
    if len(tasks) == 1:
        return self._client.get_by_id(self._client.parse_id(response), search='projects')
    else:
        etag = response['id2etag']
        etag2 = list(etag.keys())  # Get the ids
        items = [''] * len(obj)  # Create enough spots for the objects
        for proj_id in etag2:
            found = self._client.get_by_id(proj_id, search='projects')
            for original in obj:
                if found['name'] == original['name']:
                    # Get the index of original
                    index = obj.index(original)
                    # Place found at the index in return list
                    items[index] = found
        return items

update_folder(self, obj)

Updates the project folders(s) remotely based off changes made locally.

Make the changes you want to the project folder(s) first.

Parameters:

Name Type Description Default
obj dict or list

Single Folder (dict): The dictionary object of the folder to update.

Multiple Folders (list): A list containing dictionary objects of folders to update.

required

Returns:

Type Description
dict or list

Single Folder (dict): The dictionary object of the updated folder.

Multiple Folders (list): A list of dictionary objects corresponding to the updated folders.

Exceptions:

Type Description
TypeError

If obj is not a dictionary or list

RuntimeError

If the updating was unsuccessful.

Updating A Project Folder

# Lets assume that we have a folder called "Productivity"
productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
# Lets change the name to "Hobbies"
productivity_folder['name'] = "Hobbies"
# Update
updated_folder = client.project.update_folder(productivity_folder)
Result

The dictionary of the updated folder is returned.

{'id': '5ffe7dab8f089fb5795d8ef2', 'etag': 'r9xl60e5', 'name': 'Hobbies', 'showAll': True,
'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project', 'teamId': None}

Before

image

After

image

# Lets assume that we have a folder called "Productivity"
productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
# Lets assume that we have another folder called "Games"
games_folder = client.get_by_fields(name='Games', search='project_folders')
# Lets change the "Productivity" folder to "Work"
productivity_folder['name'] = "Work"
# Lets change the "Games" folder to "Hobbies"
games_folder['name'] = "Hobbies"
update_list = [productivity_folder, games_folder]  # List of objects to update
# Update
updated_folder = client.project.update_folder(update_list)
Result

A list of the updated folder objects is returned.

[{'id': '5ffe80ce8f08068e86aab288', 'etag': '0oh0pxel', 'name': 'Work', 'showAll': True,
'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project', 'teamId': None},

{'id': '5ffe80cf8f08068e86aab289', 'etag': 'xwvehtfo', 'name': 'Hobbies', 'showAll': True,
'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project', 'teamId': None}]

Before

image

After

image

Source code in managers/projects.py
def update_folder(self, obj):
    """
    Updates the project folders(s) remotely based off changes made locally.

    Make the changes you want to the project folder(s) first.

    Arguments:
        obj (dict or list):
            **Single Folder (dict)**: The dictionary object of the folder to update.

            **Multiple Folders (list)**: A list containing dictionary objects of folders to update.

    Returns:
        dict or list:
        **Single Folder (dict)**: The dictionary object of the updated folder.

        **Multiple Folders (list)**: A list of dictionary objects corresponding to the updated folders.

    Raises:
        TypeError: If `obj` is not a dictionary or list
        RuntimeError: If the updating was unsuccessful.

    !!! example "Updating A Project Folder"
        === "Single Folder Update"

            ```python
            # Lets assume that we have a folder called "Productivity"
            productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
            # Lets change the name to "Hobbies"
            productivity_folder['name'] = "Hobbies"
            # Update
            updated_folder = client.project.update_folder(productivity_folder)
            ```

            ??? success "Result"
                The dictionary of the updated folder is returned.

                ```python
                {'id': '5ffe7dab8f089fb5795d8ef2', 'etag': 'r9xl60e5', 'name': 'Hobbies', 'showAll': True,
                'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project', 'teamId': None}
                ```

                **Before**

                ![image](https://user-images.githubusercontent.com/56806733/104408388-c48bbb80-5518-11eb-80d4-34e82bbaffd7.png)

                **After**

                ![image](https://user-images.githubusercontent.com/56806733/104408436-e1c08a00-5518-11eb-953a-4933f407e4f9.png)

        === "Multiple Folder Update"

            ```python
            # Lets assume that we have a folder called "Productivity"
            productivity_folder = client.get_by_fields(name='Productivity', search='project_folders')
            # Lets assume that we have another folder called "Games"
            games_folder = client.get_by_fields(name='Games', search='project_folders')
            # Lets change the "Productivity" folder to "Work"
            productivity_folder['name'] = "Work"
            # Lets change the "Games" folder to "Hobbies"
            games_folder['name'] = "Hobbies"
            update_list = [productivity_folder, games_folder]  # List of objects to update
            # Update
            updated_folder = client.project.update_folder(update_list)
            ```

            ??? success "Result"
                A list of the updated folder objects is returned.

                ```python
                [{'id': '5ffe80ce8f08068e86aab288', 'etag': '0oh0pxel', 'name': 'Work', 'showAll': True,
                'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project', 'teamId': None},

                {'id': '5ffe80cf8f08068e86aab289', 'etag': 'xwvehtfo', 'name': 'Hobbies', 'showAll': True,
                'sortOrder': 0, 'deleted': 0, 'userId': 447666584, 'sortType': 'project', 'teamId': None}]
                ```

                **Before**

                ![image](https://user-images.githubusercontent.com/56806733/104409143-75468a80-551a-11eb-96c8-5953c97d6f6a.png)

                **After**

                ![image](https://user-images.githubusercontent.com/56806733/104409181-8bece180-551a-11eb-8424-9f147d85eb80.png)
    """
    # Check the types
    if not isinstance(obj, dict) and not isinstance(obj, list):
        raise TypeError("Project objects must be a dict or list of dicts.")

    if isinstance(obj, dict):
        tasks = [obj]
    else:
        tasks = obj

    url = self._client.BASE_URL + 'batch/projectGroup'
    payload = {
        'update': tasks
    }
    response = self._client.http_post(url, json=payload, cookies=self._client.cookies, headers=self.headers)
    self._client.sync()
    if len(tasks) == 1:
        return self._client.get_by_id(self._client.parse_id(response), search='project_folders')
    else:
        etag = response['id2etag']
        etag2 = list(etag.keys())  # Get the ids
        items = [''] * len(tasks)  # Create enough spots for the objects
        for proj_id in etag2:
            found = self._client.get_by_id(proj_id, search='project_folders')
            for original in tasks:
                if found['name'] == original['name']:
                    # Get the index of original
                    index = tasks.index(original)
                    # Place found at the index in return list
                    items[index] = found
        return items