├── .gitignore ├── .pylint.rc ├── .travis.yml ├── LICENSE ├── README.md ├── anydo ├── __init__.py ├── api.py ├── error.py └── lib │ ├── __init__.py │ ├── auth.py │ ├── bind.py │ ├── error.py │ ├── settings.py │ ├── tests │ ├── __init__.py │ ├── test_api.py │ ├── test_error.py │ ├── test_lib.py │ ├── test_pep8.py │ └── test_utils.py │ └── utils.py ├── bin └── runtest.sh ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | var 14 | sdist 15 | develop-eggs 16 | .installed.cfg 17 | lib64 18 | 19 | # Installer logs 20 | pip-log.txt 21 | 22 | # Unit test / coverage reports 23 | .coverage 24 | .tox 25 | nosetests.xml 26 | 27 | # Translations 28 | *.mo 29 | 30 | # Mr Developer 31 | .mr.developer.cfg 32 | .project 33 | .pydevproject 34 | 35 | # Linux 36 | .* 37 | !.gitignore 38 | !.pylint.rc 39 | !.travis.yml 40 | *~ 41 | -------------------------------------------------------------------------------- /.pylint.rc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. See also the "--disable" option for examples. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifiers separated by comma (,) or put this 34 | # option multiple times (only on the command line, not in the configuration 35 | # file where it should appear only once).You can also use "--disable=all" to 36 | # disable everything first and then reenable specific checks. For example, if 37 | # you want to run only the similarities checker, you can use "--disable=all 38 | # --enable=similarities". If you want to run only the classes checker, but have 39 | # no Warning level messages displayed, use"--disable=all --enable=classes 40 | # --disable=W" 41 | #disable= 42 | 43 | 44 | [REPORTS] 45 | 46 | # Set the output format. Available formats are text, parseable, colorized, msvs 47 | # (visual studio) and html. You can also give a reporter class, eg 48 | # mypackage.mymodule.MyReporterClass. 49 | output-format=text 50 | 51 | # Put messages in a separate file for each module / package specified on the 52 | # command line instead of printing them on stdout. Reports (if any) will be 53 | # written in a file name "pylint_global.[txt|html]". 54 | files-output=no 55 | 56 | # Tells whether to display a full report or only the messages 57 | reports=yes 58 | 59 | # Python expression which should return a note less than 10 (10 is the highest 60 | # note). You have access to the variables errors warning, statement which 61 | # respectively contain the number of errors / warnings messages and the total 62 | # number of statements analyzed. This is used by the global evaluation report 63 | # (RP0004). 64 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 65 | 66 | # Add a comment according to your evaluation note. This is used by the global 67 | # evaluation report (RP0004). 68 | comment=no 69 | 70 | # Template used to display messages. This is a python new-style format string 71 | # used to format the message information. See doc for all details 72 | #msg-template= 73 | 74 | 75 | [MISCELLANEOUS] 76 | 77 | # List of note tags to take in consideration, separated by a comma. 78 | notes=FIXME,XXX,TODO 79 | 80 | 81 | [BASIC] 82 | 83 | # Required attributes for module, separated by a comma 84 | required-attributes= 85 | 86 | # List of builtins function names that should not be used, separated by a comma 87 | bad-functions=map,filter,apply 88 | 89 | # Regular expression which should only match correct module names 90 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 91 | 92 | # Regular expression which should only match correct module level names 93 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 94 | 95 | # Regular expression which should only match correct class names 96 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 97 | 98 | # Regular expression which should only match correct function names 99 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 100 | 101 | # Regular expression which should only match correct method names 102 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 103 | 104 | # Regular expression which should only match correct instance attribute names 105 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 106 | 107 | # Regular expression which should only match correct argument names 108 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 109 | 110 | # Regular expression which should only match correct variable names 111 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 112 | 113 | # Regular expression which should only match correct attribute names in class 114 | # bodies 115 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 116 | 117 | # Regular expression which should only match correct list comprehension / 118 | # generator expression variable names 119 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 120 | 121 | # Good variable names which should always be accepted, separated by a comma 122 | good-names=i,j,k,ex,Run,_ 123 | 124 | # Bad variable names which should always be refused, separated by a comma 125 | bad-names=foo,bar,baz,toto,tutu,tata 126 | 127 | # Regular expression which should only match function or class names that do 128 | # not require a docstring. 129 | no-docstring-rgx=__.*__ 130 | 131 | # Minimum line length for functions/classes that require docstrings, shorter 132 | # ones are exempt. 133 | docstring-min-length=-1 134 | 135 | 136 | [FORMAT] 137 | 138 | # Maximum number of characters on a single line. 139 | max-line-length=80 140 | 141 | # Regexp for a line that is allowed to be longer than the limit. 142 | ignore-long-lines=^\s*(# )??$ 143 | 144 | # Allow the body of an if to be on the same line as the test if there is no 145 | # else. 146 | single-line-if-stmt=no 147 | 148 | # List of optional constructs for which whitespace checking is disabled 149 | no-space-check=trailing-comma,dict-separator 150 | 151 | # Maximum number of lines in a module 152 | max-module-lines=1000 153 | 154 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 155 | # tab). 156 | indent-string=' ' 157 | 158 | 159 | [VARIABLES] 160 | 161 | # Tells whether we should check for unused import in __init__ files. 162 | init-import=no 163 | 164 | # A regular expression matching the beginning of the name of dummy variables 165 | # (i.e. not used). 166 | dummy-variables-rgx=_$|dummy 167 | 168 | # List of additional names supposed to be defined in builtins. Remember that 169 | # you should avoid to define new builtins when possible. 170 | additional-builtins= 171 | 172 | 173 | [SIMILARITIES] 174 | 175 | # Minimum lines number of a similarity. 176 | min-similarity-lines=4 177 | 178 | # Ignore comments when computing similarities. 179 | ignore-comments=yes 180 | 181 | # Ignore docstrings when computing similarities. 182 | ignore-docstrings=yes 183 | 184 | # Ignore imports when computing similarities. 185 | ignore-imports=no 186 | 187 | 188 | [TYPECHECK] 189 | 190 | # Tells whether missing members accessed in mixin class should be ignored. A 191 | # mixin class is detected if its name ends with "mixin" (case insensitive). 192 | ignore-mixin-members=yes 193 | 194 | # List of classes names for which member attributes should not be checked 195 | # (useful for classes with attributes dynamically set). 196 | ignored-classes=SQLObject 197 | 198 | # When zope mode is activated, add a predefined set of Zope acquired attributes 199 | # to generated-members. 200 | zope=no 201 | 202 | # List of members which are set dynamically and missed by pylint inference 203 | # system, and so shouldn't trigger E0201 when accessed. Python regular 204 | # expressions are accepted. 205 | generated-members=REQUEST,acl_users,aq_parent 206 | 207 | 208 | [IMPORTS] 209 | 210 | # Deprecated modules which should not be used, separated by a comma 211 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 212 | 213 | # Create a graph of every (i.e. internal and external) dependencies in the 214 | # given file (report RP0402 must not be disabled) 215 | import-graph= 216 | 217 | # Create a graph of external dependencies in the given file (report RP0402 must 218 | # not be disabled) 219 | ext-import-graph= 220 | 221 | # Create a graph of internal dependencies in the given file (report RP0402 must 222 | # not be disabled) 223 | int-import-graph= 224 | 225 | 226 | [DESIGN] 227 | 228 | # Maximum number of arguments for function / method 229 | max-args=5 230 | 231 | # Argument names that match this expression will be ignored. Default to name 232 | # with leading underscore 233 | ignored-argument-names=_.* 234 | 235 | # Maximum number of locals for function / method body 236 | max-locals=15 237 | 238 | # Maximum number of return / yield for function / method body 239 | max-returns=6 240 | 241 | # Maximum number of branch for function / method body 242 | max-branches=12 243 | 244 | # Maximum number of statements in function / method body 245 | max-statements=50 246 | 247 | # Maximum number of parents for a class (see R0901). 248 | max-parents=7 249 | 250 | # Maximum number of attributes for a class (see R0902). 251 | max-attributes=7 252 | 253 | # Minimum number of public methods for a class (see R0903). 254 | min-public-methods=2 255 | 256 | # Maximum number of public methods for a class (see R0904). 257 | max-public-methods=20 258 | 259 | 260 | [CLASSES] 261 | 262 | # List of interface methods to ignore, separated by a comma. This is used for 263 | # instance to not check methods defines in Zope's Interface base class. 264 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 265 | 266 | # List of method names used to declare (i.e. assign) instance attributes. 267 | defining-attr-methods=__init__,__new__,setUp 268 | 269 | # List of valid names for the first argument in a class method. 270 | valid-classmethod-first-arg=cls 271 | 272 | # List of valid names for the first argument in a metaclass class method. 273 | valid-metaclass-classmethod-first-arg=mcs 274 | 275 | 276 | [EXCEPTIONS] 277 | 278 | # Exceptions that will emit a warning when being caught. Defaults to 279 | # "Exception" 280 | overgeneral-exceptions=Exception 281 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 2.7 3 | env: 4 | - TOX_ENV=py26 5 | - TOX_ENV=py27 6 | - TOX_ENV=py32 7 | - TOX_ENV=py33 8 | - TOX_ENV=py34 9 | - TOX_ENV=pypy 10 | install: 11 | - pip install tox coveralls 12 | script: 13 | - tox -e $TOX_ENV 14 | after_success: 15 | coveralls --verbose 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Gaurav Kalra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: **This repository is not maintained anymore.
Refer [Network](https://github.com/gvkalra/python-anydo/network) for active forks** 2 | 3 | python-anydo 4 | ============ 5 | 6 | Unofficial python bindings for Any.Do, an attractive todo list organizer. 7 | 8 | [![Build Status](https://travis-ci.org/gvkalra/python-anydo.png?branch=master)](https://travis-ci.org/gvkalra/python-anydo) 9 | [![Coverage Status](https://coveralls.io/repos/gvkalra/python-anydo/badge.png?branch=master)](https://coveralls.io/r/gvkalra/python-anydo?branch=master) 10 | 11 | The bindings cooperate with the official applications available viz: 12 | 13 | [![Google Play](http://www.any.do/images/download-badges/40px/googleplay.png)](https://play.google.com/store/apps/details?id=com.anydo) 14 | [![App Store](http://www.any.do/images/download-badges/40px/appstore.png)](https://itunes.apple.com/us/app/any.do/id497328576?ls=1&mt=8) 15 | [![Chrome Web Store](http://www.any.do/images/download-badges/40px/chromewebstore.png)](https://chrome.google.com/webstore/detail/anydo/kdadialhpiikehpdeejjeiikopddkjem) 16 | 17 | Usage Guide 18 | ----------------- 19 | Authenticate to Any.Do and create AnyDoAPI object 20 | ```python 21 | from anydo.api import AnyDoAPI 22 | api = AnyDoAPI(username='username@example.org', password='password') 23 | ``` 24 | 25 | **Get User Information** 26 | ```python 27 | api.get_user_info() 28 | ``` 29 | 30 | **Get All Tasks (including Notes)** 31 | ```python 32 | api.get_all_tasks() 33 | ``` 34 | 35 | **Get Task/Note by ID** 36 | ```python 37 | api.get_task_by_id() 38 | ``` 39 | 40 | **Delete Task/Note by ID** 41 | ```python 42 | api.delete_task_by_id() 43 | ``` 44 | 45 | **Get All Categories** 46 | ```python 47 | api.get_all_categories() 48 | ``` 49 | 50 | **Delete Category by ID** 51 | ```python 52 | api.delete_category_by_id() 53 | ``` 54 | 55 | **Create new category** 56 | ```python 57 | api.create_new_category() 58 | ``` 59 | 60 | **Create new task** 61 | ```python 62 | api.create_new_task() 63 | ``` 64 | 65 | Developing python-anydo 66 | -------------------------------------------- 67 | You should add bin/runtest.sh as pre-commit git-hook. 68 | It will help you in verifying your changes locally. 69 | ```bash 70 | $ git clone https://github.com/gvkalra/python-anydo.git 71 | $ cd python-anydo 72 | $ cp bin/runtest.sh .git/hooks/pre-commit 73 | ``` 74 | 75 | License 76 | ----------------- 77 | ```text 78 | The MIT License (MIT) 79 | 80 | Copyright (c) 2014 Gaurav Kalra 81 | 82 | Permission is hereby granted, free of charge, to any person obtaining a copy 83 | of this software and associated documentation files (the "Software"), to deal 84 | in the Software without restriction, including without limitation the rights 85 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 86 | copies of the Software, and to permit persons to whom the Software is 87 | furnished to do so, subject to the following conditions: 88 | 89 | The above copyright notice and this permission notice shall be included in 90 | all copies or substantial portions of the Software. 91 | 92 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 93 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 94 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 95 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 96 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 97 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 98 | THE SOFTWARE. 99 | ``` 100 | 101 | Authors 102 | ----------------- 103 | - Gaurav Kalra () 104 | - Kouhei Maeda () 105 | -------------------------------------------------------------------------------- /anydo/__init__.py: -------------------------------------------------------------------------------- 1 | """ modules of unofficial python bindings for Any.Do """ 2 | -------------------------------------------------------------------------------- /anydo/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.api """ 3 | from anydo.lib.bind import AnyDoAPIBinder 4 | from anydo.lib.utils import create_uuid 5 | from anydo.error import AnyDoAPIError 6 | import time 7 | 8 | 9 | class AnyDoAPI(object): 10 | """ Base class that all AnyDo API client""" 11 | def __init__(self, username=None, password=None): 12 | self.api = AnyDoAPIBinder(username, password) 13 | 14 | def __owner_id(self): 15 | """ Retrieve owner id. 16 | Returns: owner id of AnyDo task 17 | """ 18 | return self.get_user_info().get('id') 19 | 20 | def __default_category_id(self): 21 | """ Retrieve default category id. 22 | Returns: default category id of AnyDo tasks 23 | """ 24 | return [value for _, value in 25 | enumerate(self.get_all_categories()) 26 | if value['isDefault'] is True][0].get('id') 27 | 28 | def get_user_info(self): 29 | """ Fetches user information 30 | 31 | Retrieves information of currently authenticated user 32 | 33 | Args: 34 | None 35 | 36 | Returns: 37 | A dictionary of following user information: 38 | {'anonymous': Boolean, 39 | 'creationDate': Number, 40 | 'email': String, 41 | 'emails': Array, 42 | 'facebookAccessToken': String, 43 | 'facebookId': String, 44 | 'fake': Boolean, 45 | 'id': String, 46 | 'name': String, 47 | 'phoneNumbers': Array} 48 | 49 | Raises: 50 | AnyDoAPIError: 51 | Code(420): JSON Decoding Error. 52 | """ 53 | ret = self.api.user_info() 54 | try: 55 | return ret.json() 56 | except ValueError: 57 | raise AnyDoAPIError(420, "JSON Decoding Error") 58 | 59 | def get_all_tasks(self, response_type="flat", 60 | include_deleted=False, 61 | include_done=False): 62 | """ Retrieve all tasks 63 | 64 | Args: 65 | response_type: "flat" in default 66 | include_deleted: False in default 67 | include_done: False in default 68 | 69 | Returns: 70 | A list of all tasks 71 | 72 | Raises: 73 | AnyDoAPIError: 74 | Code(420): JSON Decoding Error. 75 | """ 76 | ret = self.api.tasks(response_type, 77 | include_deleted, 78 | include_done) 79 | try: 80 | return ret.json() 81 | except ValueError: 82 | raise AnyDoAPIError(420, "JSON Decoding Error") 83 | 84 | def get_all_categories(self, response_type="flat", 85 | include_deleted=False, 86 | include_done=False): 87 | """ Retrieve all categories 88 | 89 | Args: 90 | response_type: "flat" in default 91 | include_deleted: False in default 92 | include_done: False in default 93 | 94 | Returns: 95 | A list of all categories 96 | 97 | Raises: 98 | AnyDoAPIError: 99 | Code(420): JSON Decoding Error. 100 | """ 101 | ret = self.api.categories(response_type, 102 | include_deleted, 103 | include_done) 104 | try: 105 | return ret.json() 106 | except ValueError: 107 | raise AnyDoAPIError(420, "JSON Decoding Error") 108 | 109 | def get_task_by_id(self, task_id): 110 | """ Retrieve a task specified by id 111 | 112 | Args: 113 | task_id: task id formatted uuid 114 | 115 | Returns: 116 | A dictionary of task 117 | 118 | Raises: 119 | AnyDoAPIError: 120 | Code(420): JSON Decoding Error. 121 | """ 122 | ret = self.api.task(uuid=task_id) 123 | try: 124 | return ret.json() 125 | except ValueError: 126 | raise AnyDoAPIError(420, "JSON Decoding Error") 127 | 128 | def delete_task_by_id(self, task_id): 129 | """ Delete a task specified by id 130 | 131 | Args: 132 | task_id: task id formatted uuid 133 | 134 | Returns: 135 | None 136 | 137 | Raises: 138 | AnyDoAPIError: 139 | Code(421): HTTP Error Code. 140 | """ 141 | ret = self.api.delete_task(uuid=task_id) 142 | if ret.status_code != 204: 143 | raise AnyDoAPIError(421, "HTTP Error %d" % ret.status_code) 144 | 145 | def delete_category_by_id(self, category_id): 146 | """ Delete a category specified by id 147 | 148 | Args: 149 | category_id: category id formatted uuid 150 | 151 | Returns: 152 | None 153 | 154 | Raises: 155 | AnyDoAPIError: 156 | Code(421): HTTP Error Code. 157 | Code(422): Invalid Operation. 158 | """ 159 | if category_id == self.__default_category_id(): 160 | raise AnyDoAPIError(422, "Invalid Operation") 161 | ret = self.api.delete_category(uuid=category_id) 162 | if ret.status_code != 204: 163 | raise AnyDoAPIError(421, "HTTP Error %d" % ret.status_code) 164 | 165 | def create_new_category(self, category_name, 166 | default=False, 167 | list_position='null'): 168 | """ Create a new category 169 | 170 | Args: 171 | category_name: string of category name 172 | default: False in default 173 | list_position: 'null' in default 174 | 175 | Returns: 176 | A dictionary of category 177 | 178 | Raises: 179 | AnyDoAPIError: 180 | Code(420): JSON Decoding Error. 181 | """ 182 | ret = self.api.create_category(category_name, 183 | default, 184 | isDefault="true" if default 185 | else "false", 186 | listPosition=list_position, 187 | id=create_uuid()) 188 | try: 189 | return ret.json() 190 | except ValueError: 191 | raise AnyDoAPIError(420, "JSON Decoding Error") 192 | 193 | def create_new_task(self, task_title, due_day='someday'): 194 | """ Create a new task 195 | 196 | Args: 197 | task_title: string of task title 198 | due_day: 'someday' in default 199 | 200 | Returns: 201 | A dictionary of task 202 | 203 | Raises: 204 | AnyDoAPIError: 205 | Code(420): JSON Decoding Error. 206 | Code(422): Invalid Operation 207 | """ 208 | try: 209 | ret = self.api.create_task(task_title, 210 | listPositionByCategory=0, 211 | listPositionByPriority=0, 212 | listPositionByDueDate=0, 213 | status="UNCHECKED", 214 | repeatingMethod="TASK_REPEAT_OFF", 215 | shared="false", 216 | priority="Normal", 217 | creationDate=int(time.time()), 218 | taskExpanded=False, 219 | categoryId=self.__default_category_id(), 220 | dueDate={'someday': None, 221 | 'today': 0}[due_day], 222 | id=create_uuid()) 223 | return ret.json() 224 | except ValueError: 225 | raise AnyDoAPIError(420, "JSON Decoding Error") 226 | except KeyError: 227 | raise AnyDoAPIError(422, "Invalid Operation") 228 | -------------------------------------------------------------------------------- /anydo/error.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.error """ 3 | 4 | class AnyDoAPIError(Exception): 5 | """An AnyDoAPI error occured.""" 6 | def __init__(self, code, msg): 7 | super(AnyDoAPIError, self).__init__(code, msg) 8 | self.code = code 9 | self.msg = msg 10 | 11 | def __str__(self): 12 | return "(%s): %s" % (self.code, self.msg) 13 | -------------------------------------------------------------------------------- /anydo/lib/__init__.py: -------------------------------------------------------------------------------- 1 | """ anydo.lib """ 2 | -------------------------------------------------------------------------------- /anydo/lib/auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.auth """ 3 | import requests 4 | from anydo.lib import settings 5 | 6 | 7 | class AnyDoSession(object): 8 | """Authenticates with Any.Do""" 9 | def __init__(self, username=None, password=None): 10 | self.session = requests.session() 11 | AnyDoSession.post(self, 12 | url='https://sm-prod.any.do/j_spring_security_check', 13 | data={'j_username': username, 'j_password': password, 14 | '_spring_security_remember_me': 'on'}, 15 | headers={'content-type': 16 | 'application/x-www-form-urlencoded'}) 17 | 18 | def get(self, url, **kwargs): 19 | """ HTTP GET method for AnyDo.API 20 | Returns: 21 | :param url: URL for the AnyDo API resources 22 | :param **kwargs: Optional arguments that ``Request`` objects. 23 | """ 24 | return self.session.get(url, proxies=settings.PROXIES, **kwargs) 25 | 26 | def post(self, url, data=None, **kwargs): 27 | """ HTTP POST method for AnyDo.API 28 | Returns: 29 | :param url: URL for the AnyDo API resources 30 | :param data: (optional) Dictionay, 31 | bytes to send in the body of the :class:`Request` 32 | :param **kwargs: Optional arguments that ``Request`` objects. 33 | """ 34 | return self.session.post(url, data, proxies=settings.PROXIES, **kwargs) 35 | 36 | def delete(self, url, **kwargs): 37 | """ HTTP DELETE method for AnyDo.API 38 | Returns: 39 | :param url: URL for the AnyDo API resources 40 | :param **kwargs: Optional arguments that ``Request`` objects. 41 | """ 42 | return self.session.delete(url, proxies=settings.PROXIES, **kwargs) 43 | 44 | def put(self, url, data=None, **kwargs): 45 | """ HTTP PUT method for AnyDo.API 46 | Returns: 47 | :param url: URL for the AnyDo API resources 48 | :param data: (optional) Dictionay, 49 | bytes to send in the body of the :class:`Request` 50 | :param **kwargs: Optional arguments that ``Request`` objects. 51 | """ 52 | return self.session.put(url, data, proxies=settings.PROXIES, **kwargs) 53 | -------------------------------------------------------------------------------- /anydo/lib/bind.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.bind """ 3 | from anydo.lib.utils import encode_string 4 | from anydo.lib.error import AnyDoAPIBinderError 5 | from anydo.lib.auth import AnyDoSession 6 | import re 7 | import json 8 | PATH_TEMPLATE = re.compile(r"{\w+}") # #To support {variable} in paths 9 | 10 | 11 | def bind_method(**config): 12 | """ 13 | Binds Any.Do REST API to AnyDoAPIBinder() 14 | """ 15 | class AnyDoAPIBinderMethod(object): 16 | """ Method class for AnyDoAPIBinder """ 17 | path = config['path'] 18 | method = config['method'] 19 | accepts_parameters = config.get("accepts_parameters", []) 20 | 21 | def __init__(self, api, *args, **kwargs): 22 | self.api = api 23 | self.parameters = {} 24 | self._build_parameters(*args, **kwargs) 25 | self._build_path() 26 | 27 | def _build_parameters(self, *args, **kwargs): 28 | """ building parameters sending to API. 29 | :param *args: 30 | :param **kwargs: 31 | """ 32 | for index, value in enumerate(args): 33 | if value is None: 34 | continue 35 | 36 | try: 37 | self.parameters[self.accepts_parameters[index]] = \ 38 | encode_string(value) 39 | except IndexError: 40 | raise AnyDoAPIBinderError("Index Overflow") 41 | 42 | for key, value in kwargs.items(): 43 | if value is None: 44 | continue 45 | 46 | if key in self.parameters: 47 | raise AnyDoAPIBinderError( 48 | "Already have %s as %s" % (key, self.parameters[key]) 49 | ) 50 | 51 | self.parameters[key] = encode_string(value) 52 | 53 | def _build_path(self): 54 | """ building API path. """ 55 | for var in PATH_TEMPLATE.findall(self.path): 56 | name = var.strip("{}") 57 | 58 | try: 59 | value = self.parameters[name] 60 | except KeyError: 61 | raise AnyDoAPIBinderError("Could not find %s" % name) 62 | del self.parameters[name] # #Python won my heart! 63 | 64 | self.path = self.path.replace(var, value) 65 | 66 | def execute(self): 67 | """ executing API calling. """ 68 | if self.method == 'GET': 69 | return self.api.get(self.api.host + self.path, 70 | params=self.parameters) 71 | if self.method == 'DELETE': 72 | return self.api.delete(self.api.host + self.path) 73 | if self.method == 'POST': 74 | data = [] 75 | data.append(self.parameters) 76 | return self.api.post(self.api.host + self.path, 77 | data=str(json.dumps([item 78 | for item in data])), 79 | headers={'Content-Type': 80 | 'application/json'}) 81 | if self.method == 'PUT': 82 | return self.api.put(self.api.host + self.path, 83 | data=str(json.dumps(self.parameters)), 84 | headers={'Content-Type': 85 | 'application/json'}) 86 | 87 | def _call(self, *args, **kwargs): 88 | """ 89 | :param *args: 90 | :param **kwargs: 91 | """ 92 | # self=AnyDoAPIBinder(); satisfy pychecker 93 | method = AnyDoAPIBinderMethod(self, *args, **kwargs) 94 | return method.execute() 95 | 96 | return _call 97 | 98 | 99 | class AnyDoAPIBinder(AnyDoSession): 100 | """ Binder of AnyDoSession class """ 101 | host = "https://sm-prod.any.do" 102 | 103 | def __init__(self, username, password): 104 | super(AnyDoAPIBinder, self).__init__(username=username, 105 | password=password) 106 | 107 | # Fetches user information 108 | user_info = bind_method(path="/me", 109 | method="GET") 110 | 111 | # Fetches tasks (including notes) 112 | tasks = bind_method(path="/me/tasks", 113 | method="GET", 114 | accepts_parameters=["responseType", 115 | "includeDeleted", 116 | "includeDone"]) 117 | 118 | # Fetches categories 119 | categories = bind_method(path="/me/categories", 120 | method="GET", 121 | accepts_parameters=["responseType", 122 | "includeDeleted", 123 | "includeDone"]) 124 | 125 | # Fetches task/note by UUID 126 | task = bind_method(path="/me/tasks/{uuid}", 127 | method="GET", 128 | accepts_parameters=["uuid"]) 129 | 130 | # Deletes a task/note by UUID 131 | delete_task = bind_method(path="/me/tasks/{uuid}", 132 | method="DELETE", 133 | accepts_parameters=["uuid"]) 134 | 135 | # Deletes a category by UUID 136 | delete_category = bind_method(path="/me/categories/{uuid}", 137 | method="DELETE", 138 | accepts_parameters=["uuid"]) 139 | 140 | # Creates a new category 141 | create_category = bind_method(path="/me/categories", 142 | method="POST", 143 | accepts_parameters=["name", 144 | "default", 145 | "isDefault", 146 | "listPosition", 147 | "id"]) 148 | 149 | # Creates a new task/note 150 | create_task = bind_method(path="/me/tasks", 151 | method="POST", 152 | accepts_parameters=["title", 153 | "listPositionByCategory", 154 | "listPositionByPriority", 155 | "listPositionByDueDate", 156 | "status", 157 | "repeatingMethod", 158 | "shared", 159 | "priority", 160 | "creationDate", 161 | "taskExpanded", 162 | "parentGlobalTaskId", 163 | "categoryId", 164 | "dueDate", 165 | "id"]) 166 | -------------------------------------------------------------------------------- /anydo/lib/error.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.error """ 3 | 4 | 5 | class AnyDoAPIBinderError(Exception): 6 | """An AnyDoAPIBinder error occured.""" 7 | def __init__(self, msg): 8 | super(AnyDoAPIBinderError, self).__init__(msg) 9 | self.msg = msg 10 | 11 | def __str__(self): 12 | return self.msg 13 | -------------------------------------------------------------------------------- /anydo/lib/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.settings """ 3 | PROXIES = None 4 | USERNAME = "username@example.org" 5 | PASSWORD = "password" 6 | -------------------------------------------------------------------------------- /anydo/lib/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ anydo.lib.tests """ 2 | -------------------------------------------------------------------------------- /anydo/lib/tests/test_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.tests.test_api """ 3 | 4 | import unittest 5 | from httpretty import HTTPretty, httprettified 6 | from anydo import api 7 | from anydo.error import AnyDoAPIError 8 | from anydo.lib.bind import AnyDoAPIBinder 9 | from anydo.lib import settings 10 | from anydo.lib import utils 11 | 12 | 13 | class AnyDoAPITests(unittest.TestCase): 14 | """ unit test of anydo.lib.api """ 15 | 16 | @httprettified 17 | def setUp(self): 18 | HTTPretty.register_uri( 19 | HTTPretty.POST, 20 | '%s/j_spring_security_check' % AnyDoAPIBinder.host) 21 | self.conn = api.AnyDoAPI(username=settings.USERNAME, 22 | password=settings.PASSWORD) 23 | 24 | @httprettified 25 | def test_get_user_info(self): 26 | """ unit test of get_user_info """ 27 | HTTPretty.register_uri( 28 | HTTPretty.GET, 29 | '%s/me' % AnyDoAPIBinder.host, 30 | body='{"dummy_key": "dummy_value"}') 31 | self.assertEqual({'dummy_key': 'dummy_value'}, 32 | self.conn.get_user_info()) 33 | 34 | @httprettified 35 | def test_get_user_info_fail(self): 36 | """ unit test of get_user_info error case """ 37 | HTTPretty.register_uri( 38 | HTTPretty.GET, 39 | '%s/me' % AnyDoAPIBinder.host) 40 | self.assertRaises(AnyDoAPIError, self.conn.get_user_info) 41 | 42 | @httprettified 43 | def test_get_all_tasks(self): 44 | """ unit test of get_all_tasks """ 45 | HTTPretty.register_uri( 46 | HTTPretty.GET, 47 | '%s/me/tasks' % AnyDoAPIBinder.host, 48 | body='{"dummy_key": "dummy_value"}') 49 | self.assertEqual({"dummy_key": "dummy_value"}, 50 | self.conn.get_all_tasks()) 51 | 52 | @httprettified 53 | def test_get_all_tasks_fail(self): 54 | """ unit test of get_all_tasks error case """ 55 | HTTPretty.register_uri( 56 | HTTPretty.GET, 57 | '%s/me/tasks' % AnyDoAPIBinder.host) 58 | self.assertRaises(AnyDoAPIError, self.conn.get_all_tasks) 59 | 60 | @httprettified 61 | def test_get_all_categories(self): 62 | """ unit test of get_all_categories """ 63 | HTTPretty.register_uri( 64 | HTTPretty.GET, 65 | '%s/me/categories' % AnyDoAPIBinder.host, 66 | body='{"dummy_key": "dummy_value"}') 67 | self.assertEqual({"dummy_key": "dummy_value"}, 68 | self.conn.get_all_categories()) 69 | 70 | @httprettified 71 | def test_get_all_categories_fail(self): 72 | """ unit test of get_all_categories error case """ 73 | HTTPretty.register_uri( 74 | HTTPretty.GET, 75 | '%s/me/categories' % AnyDoAPIBinder.host) 76 | self.assertRaises(AnyDoAPIError, self.conn.get_all_categories) 77 | 78 | @httprettified 79 | def test_get_task_by_id(self): 80 | """ unit test of get_task_by_id """ 81 | uuid = utils.create_uuid() 82 | HTTPretty.register_uri( 83 | HTTPretty.GET, 84 | '%s/me/tasks/%s' % (AnyDoAPIBinder.host, uuid), 85 | body='{"dummy_key": "dummy_value"}') 86 | self.assertEqual({"dummy_key": "dummy_value"}, 87 | self.conn.get_task_by_id(uuid)) 88 | 89 | @httprettified 90 | def test_get_task_by_id_fail(self): 91 | """ unit test of get_task_by_id error case """ 92 | uuid = utils.create_uuid() 93 | HTTPretty.register_uri( 94 | HTTPretty.GET, 95 | '%s/me/tasks/%s' % (AnyDoAPIBinder.host, uuid)) 96 | self.assertRaises(AnyDoAPIError, self.conn.get_task_by_id, uuid) 97 | 98 | @httprettified 99 | def test_delete_task_by_id(self): 100 | """ unit test of delete_task_by_id """ 101 | uuid = utils.create_uuid() 102 | HTTPretty.register_uri( 103 | HTTPretty.DELETE, 104 | '%s/me/tasks/%s' % (AnyDoAPIBinder.host, uuid), 105 | status=204) 106 | self.assertEqual(None, 107 | self.conn.delete_task_by_id(uuid)) 108 | 109 | @httprettified 110 | def test_delete_task_by_id_fail(self): 111 | """ unit test of delete_task_by_id error case """ 112 | uuid = utils.create_uuid() 113 | HTTPretty.register_uri( 114 | HTTPretty.DELETE, 115 | '%s/me/tasks/%s' % (AnyDoAPIBinder.host, uuid), 116 | status=503) 117 | self.assertRaises(AnyDoAPIError, self.conn.delete_task_by_id, uuid) 118 | 119 | @httprettified 120 | def test_delete_category_by_id(self): 121 | """ unit test of delete_category_by_id """ 122 | uuid = utils.create_uuid() 123 | HTTPretty.register_uri( 124 | HTTPretty.GET, 125 | '%s/me/categories' % AnyDoAPIBinder.host, 126 | body='[{"isDefault": true, "id": "dummy_category0"}]') 127 | HTTPretty.register_uri( 128 | HTTPretty.DELETE, 129 | '%s/me/categories/%s' % (AnyDoAPIBinder.host, uuid), 130 | status=204) 131 | self.assertEqual(None, 132 | self.conn.delete_category_by_id(uuid)) 133 | 134 | @httprettified 135 | def test_delete_category_by_id_fail(self): 136 | """ unit test of delete_category_by_id error case """ 137 | uuid = utils.create_uuid() 138 | HTTPretty.register_uri( 139 | HTTPretty.GET, 140 | '%s/me/categories' % AnyDoAPIBinder.host, 141 | body='[{"isDefault": true, "id": "dummy_category0"}]') 142 | HTTPretty.register_uri( 143 | HTTPretty.DELETE, 144 | '%s/me/categories/%s' % (AnyDoAPIBinder.host, uuid), 145 | status=503) 146 | self.assertRaises(AnyDoAPIError, self.conn.delete_category_by_id, uuid) 147 | 148 | @httprettified 149 | def test_create_new_category(self): 150 | """ unit test of create_new_category """ 151 | uuid = utils.create_uuid() 152 | HTTPretty.register_uri( 153 | HTTPretty.POST, 154 | '%s/me/categories' % AnyDoAPIBinder.host, 155 | body='{"dummy_key": "dummy_value"}', 156 | status=201) 157 | self.assertEqual({"dummy_key": "dummy_value"}, 158 | self.conn.create_new_category(uuid)) 159 | 160 | @httprettified 161 | def test_create_new_category_fail(self): 162 | """ unit test of create_new_category error case """ 163 | uuid = utils.create_uuid() 164 | HTTPretty.register_uri( 165 | HTTPretty.POST, 166 | '%s/me/categories' % AnyDoAPIBinder.host) 167 | self.assertRaises(AnyDoAPIError, self.conn.create_new_category, uuid) 168 | 169 | @httprettified 170 | def test_create_new_task(self): 171 | """ unit test of create_new_task """ 172 | uuid = utils.create_uuid() 173 | HTTPretty.register_uri( 174 | HTTPretty.GET, 175 | '%s/me/categories' % AnyDoAPIBinder.host, 176 | body='[{"isDefault": true, "id": "dummy_category0"}]') 177 | 178 | HTTPretty.register_uri( 179 | HTTPretty.POST, 180 | '%s/me/tasks' % AnyDoAPIBinder.host, 181 | body='{"dummy_key": "dummy_value"}', 182 | status=201) 183 | self.assertEqual({"dummy_key": "dummy_value"}, 184 | self.conn.create_new_task(uuid)) 185 | 186 | @httprettified 187 | def test_create_new_task_fail(self): 188 | """ unit test of create_new_task error case """ 189 | uuid = utils.create_uuid() 190 | HTTPretty.register_uri( 191 | HTTPretty.GET, 192 | '%s/me/categories' % AnyDoAPIBinder.host, 193 | body='[{"isDefault": true, "id": "dummy_category0"}]') 194 | 195 | HTTPretty.register_uri( 196 | HTTPretty.POST, 197 | '%s/me/tasks' % AnyDoAPIBinder.host) 198 | self.assertRaises(AnyDoAPIError, self.conn.create_new_task, uuid) 199 | -------------------------------------------------------------------------------- /anydo/lib/tests/test_error.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.tests.test_error """ 3 | 4 | import unittest 5 | from anydo import error 6 | from anydo.lib import error as lib_error 7 | 8 | 9 | class ErrorTests(unittest.TestCase): 10 | """ unit test of anydo.error and anydo.lib.error """ 11 | 12 | def test_error_msg(self): 13 | """ unit test error_msg of anydo.error """ 14 | self.assertEqual(error.AnyDoAPIError('dummy', 'test').__str__(), 15 | '(dummy): test') 16 | 17 | def test_lib_error_msg(self): 18 | """ unit test error_msg of anydo.lib.error """ 19 | self.assertEqual(lib_error.AnyDoAPIBinderError('test').__str__(), 20 | 'test') 21 | -------------------------------------------------------------------------------- /anydo/lib/tests/test_lib.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.tests.test_lib """ 3 | import unittest 4 | from httpretty import HTTPretty, httprettified 5 | import time 6 | from anydo.lib.bind import AnyDoAPIBinder 7 | from anydo.lib import settings 8 | from anydo.lib import utils 9 | 10 | 11 | class TestAnyDoAPIBinder(AnyDoAPIBinder): 12 | """ unit test of AnyDoAPIBinder class """ 13 | def __getattribute__(self, attr): 14 | val = super(TestAnyDoAPIBinder, self).__getattribute__(attr) 15 | return val 16 | 17 | 18 | class AnyDoAPIBinderTests(unittest.TestCase): 19 | """ unit test of AnyDoAPIBind """ 20 | setup_done = False # #TODO: setUpClass ? 21 | 22 | @httprettified 23 | def setUp(self): 24 | if not self.setup_done: 25 | HTTPretty.register_uri( 26 | HTTPretty.POST, 27 | '%s/j_spring_security_check' % AnyDoAPIBinder.host) 28 | super(AnyDoAPIBinderTests, self).setUp() 29 | self.__class__.api = TestAnyDoAPIBinder(username=settings.USERNAME, 30 | password=settings.PASSWORD) 31 | self.__class__.setup_done = True 32 | 33 | @httprettified 34 | def test_user_info(self): 35 | """ unit test of user_info """ 36 | HTTPretty.register_uri(HTTPretty.GET, 37 | '%s/me' % AnyDoAPIBinder.host) 38 | ret = self.api.user_info() 39 | self.assertEqual(ret.status_code, 200) 40 | 41 | @httprettified 42 | def test_tasks(self): 43 | """ unit test of tasks """ 44 | HTTPretty.register_uri(HTTPretty.GET, 45 | '%s/me/tasks' % AnyDoAPIBinder.host) 46 | ret = self.api.tasks(responseType="flat", 47 | includeDeleted="false", 48 | includeDone="false") 49 | self.assertEqual(ret.status_code, 200) 50 | 51 | @httprettified 52 | def test_categories(self): 53 | """ unit test of categories """ 54 | HTTPretty.register_uri(HTTPretty.GET, 55 | '%s/me/categories' % AnyDoAPIBinder.host) 56 | ret = self.api.categories(responseType="flat", 57 | includeDeleted="false", 58 | includeDone="false") 59 | self.assertEqual(ret.status_code, 200) 60 | 61 | @httprettified 62 | def test_task(self): 63 | """ unit test of task """ 64 | task_id = utils.create_uuid() 65 | HTTPretty.register_uri(HTTPretty.GET, 66 | '%s/me/tasks/%s' % (AnyDoAPIBinder.host, 67 | task_id)) 68 | ret = self.api.task(uuid=task_id) 69 | self.assertEqual(ret.status_code, 200) 70 | 71 | @httprettified 72 | def test_delete_task(self): 73 | """ unit test of delete task """ 74 | task_id = utils.create_uuid() 75 | HTTPretty.register_uri(HTTPretty.DELETE, 76 | '%s/me/tasks/%s' % (AnyDoAPIBinder.host, 77 | task_id), 78 | status=204) 79 | ret = self.api.delete_task(uuid=task_id) 80 | self.assertEqual(ret.status_code, 204) 81 | 82 | @httprettified 83 | def test_delete_category(self): 84 | """ unit test of delete category """ 85 | category_id = utils.create_uuid() 86 | HTTPretty.register_uri(HTTPretty.DELETE, 87 | '%s/me/categories/%s' % (AnyDoAPIBinder.host, 88 | category_id), 89 | status=204) 90 | ret = self.api.delete_category(uuid=category_id) 91 | self.assertEqual(ret.status_code, 204) 92 | 93 | @httprettified 94 | def test_create_category(self): 95 | """ unit test of create category """ 96 | category_id = utils.create_uuid() 97 | HTTPretty.register_uri(HTTPretty.POST, 98 | '%s/me/categories' % AnyDoAPIBinder.host, 99 | status=201) 100 | ret = self.api.create_category(name="ANY.DO_TEST_CATEGORY", 101 | default="false", 102 | isDefault="false", 103 | listPosition="null", 104 | id=category_id) 105 | self.assertEqual(ret.status_code, 201) 106 | 107 | @httprettified 108 | def test_create_task(self): 109 | """ unit test of create task """ 110 | category_id = utils.create_uuid() 111 | task_id = utils.create_uuid() 112 | HTTPretty.register_uri(HTTPretty.POST, 113 | '%s/me/tasks' % AnyDoAPIBinder.host, 114 | status=200) 115 | ret = self.api.create_task(title="ANY.DO_TEST_TASK", 116 | listPositionByCategory=0, 117 | listPositionByPriority=0, 118 | listPositionByDueDate=0, 119 | status="UNCHECKED", 120 | repeatingMethod="TASK_REPEAT_OFF", 121 | shared="false", 122 | priority="Normal", 123 | creationDate=str(int(time.time())), 124 | taskExpanded="false", 125 | categoryId=str(category_id), 126 | dueDate=None, 127 | id=task_id) 128 | self.assertEqual(ret.status_code, 200) 129 | 130 | @httprettified 131 | def test_create_note(self): 132 | """ unit test of create note """ 133 | task_id = utils.create_uuid() 134 | category_id = utils.create_uuid() 135 | note_id = utils.create_uuid() 136 | HTTPretty.register_uri(HTTPretty.POST, 137 | '%s/me/tasks' % AnyDoAPIBinder.host) 138 | # Add note in task 139 | ret = self.api.create_task(title="ANY.DO_TEST_NOTE", 140 | listPositionByCategory=0, 141 | listPositionByPriority=0, 142 | listPositionByDueDate=0, 143 | status="UNCHECKED", 144 | repeatingMethod="TASK_REPEAT_OFF", 145 | shared="false", 146 | priority="Normal", 147 | creationDate=str(int(time.time())), 148 | taskExpanded="false", 149 | parentGlobalTaskId=task_id, 150 | categoryId=str(category_id), 151 | dueDate=None, 152 | id=note_id) 153 | self.assertEqual(ret.status_code, 200) 154 | -------------------------------------------------------------------------------- /anydo/lib/tests/test_pep8.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Copyright 2011 Takeshi KOMIYA 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | original source from: ('https://bitbucket.org/tk0miya/blockdiag' 18 | '/src/0789c102744c92767f0f0efb87b1b297741bb04c' 19 | '/src/blockdiag/tests/test_pep8.py') 20 | """ 21 | from __future__ import print_function 22 | import unittest 23 | import os 24 | import sys 25 | import pep8 26 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 27 | BASE_DIR = os.path.dirname(CURRENT_DIR) 28 | sys.path.append(CURRENT_DIR) 29 | 30 | 31 | class Pep8Tests(unittest.TestCase): 32 | """ Unit test Pep8 """ 33 | 34 | def test_pep8(self): 35 | """ runner """ 36 | arglist = [['statistics', True], 37 | ['show-source', True], 38 | ['repeat', True], 39 | ['paths', [BASE_DIR]]] 40 | 41 | pep8style = pep8.StyleGuide(arglist, 42 | parse_argv=False, 43 | config_file=True) 44 | options = pep8style.options 45 | report = pep8style.check_files() 46 | if options.statistics: 47 | report.print_statistics() 48 | 49 | # reporting errors (additional summary) 50 | errors = report.get_count('E') 51 | warnings = report.get_count('W') 52 | message = 'pep8: %d errors / %d warnings' % (errors, warnings) 53 | print(message) 54 | assert report.total_errors == 0, message 55 | -------------------------------------------------------------------------------- /anydo/lib/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.tests.test_utils """ 3 | import unittest 4 | import re 5 | import sys 6 | from anydo.lib import utils 7 | 8 | 9 | class UtilsTests(unittest.TestCase): 10 | """ unit test of anydo.lib.utils """ 11 | 12 | def setUp(self): 13 | self.pattern = re.compile(r'(^([\w-]+)==$)', flags=re.U) 14 | 15 | def test_create_uuid(self): 16 | """ unit test of create_uuid """ 17 | self.assertTrue(self.pattern.match(utils.create_uuid())) 18 | 19 | def test_encode_string(self): 20 | """ unit test of encode_string """ 21 | self.assertEqual(utils.encode_string('test'), 'test') 22 | self.assertEqual(utils.encode_string('1234'), '1234') 23 | self.assertEqual(utils.encode_string('test1234 Äë'), 'test1234 Äë') 24 | 25 | # "テスト" means "test" in Japansese. 26 | if sys.version_info < (3, 0): 27 | word = ('\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88 123eA' 28 | '\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88') 29 | else: 30 | word = 'テスト 123eAテスト' 31 | self.assertEqual(utils.encode_string('テスト 123eAテスト'), word) 32 | -------------------------------------------------------------------------------- /anydo/lib/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ anydo.lib.utils """ 3 | import sys 4 | import base64 5 | import uuid 6 | 7 | 8 | def encode_string(value): 9 | """ encoding string 10 | Returns: value | utf-8 encoded string 11 | :param value: utf-8 encoded string or unicode 12 | """ 13 | if sys.version_info < (3, 0): 14 | return value.encode('utf-8') \ 15 | if isinstance(value, unicode) else str(value) 16 | else: 17 | return value 18 | 19 | 20 | def create_uuid(): 21 | """ creating uuid. 22 | Returns: utf-8 encoded and base64 encoded uuid that is as follows; 23 | e.g. u'08s-gOkKSWC26ntWUGkKIQ==' 24 | """ 25 | unique_id = base64.b64encode(uuid.uuid4().bytes) 26 | safe_id = unique_id.replace(b"+", b"-").replace(b"/", b"_") 27 | return safe_id.decode('utf-8') 28 | -------------------------------------------------------------------------------- /bin/runtest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | clean () { 4 | #python 5 | find . -name "*.pyc" -delete 6 | 7 | #linux 8 | find . -name "*~" -delete 9 | } 10 | 11 | cd $(git rev-parse --show-toplevel) 12 | clean 13 | 14 | if which pip > /dev/null; then 15 | pip install --upgrade pylint 16 | pip install --upgrade tox 17 | pylint --rcfile=.pylint.rc anydo/*.py anydo/lib/*.py anydo/lib/tests/*.py 18 | tox 19 | fi 20 | 21 | clean 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import setup, find_packages 4 | from setuptools.command.test import test as TestCommand 5 | import sys 6 | 7 | 8 | class Tox(TestCommand): 9 | def finalize_options(self): 10 | TestCommand.finalize_options(self) 11 | self.test_args = [] 12 | self.test_suite = True 13 | 14 | def run_tests(self): 15 | import tox 16 | errno = tox.cmdline(self.test_args) 17 | sys.exit(errno) 18 | 19 | 20 | classifiers = [ 21 | "License :: OSI Approved :: MIT License", 22 | "Programming Language :: Python", 23 | "Programming Language :: Python :: 2", 24 | "Programming Language :: Python :: 2.6", 25 | "Programming Language :: Python :: 2.7", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.2", 28 | "Programming Language :: Python :: 3.3", 29 | "Programming Language :: Python :: Implementation :: CPython", 30 | "Programming Language :: Python :: Implementation :: PyPy", 31 | "Topic :: Internet", 32 | "Topic :: Internet :: WWW/HTTP", 33 | "Topic :: Software Development :: Libraries :: Python Modules", 34 | ] 35 | 36 | setup(name="python-anydo", 37 | version="0.1.0", 38 | description="Unoffical Any.Do API client", 39 | license="MIT", 40 | install_requires=["requests"], 41 | author="Gaurav Kalra", 42 | author_email="gvkalra@gmail.com", 43 | url="https://github.com/gvkalra/python-anydo", 44 | classifiers=classifiers, 45 | packages=find_packages(), 46 | keywords="anydo", 47 | tests_require=['tox'], 48 | cmdclass={'test': Tox}, 49 | zip_safe=True) 50 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26,py27,py32,py33,py34,pypy 3 | 4 | [testenv] 5 | commands = 6 | {envpython} --version 7 | coverage run --source=anydo -m py.test -v 8 | 9 | [py] 10 | deps = 11 | pytest 12 | pep8 13 | coverage 14 | httpretty 15 | 16 | [testenv:py26] 17 | deps= 18 | {[py]deps} 19 | basepython = python2.6 20 | 21 | [testenv:py27] 22 | deps= 23 | {[py]deps} 24 | basepython = python2.7 25 | 26 | [testenv:py32] 27 | deps= 28 | {[py]deps} 29 | basepython = python3.2 30 | 31 | [testenv:py33] 32 | deps= 33 | {[py]deps} 34 | basepython = python3.3 35 | 36 | [testenv:py34] 37 | deps= 38 | {[py]deps} 39 | basepython = python3.4 40 | 41 | [testenv:pypy] 42 | deps= 43 | {[py]deps} 44 | basepython = pypy 45 | --------------------------------------------------------------------------------