Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cee3fe6
Fix syntax error by reference items on self
ScottG489 Nov 14, 2023
c040fe7
Always fetch checklists regardless of count
ScottG489 Nov 14, 2023
694768a
Create board for checklist tests instead of using existing one
ScottG489 Nov 14, 2023
fbe8f3e
Create test board for board and card tests instead of using existing one
ScottG489 Nov 14, 2023
4bb6fc5
Create test board in trello client tests
ScottG489 Nov 14, 2023
f000a8b
Remove mention of no longer used environment variables from README
ScottG489 Nov 14, 2023
081d09e
Board tests setup and teardown board for each test
ScottG489 Nov 22, 2023
1fb7aba
Reformat whitespace in Board class
ScottG489 Nov 22, 2023
14aea1a
Allow board and list classes to be created from minimal json
ScottG489 Nov 22, 2023
994308c
Formally support batch requests for two resource endpoints
ScottG489 Nov 22, 2023
2f76151
Update project specific details to reference this fork
ScottG489 Nov 22, 2023
9be3c7f
Create python-publish.yml workflow
ScottG489 Nov 22, 2023
ea674e1
Update project specific details to reference this fork
ScottG489 Nov 22, 2023
91f7419
Bump version
ScottG489 Nov 22, 2023
87c2d82
Remove test repo URL for publishing to pypi
ScottG489 Nov 22, 2023
59f1079
Document how to publish to PyPI
ScottG489 Nov 22, 2023
739c409
Bump version to 0.21.0
ScottG489 Nov 22, 2023
c252c01
Allow batch module to be used
ScottG489 Nov 22, 2023
c81fda4
Bump version to 0.22.0
ScottG489 Nov 22, 2023
052f475
Allow card class to be created from minimal json
ScottG489 Nov 27, 2023
f4c4557
Bump version to 0.23.0
ScottG489 Nov 27, 2023
c8c8c38
Initialize trello list object with an empty cards array
ScottG489 Nov 28, 2023
a851462
Bump version to 0.23.1
ScottG489 Nov 28, 2023
ad011ad
Merge remote-tracking branch 'upstream/master'
ScottG489 Aug 19, 2025
28efbc4
Bump version to 0.24.1
ScottG489 Aug 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
release:
types: [published]

permissions:
contents: read

jobs:
deploy:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1 @@
include README.rst
include README.md
33 changes: 24 additions & 9 deletions README.rst → README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ are cached, but the child objects are not. This can possibly be improved when
the API allows for notification subscriptions; this would allow caching
(assuming a connection was available to invalidate the cache as appropriate).

I've created a `Trello Board <https://trello.com/board/py-trello/4f145d87b2f9f15d6d027b53>`_
for feature requests, discussion and some development tracking.

Install
=======

::

pip install py-trello
pip install ha-py-trello

Usage
=====
Expand Down Expand Up @@ -92,12 +89,30 @@ To run the tests, run ``python -m unittest discover``. Four environment variable

* ``TRELLO_API_KEY``: your Trello API key
* ``TRELLO_TOKEN``: your Trello OAuth token
* ``TRELLO_TEST_BOARD_COUNT``: the number of boards in your Trello account
* ``TRELLO_TEST_BOARD_NAME``: name of the board to test card manipulation on. Must be unique, or the first match will be used
* ``TRELLO_TEST_STAR_COUNT``: the number of stars on your test Trello board

*WARNING*: The tests will delete all cards on the board called `TRELLO_TEST_BOARD_NAME`!
*NOTE*: **It's recommended to create a separate Trello account for testing. While the tests try to only modify or delete
resources they've created, to remove all possibility of unintentional data loss, we recommend not using a personal
Trello account with existing data.**

To run tests across various Python versions,
`tox <https://tox.readthedocs.io/en/latest/>`_ is supported. Install it
and simply run ``tox`` from the ``py-trello`` directory.
and simply run ``tox`` from the ``ha-py-trello`` directory.

## Publishing
To publish, simply create a release on GitHub and a workflow will kick off to publish to PyPI. If you'd like to publish
locally, follow the below instructions.

First ensure the appropriate tools are installed locally:
```shell
python3 -m pip install --upgrade build
python3 -m pip install --upgrade twine
```
Then build and publish:
```shell
python3 -m build
python3 -m twine upload dist/*
```
For more information see the [official packaging and publishing docs](https://packaging.python.org/en/latest/tutorials/packaging-projects).

---
*Forked from original: https://github.com/sarumont/py-trello*
16 changes: 8 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
from setuptools import setup, find_packages

setup(
name="py-trello",
version="0.20.1",
name="ha-py-trello",
version="0.24.1",

description='Python wrapper around the Trello API',
long_description=open('README.rst').read(),
author='Richard Kolkovich',
author_email='[email protected]',
url='https://trello.com/board/py-trello/4f145d87b2f9f15d6d027b53',
download_url='https://github.com/sarumont/py-trello',
description='Python wrapper around the Trello API (Home Assistant specific)',
long_description=open('README.md').read(),
author='Scott Giminiani (originally Richard Kolkovich)',
author_email='[email protected]',
url='https://github.com/ScottG489/ha-py-trello',
download_url='https://github.com/ScottG489/ha-py-trello',
keywords='python',
license='BSD License',
classifiers=[
Expand Down
56 changes: 32 additions & 24 deletions test/test_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,33 @@ class TrelloBoardTestCase(unittest.TestCase):
independently.
"""

_test_board_guid = "c0b8eaf5-d63e-4586-a2be-5d1567e22cc9"

@classmethod
def setUpClass(cls):
cls._trello = TrelloClient(os.environ['TRELLO_API_KEY'],
token=os.environ['TRELLO_TOKEN'])
for b in cls._trello.list_boards():
if b.name == os.environ['TRELLO_TEST_BOARD_NAME']:
cls._board = b
break
if not cls._board:
cls.fail("Couldn't find test board")
cls._list = cls._board.add_list(str(datetime.now()))
cls.clean_boards()

@classmethod
def tearDownClass(cls):
cls.clean_boards()

@classmethod
def clean_boards(cls):
trello = TrelloClient(os.environ['TRELLO_API_KEY'],
token=os.environ['TRELLO_TOKEN'])
for board in trello.list_boards():
if cls._test_board_guid in board.name:
board.delete()

def setUp(self):
self._trello = TrelloClient(os.environ['TRELLO_API_KEY'],
token=os.environ['TRELLO_TOKEN'])
self._board = self._trello.add_board(f"TEST BOARD ({TrelloBoardTestCase._test_board_guid})")
self._list = self._board.add_list(str(datetime.now()))
self._add_card("test_card")

def tearDown(self):
self._board.delete()

def _add_card(self, name, description=None):
try:
Expand Down Expand Up @@ -142,36 +158,28 @@ def test90_get_board(self):
self.assertEqual(self._board.name, board.name)

def test100_add_board(self):
test_board = self._trello.add_board("test_create_board")
test_list = test_board.add_list("test_list")
test_list = self._board.add_list("test_list")
test_list.add_card("test_card")
open_boards = self._trello.list_boards(board_filter="open")
self.assertEqual(len([x for x in open_boards if x.name == "test_create_board"]), 1)
self.assertEqual(self._trello.get_board(self._board.id).id, self._board.id)

def test110_copy_board(self):
boards = self._trello.list_boards(board_filter="open")
source_board = next( x for x in boards if x.name == "test_create_board")
self._trello.add_board("copied_board", source_board=source_board)
listed_boards = self._trello.list_boards(board_filter="open")
copied_board = next(iter([x for x in listed_boards if x.name == "copied_board"]), None)
copied_board = self._trello.add_board("Copied " + self._board.name, source_board=self._board)
self.assertIsNotNone(copied_board)
open_lists = copied_board.open_lists()
self.assertEqual(len(open_lists), 4) # default lists plus mine
test_list = open_lists[0]
self.assertEqual(len(test_list.list_cards()), 1)
test_card = next ( iter([ x for x in test_list.list_cards() if x.name == "test_card"]), None )
test_card = next(iter([x for x in test_list.list_cards() if x.name == "test_card"]), None)
self.assertIsNotNone(test_card)
copied_board.delete()

def test120_close_board(self):
boards = self._trello.list_boards(board_filter="open")
open_count = len(boards)
test_create_board = next( x for x in boards if x.name == "test_create_board") # type: Board
copied_board = next( x for x in boards if x.name == "copied_board") # type: Board
test_create_board.close()
copied_board.close()
self._board.close()
still_open_boards = self._trello.list_boards(board_filter="open")
still_open_count = len(still_open_boards)
self.assertEqual(still_open_count, open_count - 2)
self.assertEqual(still_open_count, open_count - 1)

def test130_get_checklists_board(self):
chklists = self._board.get_checklists(cards = 'open')
Expand Down
14 changes: 8 additions & 6 deletions test/test_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ class TrelloCardTestCase(unittest.TestCase):
independently.
"""

_trello = None
_board = None

@classmethod
def setUpClass(cls):
cls._trello = TrelloClient(os.environ['TRELLO_API_KEY'],
token=os.environ['TRELLO_TOKEN'])
for b in cls._trello.list_boards():
if b.name == os.environ['TRELLO_TEST_BOARD_NAME']:
cls._board = b
break
if not cls._board:
cls.fail("Couldn't find test board")
cls._board = cls._trello.add_board("TEST BOARD")
cls._list = cls._board.add_list(str(datetime.now()))

@classmethod
def tearDownClass(cls):
cls._board.delete()

def _add_card(self, name, description=None):
try:
card = self._list.add_card(name, description)
Expand Down
14 changes: 8 additions & 6 deletions test/test_checklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ class TrelloChecklistTestCase(unittest.TestCase):
independently.
"""

_trello = None
_board = None

@classmethod
def setUpClass(cls):
cls._trello = TrelloClient(os.environ['TRELLO_API_KEY'],
token=os.environ['TRELLO_TOKEN'])
for b in cls._trello.list_boards():
if b.name == os.environ['TRELLO_TEST_BOARD_NAME']:
cls._board = b
break
if not cls._board:
cls.fail("Couldn't find test board")
cls._board = cls._trello.add_board("TEST BOARD")
cls._list = cls._board.add_list(str(datetime.now()))

@classmethod
def tearDownClass(cls):
cls._board.delete()

def _add_card(self, name, description=None):
try:
card = self._list.add_card(name, description)
Expand Down
49 changes: 37 additions & 12 deletions test/test_trello_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import os
import unittest
from datetime import datetime
from trello import TrelloClient, Unauthorized, ResourceUnavailable
from trello import TrelloClient, Unauthorized, ResourceUnavailable, Board, List
from trello.batch.board import Board as BatchBoard
from trello.batch.batcherror import BatchError


class TrelloClientTestCase(unittest.TestCase):
Expand All @@ -19,10 +21,38 @@ def setUp(self):
self._trello = TrelloClient(os.environ['TRELLO_API_KEY'],
token=os.environ['TRELLO_TOKEN'])

def test_fetch_batch(self):
board = self._trello.add_board("TEST BOARD")

batch_responses = self._trello.fetch_batch([
BatchBoard.GetLists(board.id, ['id', 'name'], 'open', ['idCard']),
BatchBoard.GetBoard(board.id, ['id', 'name']),
BatchBoard.GetLists('123', ['name'])
])
board_lists = batch_responses[0]
boards = batch_responses[1]
batch_error = batch_responses[2]

self.assertTrue(board_lists.success)
self.assertIsInstance(board_lists.payload[0], List)
self.assertEqual(len(board_lists.payload), 3)

self.assertTrue(boards.success)
self.assertIsInstance(boards.payload, Board)
self.assertEqual(boards.payload.name, "TEST BOARD")

self.assertFalse(batch_error.success)
self.assertIsInstance(batch_error.payload, BatchError)
self.assertEqual(batch_error.payload.message, "invalid id")

board.delete()

def test01_list_boards(self):
self.assertEqual(
len(self._trello.list_boards(board_filter="open")),
int(os.environ['TRELLO_TEST_BOARD_COUNT']))
board = self._trello.add_board("TEST BOARD")
boards = self._trello.list_boards(board_filter="open")
self.assertGreater(len(boards), 0)
self.assertIn(board, boards)
board.delete()

def test10_board_attrs(self):
boards = self._trello.list_boards()
Expand Down Expand Up @@ -111,23 +141,18 @@ def test54_resource_unavailable(self):
self.assertRaises(ResourceUnavailable,
self._trello.get_card, '0')

def test_list_stars(self):
"""
Test trello client star list
"""
self.assertEqual(len(self._trello.list_stars()), int(os.environ["TRELLO_TEST_STAR_COUNT"]), "Number of stars does not match TRELLO_TEST_STAR_COUNT")

def test_add_delete_star(self):
"""
Test add and delete star to/from test board
"""
test_board_id = self._trello.search(os.environ["TRELLO_TEST_BOARD_NAME"])[0].id
new_star = self._trello.add_star(test_board_id)
board = self._trello.add_board("TEST BOARD")
new_star = self._trello.add_star(board.id)
star_list = self._trello.list_stars()
self.assertTrue(new_star in star_list, "Star id was not added in list of starred boards")
deleted_star = self._trello.delete_star(new_star)
star_list = self._trello.list_stars()
self.assertFalse(deleted_star in star_list, "Star id was not deleted from list of starred boards")
board.delete()

class TrelloClientTestCaseWithoutOAuth(unittest.TestCase):
"""
Expand Down
Empty file added trello/batch/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions trello/batch/batcherror.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement, print_function, absolute_import

from trello import TrelloBase
from trello.compat import force_str
from trello.member import Member


class BatchError:
"""
Class representing a BatchError
"""
def __init__(self, status_code, name, message):
super(BatchError, self).__init__()
self.status_code = status_code
self.name = name
self.message = message

@classmethod
def from_json(cls, json_obj):
return BatchError(json_obj['statusCode'], json_obj['name'], json_obj['message'])

def __repr__(self):
return force_str(u'<BatchError %s>' % self.name)
19 changes: 19 additions & 0 deletions trello/batch/batchresponse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement, print_function, absolute_import

from trello import TrelloBase
from trello.compat import force_str
from trello.member import Member


class BatchResponse:
"""
Class representing a BatchError
"""
def __init__(self, payload, success):
super(BatchResponse, self).__init__()
self.payload = payload
self.success = success

def __repr__(self):
return force_str(u'<BatchResponse %s>' % self.success)
Loading